Skip to content

State Flow and Event Boundary

Runtime contract baseline: 0.4.0 · Updated 2026-08-29

This is the authoritative Core Engine data-flow and cross-object communication boundary for Contracts, StateFlow, StateGraph, and StateGraphHost.

CoCoFlow 0.4 reduces an Actor’s gameplay processing to a one-way State Flow. External input first becomes frozen Intent. StateGraph only interprets that input and produces execution instructions. Operators perform world side effects, and the Commit Barrier finally produces the Actor’s complete state. Cross-object communication cannot bypass this Flow to modify StateGraph or ContextFrame directly.

In this document, ContextFrame means the complete committed logical state of one Actor after a Tick. It is not a “frozen fact surface before StateGraph,” a world-level snapshot, or a Unity object graph.

Actor A
StateGraph -> OperationFrame -> Operators
-> Outcomes + EventOutbox candidates
-> Commit ContextFrame A
-> successful commit assigns EventSequence and publishes EventOutbox
Cross Object
CoCoEventPacket<TEvent>
-> Host Gateway
-> internal EventRouter for one EventDomain
-> Actor B Incoming EventInbox
Actor B next accepted CoCoTick
Input / AI + sealed EventInbox
-> Event-to-Intent Adapters
-> IntentFrame B
+ Previous ContextFrame B
-> StateGraph
-> OperationFrame Sections
-> Operators
-> Outcomes + EventOutbox candidates
-> Commit ContextFrame B

As an analogy, the Host Gateway is the sending and receiving point, the EventEnvelope is the shipping label, the EventRouter is the cross-Actor sorting center, and the Actor EventInbox is the mailbox at the door. An Event-to-Intent Adapter translates incoming mail into this Actor’s Intent. Actor-local events go directly from the Gateway into their own Inbox without passing through the Router. StateGraph only ever reads the translated, frozen IntentFrame.

IntentFrame is the sole input surface for one CoCoTick:

  • Input, AI, Host Sampling, and sealed EventInbox Adapters can only provide candidates.
  • Candidates are arbitrated by the Priority/Reducer fixed before Running.
  • Each Source is sampled at most once per Tick, and the Frame is frozen once.
  • Each GraphRuntimeInstance creates and exclusively owns its Reducer instances through setup-only factories. Actors never share mutable Reducer state.
  • Before Freeze, the value state of every Reducer is checkpointed. If any Reducer fails or the lifecycle is interrupted, all Reducers and the partial IntentFrame roll back together.
  • Every Layer in the same Tick reads the same Frame identity and values.
  • No candidates still produces a valid empty IntentFrame.
  • It stores no raw EventPacket, Envelope, Source object, or Unity input API.
  • It never enters ContextFrame, Temporal History, or Persistence.
  • Pause/Suspend produces no Tick, so nothing is sampled or arbitrated and no new IntentFrame is produced.
  • Starting the next Collection, Cancel, Timeline Reset, and Dispose immediately invalidate the previous IntentFrame. When a Source, Adapter, or Reducer throws, Collection first rolls back and then rethrows.
  • Cancel also rolls back the Inbox Projection Claim and prevents the same Tick from beginning again.
  • User Source/Adapter/Reducer callbacks cannot re-enter Begin, Sample, Project, Freeze, or Cancel. A Dispose requested inside a callback is deferred until the callback exits and then takes priority.

Conflicts between Sources depend only on explicit Priority and Reducer rules, not EventBus arrival order. Discrete input from one Source preserves order with SourceEventSequence.

OperationFrame is the complete execution guide produced by StateGraph for the current Tick and is the only Frame with public Section contracts:

  • An Operator declares the execution data it requires through a Section interface.
  • The deduplicated union of Operator requirements must exactly match Graph Operation Provides in both directions.
  • When several Operators require the same interface, it is deduplicated by interface identity.
  • Interfaces with the same field shape but different identities are always different Sections.
  • A Section interface may inherit only the framework marker. Section-to-Section inheritance is invalid.
  • The Provides Manifest must contain the complete Section shape: total byte size, field count, and each field’s dense index, ordinal name, unmanaged type, byte offset, and size.
  • Sections are read-only while Operators execute and cannot serve as persistent Actor state.
  • Discrete execution uses structured Sections with explicit Enabled, ActivationId, and Sequence values.
  • No input produces a valid Disabled/Zero Section rather than a second command queue.

