docs(runtime): close J3.4 and plan J3.5
This commit is contained in:
parent
5ef8b5371d
commit
d3e96ff912
13 changed files with 562 additions and 58 deletions
315
docs/plans/2026-07-26-modern-runtime-slice-j3-delta-stream.md
Normal file
315
docs/plans/2026-07-26-modern-runtime-slice-j3-delta-stream.md
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
# Modern runtime J3.5 — committed entity/object deltas and borrowed views
|
||||
|
||||
**Status:** READY
|
||||
**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
|
||||
|
||||
Record the exact J3.5 production commit here before its connected gate. J3.4
|
||||
remains independently reversible:
|
||||
|
||||
```text
|
||||
git revert 5ef8b5371d8990f0380acd939e71cd711289d429
|
||||
```
|
||||
|
||||
Do not revert J3.4 as a shortcut for a J3.5 failure.
|
||||
|
||||
## Next boundary
|
||||
|
||||
J3.6 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue