# Modern runtime J3.5 — committed entity/object deltas and borrowed views **Status:** COMPLETE — `ce3ac310d92722ffb637e81cb1957458874dd220` **Parent:** `2026-07-25-modern-runtime-slice-j3.md` **Required production base:** `5ef8b5371d8990f0380acd939e71cd711289d429` **Prior closeout:** `docs/research/2026-07-26-slice-j3-4-canonical-object-lifetime.md` ## Objective Publish one synchronous, monotonic, generation-stamped stream from the canonical Runtime entity/object lifetime and make both graphical and no-window/direct hosts observe those same committed facts. `IGameRuntimeView.Entities` and `IGameRuntimeView.Inventory` must read the Runtime-owned `RuntimeEntityDirectory` and `ClientObjectTable` directly. `CurrentGameRuntimeEventAdapter` must stop reconstructing entity changes from App projection visibility and inventory changes from an App-owned event seam. This slice adds no later-frame queue, copied world, inventory mirror, input delay, renderer dependency, or gameplay behavior change. ## Current seam at the production base - `RuntimeEntityObjectLifetime` owns the exact directory and object table. - `CurrentGameRuntimeViewAdapter.EntityView` still reads App `LiveEntityRuntime`, so a direct host cannot enumerate canonical entities without constructing the graphical projection host. - Its inventory view reads the right table but asks `LiveEntityRuntime` to recover incarnation metadata. - `CurrentGameRuntimeEventAdapter` owns a separate sequencer and reconstructs: - entity events only from `ProjectionVisibilityChanged`; and - inventory events by subscribing directly to object-table callbacks. - Registered, refreshed, parented, picked-up, state-updated, cellless, and authoritative-delete canonical edges therefore do not have one complete Runtime stream. - Runtime local IDs are still claimed at App materialization rather than at canonical registration, even though direct and graphical hosts require one stable cross-host identity. The J1 adapter was intentionally temporary. J3.5 deletes these superseded entity/object adapter responsibilities. ## Fixed contracts ### One canonical stream owner Add one instance-scoped stream under `AcDream.Runtime.Entities`, owned by `RuntimeEntityObjectLifetime`. It exposes a narrow subscription equivalent to: ```csharp public interface IRuntimeEntityObjectObserver { void OnEntity(in RuntimeEntityDelta delta); void OnInventory(in RuntimeInventoryDelta delta); } public interface IRuntimeEntityObjectEventSource { IDisposable Subscribe(IRuntimeEntityObjectObserver observer); } ``` Entity and inventory deltas share one sequence owner. There is no entity queue plus inventory queue and no App repeater. Every stamp carries the exact `RuntimeGenerationToken` and current Runtime frame number. Sequence rules: - sequence starts at one for each live session generation; - entity and inventory commits consume the same monotonic sequence; - rejected/stale packets consume no sequence; - callback reentrancy appends later sequence values in actual commit order; - session reset publishes its committed withdrawals/removals/clear edge, then the retired generation is sealed; - a replacement generation cannot publish through the retired stream context. ### Canonical entity identity and snapshot Runtime claims a local ID when it creates the canonical active record, before any graphical projection callback. The ID remains stable for that incarnation until canonical teardown; graphical hydration failure must not release a still-active Runtime identity. The public entity view is canonical and presentation-free: - server GUID; - Runtime local ID; - exact `INSTANCE_TS` incarnation; - full cell; - final physics state; - accepted position/snapshot facts. Graphical hydration, loaded/pending visibility, and render-resource state do not become gameplay truth. If retained diagnostic contracts still need attached-host counts, those are supplied by a separate projection acknowledgement/diagnostic seam and are not mixed into canonical entity identity. ### Commit ordering An entity delta is created only after the exact Runtime record mutation commits. The graphical projection host receives that same immutable committed delta synchronously and returns an exact acknowledgement; public observers then observe the same sequence. There is no second conversion step and no frame boundary. Required entity edges: - `Registered` after active-record/local-ID commit; - `Updated` after same-incarnation CreateObject, ObjDesc, vector, movement, accepted position, parent, or equivalent canonical refresh; - `Rebucketed` after a canonical full-cell change; - `Hidden` after a committed Hidden state transition; - `Withdrawn` after pickup/accepted parent/cellless exit-world while the incarnation remains logically alive; - `Deleted` after authoritative identity retirement and retained-object removal acceptance; - generation replacement orders old `Deleted`/withdrawal before new `Registered`. Loaded-to-pending renderer visibility is not a canonical withdrawal and emits no gameplay entity delta. Required inventory edges remain the exact object-table callback order: - `Added`; - `Updated`; - `Moved`; - `Removed` with exact removed generation; - `Cleared`. Unknown-object move notices retain the object ID and placement facts rather than disappearing because no `ClientObject` instance exists. ### Borrowed views The Runtime lifetime group exposes concrete allocation-free `IRuntimeEntityView` and `IRuntimeInventoryView` implementations: - entity enumeration walks `RuntimeEntityDirectory.ActiveRecords`; - entity lookup resolves directly through the directory; - inventory enumeration/lookup walks `ClientObjectTable`; - inventory incarnation comes from the exact Runtime directory or the object's retained generation, never an App sidecar; - visitors receive value snapshots and may not retain mutable owners. `CurrentGameRuntimeViewAdapter` borrows those view objects. It retains only the still-unmoved J4+ chat/movement/portal/lifecycle composition. ## Execution ### 1. Pin the pre-cutover normalized oracle Add a focused deterministic fixture covering: - create, same-incarnation refresh, and generation replacement; - ObjDesc/property and accepted state/vector/movement/position updates; - parent, pickup, cellless withdrawal, and re-entry; - object add/update/move/remove/clear; - optimistic move, confirm, rejection rollback, and container replacement; - authoritative delete and delete/recreate with the same GUID; - reset and next-generation reuse; - callback reentrancy. Record both the canonical final state and exact event order. Preserve existing retail-facing object-table callback order; add missing canonical entity edges without treating projection visibility as gameplay truth. ### 2. Add the Runtime entity/object stream - Add the stream owner and narrow observer/source contracts in Runtime. - Give it one per-generation sequencer and explicit session-generation/frame context. - Use copy-on-write subscriber storage so steady dispatch allocates no observer list and unsubscribe is exact. - Reject duplicate subscriptions, cross-owner subscriptions, stale generations, and dispatch after disposal. - Keep callback execution on the existing update/network-drain thread. - Revalidate generation/incarnation/operation version after any callback that can re-enter Runtime. ### 3. Move identity allocation to canonical registration - Claim the Runtime local ID in the same transaction that installs an active canonical record. - Preserve current allocation range and wrap/exhaustion behavior. - Stop releasing the ID when only App materialization rolls back. - Release it only when the canonical incarnation reaches terminal teardown. - Prove pending/unhydrated and direct-host entities still have exact keys. - Prove GUID reuse gets a new key and stale keys never resolve. ### 4. Publish canonical entity commits - Centralize accepted canonical mutations behind `RuntimeEntityObjectLifetime` transaction methods. - Publish only after state commits and only while the exact operation remains current. - Carry the complete immutable accepted snapshot required by a graphical or direct projection host. - Replace App's projection-visibility-derived entity event reconstruction. - Preserve synchronous graphical projection application and exact retryable teardown acknowledgements. - Do not emit on renderer rebucket, loaded/pending visibility, hydration retries, or other presentation-only changes. ### 5. Publish exact object-table commits - Bind the stream to its owned `ClientObjectTable`. - Convert callbacks to immutable `RuntimeInventoryItemSnapshot` values with generation metadata resolved by Runtime. - Preserve add/update/move/remove/clear order and callback reentrancy. - Keep optimistic movement and rollback synchronous. - Ensure an authoritative delete produces the exact entity/object ordering pinned by J3.4. - Do not add a second retained inventory collection. ### 6. Replace App entity/object view and event adapters - Make `CurrentGameRuntimeViewAdapter.Entities` and `.Inventory` return the Runtime group's borrowed views. - Remove its `LiveEntityRuntime` dependency for canonical entity/inventory reads. - Make `CurrentGameRuntimeEventAdapter` forward the Runtime stream directly. - Remove its `ProjectionVisibilityChanged` and object-table subscriptions. - Retain command, lifecycle, chat, movement, and portal adapter work only until their scheduled J4–J6 owner moves. - Add source guards preventing a new App entity/object mirror, sequencer, or owner-event reconstruction. ### 7. Direct/graphical parity and teardown - Build one no-App Runtime fixture and one graphical-host fixture over the same input script. - Compare normalized generation, sequence, identity, cell/state, properties, placement, and terminal edges exactly. - Verify subscriber removal and Runtime disposal leave zero observers. - Verify reset seals the old generation and a new session begins at sequence one without stale delivery. - Verify graphical callbacks remain same-thread and press-time object reads remain immediate. ## Adversarial cases - Observer adds/removes an observer during dispatch. - Observer creates, deletes, or replaces the same GUID reentrantly. - Observer clears the session during create or object-table callbacks. - Graphical projection callback throws after canonical commit. - Same-incarnation CreateObject arrives while a prior projection teardown is retained. - Authoritative delete races a dormant exact-incarnation record. - Equal, stale, and wrapped timestamps. - Local-ID wrap and namespace exhaustion. - Graphical hydration fails before and after resource registration. - Unknown-object move and removal. - Container replacement mutates contents from inside an event callback. - Multiple Runtime groups use identical GUIDs and session-generation values. - Subscription disposal is replayed and Runtime disposal occurs during a callback. ## Mandatory gates Run in this order: 1. Runtime stream, borrowed-view, sequence, reentrancy, and identity tests. 2. Core `ClientObjectTable*` and `ObjectTableWiring*` tests. 3. Focused App Runtime-adapter, hydration, update, rebucket, deletion, reset, inventory, paperdoll, selection, interaction, and cooldown tests. 4. Direct-host versus graphical-host normalized parity fixture. 5. All Runtime tests. 6. All App tests. 7. `dotnet build AcDream.slnx -c Release`. 8. `dotnet test AcDream.slnx -c Release --no-build`. 9. Exact-binary connected lifecycle/reconnect gate. 10. At every connected checkpoint: zero render-shadow mismatches, zero pending deltas, graceful process exit, and exact canonical/projection teardown convergence. A user visual pause is required only if an automated or connected gate exposes a visible inventory, selection, radar, paperdoll, or world-object symptom. ## Acceptance - Runtime owns one per-session ordered entity/object stream. - Entity and inventory deltas share one generation/sequence order. - Runtime views read the directory and object table directly. - Every active canonical entity has a Runtime key before App projection. - App reconstructs neither entity nor inventory events. - Projection visibility is not emitted as canonical gameplay withdrawal. - Graphical and no-window normalized traces are identical. - No copied collection, queue, extra input frame, or backend dependency exists. - Reentrant callbacks cannot resurrect or mutate a superseded incarnation. - Reset/disposal leaves zero observers, queued deltas, and retained stream state. - Complete Release and exact connected gates pass on one committed binary. ## Rollback J3.5 is independently reversible: ```text git revert ce3ac310d92722ffb637e81cb1957458874dd220 ``` Do not revert J3.4 as a shortcut for a J3.5 failure. Automated and connected evidence is recorded in `../research/2026-07-26-slice-j3-5-canonical-delta-stream.md`. ## Next boundary J3.6 executes from [`2026-07-26-modern-runtime-slice-j3-hardening.md`](2026-07-26-modern-runtime-slice-j3-hardening.md). It failure-injects the complete canonical entity/object lifecycle and proves zero records, tombstones, projections, resources, container entries, parent candidates, subscriptions, or pending deltas after reset and disposal. That closeout is required before J4 moves chat, inventory transactions, vitals, enchantments, spells/components, cooldowns, and ViewModel-facing revisions.