StateGraph produces Sections through a restricted writer with a fixed composition rank. Layers later in the Layer list rank higher, and a child State ranks above its parent within one Layer. At the same rank, the later lifecycle call overwrites the earlier one, but invocation order cannot reverse rank. A Leaf-to-Parent Exit therefore cannot let the parent overwrite the child. Continuous Sections compose per field. A Discrete Section allocates Sequence only once for the final winning contribution.

OperationFrame uses an independent two-phase protocol:

TryBegin -> Write -> TryFinalize -> FinalizedFrame -> Commit / Cancel

TryFinalize freezes candidate data only. It neither updates LastTick nor consumes OperationSequence. Runtime places the Finalized OperationFrame, candidate ActiveLeaf, Memory, and Clock into a single-use staged Tick. No next Step can begin before that Tick is accepted or cancelled. The Host accepts it only through a composite commit barrier shared with Context, Claims, Clock, and Sequence. Cancellation keeps the old Path, Memory, Clock, Context Revision, and sequence authority unchanged.

Contracts defines descriptors, registries, fixed Layout input, and explicit test construction seams. The Compiler automatically aggregates, validates, and compiles the Provides Manifest and immutable Graph lookups from Graph declarations. Catalog and Registry use the same Shape Validator. Runtime binding must compare every field exactly and cannot trust the Shape fingerprint alone. The Host executes Operators in explicit serialized order. See StateGraph Asset and Compiler for the concrete Compiler schema and diagnostics.

ContextFrame is a generation-scoped, read-only handle to the complete committed logical state of one GraphRuntimeInstance at the Commit Barrier. Internal arena storage cells may be reused, but the Generation captured by a Handle cannot:

  • It contains GraphInstanceId, TimelineEpoch, Tick, Revision, and Layout compatibility identity.
  • It uses a fixed StateBlock/Slot Layout rather than exposing a root Context or Operation Section.
  • A StateBlock Owner is fixed as Graph, Operator, or Actor. Graph state, Graph auxiliary, canonical Claim, Operator Outcome, Actor Binding, and Derived rebuilders must classify and cover every Slot exactly once, while any number of readers is allowed.
  • It contains the active state, transition progress, Activation, Actor continuous values, and controllable Operator progress required to continue, or values from which they can be reconstructed uniquely.
  • It contains no Inbox, IntentFrame, raw Envelope, Source, in-flight local variables, unpublished Outbox, or Unity object reference graph.
  • Different GraphRuntimeInstances never share mutable StateBlocks.
  • The first Tick creates a CoCoContextFrameReadView from the real Layout defaults. It does not create a fictional Tick 0 or Revision 0 Frame. The first successful Commit creates Revision 1.
  • Retaining a live Frame prevents its cell from being reused. After Release, that cell can serve a new Generation, but every old Handle remains permanently invalid and never becomes readable again when the cell is reused.
  • Every successful Commit, including a no-op Tick, increments Revision.
  • Restore accepts only a committed ContextFrame or a valid projection, creates a new Revision in a new TimelineEpoch, and records the source GraphInstanceId, TimelineEpoch, Tick, and Revision.

ContextFrame is the only Actor commit record that can be carried, retained, and restored. Live Graph Path/Memory/Activation, ActorClock, and Claim caches can only mirror it or be reconstructed uniquely from it; they cannot become a second authority. ContextFrame promises logical restore for one Actor only. It does not restore the world or another Actor, nor undo Event consequences already delivered to other Actors.

The trusted Project Provider supplies the actual Layout defaults. A semantic fingerprint is the Provider’s declaration token that the value matches the Manifest semantically, not a canonical hash recomputed by the framework from defaultValue. After Runtime initialization and before publication, an initial Graph State capture is compared with those defaults without producing a Revision.

