acdream/docs/plans/2026-07-26-modern-runtime-slice-j3-delta-stream.md
Erik 84954c8b77 docs(runtime): close J3.5 and plan J3.6
Record the canonical entity/object delta cutover, full Release and exact-binary connected evidence, rollback, and the adversarial zero-owner J3.6 execution contract. Synchronize architecture, roadmap, milestones, and agent guidance.

Co-authored-by: Codex <codex@openai.com>
2026-07-26 06:54:47 +02:00

318 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 J4J6 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.