3. Operations, Outcomes, and the Commit Barrier

Section titled “3. Operations, Outcomes, and the Commit Barrier”

An Operator is the execution boundary allowed to touch Unity objects and project services. StateGraph does not call concrete Unity APIs. It delivers an OperationFrame instead. Each Operator reads its corresponding Section and produces an Outcome plus optional EventOutbox candidates.

Commit order is fixed:

Preview -> Context Prepare -> Intent Collect/Freeze -> Graph Stage + Trace
-> Graph-owned State/Value Capture -> Claim Arbitration/Claim Capture
-> Operators/Outcomes/Outbox -> Actor-owned Capture -> Derived Finalize
-> Composite Preflight -> Temporal Projection Capture
-> no-fail Commit + Temporal Publish -> complete EventOutbox Publish

ContextFrame Commit is the sole committed gameplay-logic authority boundary for the current Tick:

  • A successful Commit produces one complete new ContextFrame.
  • Every preflightable validation and capacity reservation happens before Operators execute. No free Context cell is a retryable result that advances neither Tick nor Inbox. A Prepared Token exposes only a Writer and TryFinalize.
  • Claims are calculated once before any real Operator callback. A Discrete Claim binds Enabled + ActivationId. Multiple Claims from one Operator are all-or-none and are arbitrated stably by Priority, Host list order, and OperatorId. Every Claim points to one Graph-owned Claim State Slot. Competitors for one ClaimId must point to the same Slot. An ordinary loser gets ClaimDenied without Fault, and the arbitrator still writes the canonical Slot exactly once.
  • An Outcome Writer binds the OperatorId, Transaction Token, and that Operator’s Slot allowlist, and expires immediately when the callback exits. It can write only a Manifest-declared, non-Derived, Operator-owned Slot with a unique owner. Later Operators still read only the previous committed Context.
  • Writers reject Derived Slots. Finalize rebuilds every Derived value from authoritative inputs in deterministic topology on every successful Tick, including no-op Ticks. Only a Finalized Token may Commit.
  • Graph-owned capture completes before Claims and every real Operator callback. Actor Binding captures Actor-owned Slots after Operators. Both, as well as later Operators, can read only Previous Context and cannot read this Tick’s candidate.
  • If a Derived Rebuilder returns failure or throws, the candidate is abandoned first and the old ContextFrame remains authoritative. After cancellation completes, the Host converges the throwing path into a structured Fault diagnostic.
  • With Temporal enabled, encoding projection staging from the finalized candidate remains in the fallible segment. Codec failure cancels the whole Tick. Publishing the prepared Ring entry after the authority barrier is no-fail.
  • StateGraph cannot read an in-progress Outcome in the current Tick.
  • Outbox candidates are invisible before Commit.
  • On Commit failure, Cancel, Preview, or Restore, the old ContextFrame remains authoritative.
  • Failure also cancels the staged Tick, publishes no Event, consumes no final EventSequence or OperationSequence, and causes no cross-Actor side effect.
  • The no-fail barrier permits no callback, allocation, capacity request, or fallible mutation. It atomically exchanges Context authority and commits OperationSequence, Graph Path/Memory/Activation, ActorClock, Claims, a contiguous EventSequence range, and the Intent Tick.
  • If failure occurs after a real Operator callback, the old Context remains authoritative while the Host faults and sets RequiresWorldCorrection. CoCoFlow does not fabricate a Unity-world rollback.

EventOutbox uses typed preallocated lanes and a global order ledger. Finalize only preflights capacity, metadata, and Sequence overflow; it consumes no Sequence. After Commit, packets publish in Host Operator order and then each Operator’s append order. Every EventType in the same GraphInstance/Epoch shares one contiguous Sequence. Subscriber exceptions remain isolated. An infrastructure exception records Fault while publication continues for the remaining committed packets. Sequences are not reclaimed, events are not retried automatically, and already delivered events are not rolled back.

Trace first records Transition candidates whose Source/Window is valid and whose Conditions all pass, in compiled order, then records the Winner separately. A Frame Reference stores only identity, exact Layout metadata, Revision, and HasCommittedFrame; it does not retain a Context Handle. The first Tick’s Previous reference points to exact Layout defaults with HasCommittedFrame=false, never a fictional Revision 0. A failed transaction ends with Cancelled and cannot contain Commit, Sequence, or Published entries.

Runtime Debugger Snapshot and Trace are different data surfaces. Snapshot is an internal immutable point copy of the latest committed authority boundary for reading Host/Lifecycle/Fault, Context Revision, Tick/Clock/Epoch, each Layer’s Active Path, and committed Transitions. It exposes no candidate, payload, Inbox, Envelope, Context Handle, or private reflected field. Trace is an optional identity-only event history. Its Capacity defaults to 0 and cannot change while Running. A failed or cancelled Tick cannot become a committed Snapshot or fabricate Trace Commit, Sequence, or Published records.

CoCoFlow 0.4 gameplay messages use one atomic value:

CoCoEventPacket<TEvent> = CoCoActorEventEnvelope + immutable typed payload

The Envelope expresses at least:

  • EventTypeId
  • EventDomainId
  • SourceGraphInstanceId
  • nullable TargetGraphInstanceId; null is used only for DeclaredBroadcast
  • SourceTimelineEpoch
  • SourceTick
  • SourceEventSequence
  • DeliveryMode: Targeted or DeclaredBroadcast
  • Reliability
  • optional StableEntityId, ActivationId, and CorrelationId

The local gameplay hot path does not use string payloads. The current 0.3.9 payloadTypeId/payload belongs only at network, logging, or Codec boundaries. The legacy PublishWithEnvelope sends Payload and Envelope separately and cannot serve as the 0.4 Actor routing protocol.

Identity and routing rules:

  • Every Event declaration in one Graph must belong to one EventDomain. A Host with no Event declaration creates neither EventInbox nor Router.
  • Each GraphRuntimeInstance with Event declarations has exactly one EventInbox.
  • Each EventDomain lazily creates one internal EventRouter.
  • EventDomain and ClockDomain are separate.
  • An Actor-local event travels directly from the Host Gateway into its own Inbox without passing through the Router.
  • A Targeted message is routed in O(1) by the current TargetGraphInstanceId.
  • StableEntityId is used only for persistence, cross-load identity, networking, and diagnostics. It must resolve to the current GraphInstanceId before entering the local Router.
  • DeclaredBroadcast delivers only to Actors in the same EventDomain that explicitly declare the corresponding Adapter.
  • Broadcast does not echo to the source Actor by default.
  • Undeclared broadcast, wrong target, unknown Domain, old Epoch, and duplicates are rejected with structured diagnostics.
  • The same SourceGraphInstanceId, SourceTimelineEpoch, and SourceEventSequence cannot be reused for another EventTypeId. Reusing one Sequence across types is a protocol error.

The Compiler builds Graph-level static declarations of EventTypeId + ProvidedIntentId and stores Event Domain, Payload Type, and Provided Intent Type in the Intent Requirement Manifest. These declarations only prove static type, Intent shape, and the MaxContributions capacity lower bound. They contain no Adapter instance, priority, projection capacity, broadcast, Inbox, or reliability policy. Before Start, the Host checks the actual Adapters for exact missing/extra/duplicate/type coverage. Any mismatch leaves the Host in Created with zero Router registrations, callbacks, or Ticks. When one EventType projects to several Intents, the Host creates one typed Inbox lane and runs several Adapters according to the declarations.

The deduplication key is:

SourceGraphInstanceId
+ SourceTimelineEpoch
+ SourceEventSequence
+ EventTypeId

Sequence is monotonic within one (SourceGraphInstanceId, SourceTimelineEpoch). Intent candidates are ordered deterministically by higher Priority, lower registration sequence, SourceGraphInstanceId, SourceTimelineEpoch, and SourceEventSequence. A Reducer cannot treat Router arrival order as a gameplay rule.

Inbox uses preallocated double buffering with a Capacity fixed before Running:

Incoming buffer
-- seal at Step start --> current Tick sealed batch
messages arriving after seal
-> Incoming buffer for the next accepted Tick

A Router callback may only validate, route, deduplicate, and enqueue. It cannot call StateGraph or Operators, Commit, or modify ContextFrame. An Event-to-Intent Adapter reads only the sealed batch and produces typed Intent candidates. StateGraph never sees the raw Envelope and has no ACK, Dequeue, or Consume lane.

One message enters at most one IntentFrame. A request that must persist is processed by StateGraph/Operators and committed into ContextFrame Pending State. Inbox itself is not fact storage.

Capacity and lifecycle rules:

  • Capacity, Reliability Policy, Broadcast Manifest, and Adapter set are fixed before Running and cannot be resized or hot-swapped while running.
  • Before Running, Inbox must bind a live Intent Runtime whose Bindings are frozen. Inbox typed lanes must exactly match the Runtime’s deduplicated Adapter Manifest in both directions by EventDomain, EventType, and Payload Type. Each lane’s Capacity cannot exceed its Adapter’s minimum Projection Capacity. Runtime must also be idle during binding, Start, Tick Seal, Suspend, and Resume. Messages arriving during Collection cannot enter the current IntentFrame through another Seal. When Start fails, Inbox remains Created.
  • Ordinary Suspend retains Router registration and continues bounded accumulation. The next Tick after Resume delivers it.
  • Begin Temporal Preview immediately clears the queue, sealed batch, and dedup window. Later gameplay Events are dropped and counted, not retained until Resume.
  • Preview Cancel keeps the original TimelineEpoch but does not resurrect the backlog cleared by Begin.
  • After Confirm successfully switches to a new TimelineEpoch, all old Inbox batches, packets, and dedup windows become invalid. Only new input belonging to the new Epoch is accepted.
  • Ordinary Suspend/Resume is not Rewind. It keeps the current TimelineEpoch and legal bounded backlog.
  • Reliable overflow latches Host Fault at a safe boundary. The Fault gate rejects new gameplay input and ordinary Resume.
  • Unreliable overflow rejects the newest message and increments a diagnostic counter.
  • Stop/Dispose unregisters routing and clears Inbox and its dedup window.
  • When the bound Intent Runtime is disposed, a Running Inbox stops and clears. A Created Inbox only unbinds so a replacement Runtime can bind. Later Enqueue/Seal/lifecycle entry points must reject the invalid binding.
  • Inbox Stop/Dispose requested inside a callback is deferred until the callback exits. It first cancels the current Collection and rolls back the Projection Claim. An invalid sealed batch cannot continue contributing.
  • Droppable presentation events such as audio, VFX, and logging continue using the ordinary EventBus and do not enter the gameplay Inbox.

The Host registers with the Router last, after all startup checks complete, and unregisters first during Stop/Dispose. When the final Host leaves a Domain, the Router releases its internal CoCoEventAgent subscription. A Router callback accepts only atomic CoCoEventPacket<TEvent> values, then validates and enqueues them. It does not call Runtime, Operators, or Context and does not use the legacy PublishWithEnvelope. The Host publishes EventOutbox through an internal outbound seam only after composite Commit succeeds. Destroy/Stop/ Dispose requested during publication completes only after the entire committed list publishes.

ContextFrame is complete in-memory state. Descriptors use two orthogonal sets of metadata rather than three parallel fact surfaces:

  • Projection Flags independently contain Temporal and Durable. The former enters Actor time history and the latter enters the StateGraphHost persistence payload. One Slot may have both.
  • Restore Policy independently selects Stored, ResetToDefault, or Derived.
  • Derived must declare dependencies and is reconstructed deterministically from restored Slots during every Commit Finalize and Restore Finalize. A Writer cannot write it directly, and it is not stored separately as an authoritative value.
  • When a Projection includes a Derived Slot, it must also include all transitive Stored/Derived dependencies. ResetToDefault dependencies are exempt because they can be restored deterministically. Layout Freeze performs the primary closure validation, and Codec creation validates it defensively again.
  • Missing dependencies or incompatible Layout/Codec configuration makes Restore fail deterministically with diagnostics.

The Temporal Ring does not store or retain a complete ContextFrame. Each Host exclusively owns a preallocated Ring with a fixed entry Capacity:

  • It encodes only exact-layout payloads for Temporal + Stored Slots.
  • Temporal + ResetToDefault stores no value and uses the Layout default on Restore.
  • Temporal + Derived stores no result and rebuilds it from the complete closed dependency set on Restore.
  • Stored Slots not marked Temporal also use Layout defaults rather than mixing in current values from before Rewind.
  • Each Entry separately stores immutable GraphInstance, TickFrame, Revision, and Origin metadata.
  • Capacity includes current authority. Count becomes 1 after the first successful Commit. Zero disables History. Enabled History requires at least 2 entries, and Capacity 1 is rejected at startup. A full Ring overwrites the oldest entry. It cannot resize or hot-swap while Running.

Capacity 0 does not require a Restore Binding. Assignments of the wrong type, destroyed components, or components outside the Host boundary are ignored. A valid Binding inside this Host may be retained solely for general World Correction after a dirty non-Temporal failure.

Capture reads the Finalized Context candidate but completes before the authority swap. Codec/capture failure fails the whole Tick, leaving old Context/Graph/ Clock/Claim/History unchanged with no Outbox and no final Sequence. After authority exchanges successfully, Ring publish/overwrite must be no-fail. The ordinary ContextFrame Retain/Release contract remains valid; the Temporal Ring simply does not use it.

Preview moves only a non-authoritative cursor and invokes the Host’s single synchronous ICoCoContextRestoreBinding. It does not use negative Delta or run State Enter/Exit, Condition, Transition, Operators, Actor capture, Events, or Trace. Cancel reprojects current authority through the same Binding only after the session has completed at least one successful Preview projection. Cancelling immediately after Begin does not invoke the Binding. Neither path exchanges logical authority or switches Epoch.

Confirm fully validates and prepares Context, Graph Path/Memory, Clock, and Claims outside the barrier, then invokes the Binding exactly once. After Unity projection succeeds, a no-fail barrier atomically exchanges logical authority, discards the future after the selected point, and records the new-Epoch Restore Commit as the new branch head. Restore keeps the source TimelineId and ClockDomainId, strictly advances ExecutionSequence, and uses a TimelineEpoch strictly newer than both source and current Epoch. Normal computation resumes on the next accepted positive-delta Tick.

When no earlier Preview projection exists and the callback has not started, a Binding preflight failure only rejects the request and the Host remains healthy. Once a callback has started, or while the session still has a successful Preview projection, a Binding refusal, exception, destruction, or possible partial Unity mutation leaves old logical authority valid while the Host faults with RequiresWorldCorrection=true. Correction reprojects Unity from the last logical authority through the same Binding and clears the matching recoverable Fault only after success. A Temporal payload is an internal same-session, exact-layout representation, not a stable wire identity or cross-session save format. Persistence uses a separate Durable Codec and schema-v2 Save Document and owns StableEntityId, migration, Containers, and file storage.

  • One Unity Update or FixedUpdate triggers at most one CoCoTick. Whether it triggers and its Delta are decided by the Host’s internal Clock/Driver. Every Manual call is an independent Tick with no accumulator or catch-up.
  • CoCoTickFrame accepts only a finite positive Delta.
  • Actor TimeScale must also be finite and greater than zero. Pause/Suspend means zero Ticks, not a Frame with zero Delta.
  • Reverse playback does not use negative Delta. Preview projects history only, and Confirm performs one formal Restore in a new Epoch.
  • The internal Debug Step for a healthy Suspended Host accepts one explicit finite positive Delta and executes exactly one ordinary forward Tick under Update, FixedUpdate, or Manual driving. It may Commit, append enabled Trace/ Temporal history, and publish committed Events, then returns to Suspended on success. It is neither Rewind nor an authority-neutral Preview.
  • Unity callbacks and Manual Drivers can only serve as Host/Driver input.
  • Animator/SMB callbacks cannot immediately call StateGraph or modify the current Frame. They can enter presentation flow or cross the Event/Intent boundary for a later Tick.

Host startup first completes Compile, Provider Configure/Freeze, and Transaction Preflight. It creates the Clock and Runtime, runs Start and initial Graph/default validation, publishes Host fields, and registers the Router only after all of the following are valid: Graph producers, explicitly ordered Intent Sources, Event-to-Intent Adapters, Operators, required Actor Context Binding, Restore Binding when Temporal History is enabled, Operator/Outcome/Claim setup, Temporal Capacity, and Outbox Capacity. Configuration errors therefore invoke no Logic/Condition/Memory factory, Reset, Fingerprint, Graph capture, Operator, or Actor callback. The Host remains Created.

CoCoStateGraphHost remains the framework’s only public MonoBehaviour. The Asset is the only required field; Driver, AutoStart, TimeScale, Temporal History, and diagnostic Capacity are settings on the same Host. These Host references only select the concrete scene instances and order for this Actor. The Project Provider remains the type authority for the frozen Catalog, State/Condition factories, generic Intent/Adapter binding, Operation/Context types, Codecs, defaults, and AOT-safe construction. The Editor may suggest values but saves only after user confirmation. Running configuration is read-only. The Host does not discover references by scanning the scene. Runtime, Clock, Inbox, Router, Logic, Condition, Memory, and Temporal Ring are ordinary internal objects. Playable Animation, controllable playback progress, and Root Motion belong to Animation or the project’s presentation layer.

StateLogic and Layer assembly/API surfaces must not reference:

  • CoCoEventBus
  • CoCoEventAgent
  • EventEnvelope/EventPacket
  • EventRouter
  • EventInbox/EventOutbox
  • Unity Object, Animator, or Playable types

Editor Analyze and Player build preflight traverse the complete resolved assembly dependency closure from every author-type root recorded in the Catalog. Every reachable custom assembly must have an asmdef with noEngineReferences:true. The gate fails closed when it reaches Unity, Editor, legacy Core, StateGraphAuthoring, Modules, or a custom precompiled dependency that cannot be proven safe. Runtime’s direct-reference guard is only a fast first line and does not replace closure validation. The pure Compiler does not scan assemblies.

These types read only the current IntentFrame and Previous ContextFrame and produce only internal StateGraph decisions and OperationFrame data. Fixed-Layout reads, Intent arbitration, Mailbox projection, and the Commit protocol must not depend on reflection, string lookup, or steady-state allocation in the hot path.

  • Contracts, StateFlow, StateGraph, StateGraphAuthoring Runtime, and StateGraphHost form the mature Core Engine for this release.
  • StateGraph Editor is current usable tooling but is outside the Runtime API maturity guarantee.
  • Persistence integrates with the Host through Durable Context projection and separately owns schema v2, StableEntityId, Containers, and file lifecycle.
  • Animation, Input, Locomotion, Content, Map, Pooling, and other modules may integrate only through declared Intent, Operation, Context, Event, or restore-binding boundaries. They cannot bypass Commit authority.
  • Core maturity does not imply cross-Actor atomic transactions, whole-world rollback, Jobs/Burst scheduling, zero allocation across every Unity callback, complete Editor tooling, or marketplace-grade certification.

The EventBus, Services, old mutable Context, and related facilities retained under Runtime/Core/*.cs are outside this Core Engine maturity declaration. Existing modules such as Camera may still use them as compatibility implementation details, but that does not make them part of the 0.4 StateFlow/ StateGraph contract. CoCoFlow 0.4 provides no automatic migration layer from the old Runtime to the new StateGraph.