270 lines
12 KiB
Markdown
270 lines
12 KiB
Markdown
# Modern runtime J3.4 — canonical object-table ownership
|
|
|
|
**Status:** COMPLETE
|
|
**Parent:** `2026-07-25-modern-runtime-slice-j3.md`
|
|
**Required production base:** `e937cc36df39cf6ea1eaba2f9c0243d1929da702`
|
|
**Prior closeout:** `docs/research/2026-07-25-slice-j3-3-exact-projection-store.md`
|
|
**Production commit:** `5ef8b5371d8990f0380acd939e71cd711289d429`
|
|
**Closeout:** `docs/research/2026-07-26-slice-j3-4-canonical-object-lifetime.md`
|
|
|
|
## Objective
|
|
|
|
Make one presentation-independent Runtime lifetime-group root own both:
|
|
|
|
- the canonical `RuntimeEntityDirectory`; and
|
|
- the existing canonical `ClientObjectTable`.
|
|
|
|
App, retained UI, interaction controllers, and the graphical projection host
|
|
must borrow those exact same instances. There must be no copied inventory
|
|
model, second object table, asynchronous delivery seam, extra input frame, or
|
|
change to retail event order.
|
|
|
|
This slice moves ownership and transaction boundaries, not the
|
|
`ClientObjectTable` implementation out of `AcDream.Core`. The type is already
|
|
presentation-free and has extensive Core conformance coverage.
|
|
|
|
## Current ownership inventory
|
|
|
|
At the required base:
|
|
|
|
- `LiveEntityRuntime` constructs its own `RuntimeEntityDirectory`.
|
|
- `GameWindow` constructs a public readonly `ClientObjectTable Objects`.
|
|
- App composition records pass that table to retained UI, selection,
|
|
interaction, magic, combat, paperdoll, appraisal, cooldown, and diagnostics.
|
|
- Runtime `LiveSessionEventRouter` already applies property, stack, inventory,
|
|
player-description, container, and appraisal traffic directly to the
|
|
borrowed table through `ObjectTableWiring`.
|
|
- App `LiveEntityHydrationController` applies CreateObject to the table only
|
|
after canonical Runtime create acceptance and revalidates
|
|
`CreateIntegrationVersion`.
|
|
- App `LiveEntityDeletionController` applies logical DeleteObject before the
|
|
exact App projection teardown suffix.
|
|
- App `LiveSessionResetManifest` clears the object table before clearing live
|
|
graphical projections.
|
|
- Optimistic moves, confirmations, rollback, stack updates, container
|
|
replacement, and every table event are synchronous on the current thread.
|
|
|
|
Retail behavior and citations already live in:
|
|
|
|
- `src/AcDream.Core/Items/ClientObjectTable.cs`;
|
|
- `src/AcDream.Core.Net/ObjectTableWiring.cs`;
|
|
- `docs/research/2026-07-13-retail-give-item-pseudocode.md`;
|
|
- the named-retail symbols cited beside `ACCObjectMaint::CreateObject`,
|
|
`ACCObjectMaint::DeleteObject`, and
|
|
`ACCWeenieObject::ServerSaysMoveItem`.
|
|
|
|
J3.4 does not reinterpret those algorithms.
|
|
|
|
## Fixed target
|
|
|
|
Add a presentation-free Runtime owner equivalent to:
|
|
|
|
```csharp
|
|
public sealed class RuntimeEntityObjectLifetime
|
|
{
|
|
public RuntimeEntityDirectory Entities { get; }
|
|
public ClientObjectTable Objects { get; }
|
|
}
|
|
```
|
|
|
|
The concrete name may change, but these invariants do not:
|
|
|
|
1. The owner constructs each canonical instance exactly once.
|
|
2. `LiveEntityRuntime` receives `Entities`; it may not construct a directory.
|
|
3. Runtime session routing receives `Objects` from the same owner.
|
|
4. App projection, UI, and interaction composition receive borrowed references
|
|
only.
|
|
5. Accepted CreateObject/DeleteObject object-table mutation is implemented by
|
|
the Runtime group and revalidates the exact canonical record/version.
|
|
6. Reset preserves the current order: object-table clear before graphical live
|
|
projection teardown, with completed stages never replayed.
|
|
7. Studio/sample-data object tables remain independent fixture owners and are
|
|
not part of the live session.
|
|
|
|
## Execution
|
|
|
|
### 1. Pin parity traces and construction identity
|
|
|
|
- Capture normalized traces for:
|
|
- initial player description and equipment manifest;
|
|
- item CreateObject add/update;
|
|
- same-incarnation refresh;
|
|
- new-generation replacement;
|
|
- property Int/Int64/String/Float/DataID updates;
|
|
- stack-size update;
|
|
- optimistic move, authoritative confirm, and rejection rollback;
|
|
- wield/unwield;
|
|
- ViewContents replacement and close;
|
|
- logical delete, GUID reuse, and session clear.
|
|
- Add a construction-identity test proving the router, App projection host,
|
|
retained UI source, and interaction source all receive one table instance.
|
|
- Preserve current synchronous event order in the golden trace.
|
|
|
|
### 2. Introduce the Runtime lifetime-group root
|
|
|
|
- Add the owner under `src/AcDream.Runtime/Entities/`.
|
|
- Construct `RuntimeEntityDirectory` and `ClientObjectTable` there.
|
|
- Expose borrowed properties only; no collection mirror or event repeater.
|
|
- Add Runtime-only tests that instantiate it without loading App, UI, Silk,
|
|
OpenAL, Arch, or ImGui assemblies.
|
|
- Keep mutable state instance-scoped so two roots can hold conflicting GUIDs
|
|
and inventory contents without interference.
|
|
|
|
### 3. Inject canonical identity into the App projection host
|
|
|
|
- Change `LiveEntityRuntime` to require the exact
|
|
`RuntimeEntityDirectory` instance from the group.
|
|
- Remove its internal `new RuntimeEntityDirectory(...)`.
|
|
- Preserve the existing local-ID start override for focused wraparound tests
|
|
through an explicit fixture/group construction seam, not a second production
|
|
allocator.
|
|
- Assert that App cannot replace the directory after projection construction.
|
|
- Preserve J3.3 `RuntimeEntityKey` identity and teardown behavior unchanged.
|
|
|
|
### 4. Replace the `GameWindow` object-table owner
|
|
|
|
- Construct the Runtime group before retained UI and live presentation
|
|
composition.
|
|
- Delete `GameWindow`'s public readonly `ClientObjectTable Objects` field.
|
|
- Pass `runtimeGroup.Objects` through the existing typed composition records.
|
|
- Keep App controllers as direct synchronous borrowers; do not wrap every read
|
|
in a copied DTO or event bus.
|
|
- Add source guards rejecting a production App `new ClientObjectTable()` and a
|
|
second Runtime entity directory. Exempt Studio/sample-data and tests.
|
|
|
|
### 5. Move accepted object mutation to the Runtime group
|
|
|
|
- Move the generation/version-sensitive
|
|
`ObjectTableWiring.ApplyEntitySpawn/Delete` transaction boundary out of App.
|
|
- Runtime must apply a CreateObject only after the canonical directory accepts
|
|
that exact incarnation and while its create-integration version remains
|
|
current.
|
|
- New-generation replacement must remove the old object generation and publish
|
|
the same add/update/removal sequence as today.
|
|
- Logical delete must mutate the table before App graphical teardown, exactly
|
|
as the current `beforeTeardown` callback does.
|
|
- Pickup/parent leave-world must not delete the object-table entry.
|
|
- Dormant liveness pruning must retain the existing object-table semantics;
|
|
only an authoritative logical delete removes the retained object generation.
|
|
- Keep all callbacks synchronous and revalidate after every callback that can
|
|
replace, delete, or clear the incarnation.
|
|
|
|
### 6. Route session and reset ownership
|
|
|
|
- Bind J2 `LiveSessionEventRouter` directly from the Runtime group rather than
|
|
an App-owned table field.
|
|
- Keep the current pre-Connect subscription order.
|
|
- Keep local-player property projection to `LocalPlayerState` and the same
|
|
table instance until J4 moves the coherent player/gameplay state group.
|
|
- Replace the App reset field with a Runtime-group object-table reset
|
|
acknowledgement at the same manifest position.
|
|
- Failure injection must retain the unfinished reset suffix and never clear a
|
|
replacement session's table.
|
|
|
|
### 7. Delete superseded ownership seams
|
|
|
|
- Remove public ownership comments and aliases that describe the table as a
|
|
GameWindow state owner.
|
|
- Remove any App construction helper whose only purpose was to allocate the
|
|
live table.
|
|
- Keep direct table parameters on UI/controllers for now: they are explicit
|
|
borrowed dependencies and J4 will move higher-level inventory transactions.
|
|
- Update architecture, plans, durable memory, and exact rollback in the same
|
|
closeout commit.
|
|
|
|
## Adversarial cases
|
|
|
|
- Reentrant `ObjectAdded` deletes or replaces the same GUID.
|
|
- Reentrant `ObjectMoved` starts another optimistic move.
|
|
- Delete arrives during CreateObject callbacks.
|
|
- Same GUID with a new `INSTANCE_TS` while old projection teardown is pending.
|
|
- Unknown-object authoritative move publishes the same nullable item event.
|
|
- Equal, stale, and wrapped object/physics generations.
|
|
- Multiple in-flight optimistic moves followed by confirm/reject in either
|
|
order.
|
|
- Container replacement while item events mutate the same container.
|
|
- Reset during object-table event dispatch.
|
|
- Failed graphical teardown after canonical object removal.
|
|
- Two Runtime roots use identical GUIDs with different contents.
|
|
- No-window root executes the full entity/object lifecycle without loading App
|
|
or backend assemblies.
|
|
|
|
## Mandatory gates
|
|
|
|
Run in this order:
|
|
|
|
1. Runtime entity/object lifetime tests.
|
|
2. Core `ClientObjectTable*` and `ObjectTableWiring*` tests.
|
|
3. Focused App hydration, deletion, reset, inventory, interaction, paperdoll,
|
|
magic, cooldown, and runtime-adapter tests.
|
|
4. All Runtime tests.
|
|
5. All App tests.
|
|
6. `dotnet build AcDream.slnx -c Release`.
|
|
7. `dotnet test AcDream.slnx -c Release --no-build`.
|
|
8. Exact-binary connected lifecycle/reconnect gate.
|
|
9. At every connected checkpoint: zero render-shadow mismatches, zero pending
|
|
deltas, graceful process exit, and object/projection teardown convergence.
|
|
|
|
A user visual pause is not required unless an automated or connected gate
|
|
exposes an inventory, paperdoll, selection, or interaction symptom.
|
|
|
|
## Acceptance
|
|
|
|
- One Runtime lifetime-group root owns the exact entity directory and object
|
|
table.
|
|
- `GameWindow` owns neither instance directly.
|
|
- App and Runtime routing borrow the same object table.
|
|
- Runtime remains the only GUID/incarnation/local-ID authority.
|
|
- Create/delete/property/container ordering matches the pinned trace.
|
|
- Optimistic move/rollback and press-time UI remain synchronous.
|
|
- No App/UI/backend dependency enters Runtime.
|
|
- No mirror, queue, frame delay, or steady-state traversal allocation is added.
|
|
- Runtime-only entity/object lifecycle and multiple-instance isolation pass.
|
|
- Complete Release and exact connected gates pass on one committed binary.
|
|
|
|
## Rollback
|
|
|
|
The exact J3.4 rollback is:
|
|
|
|
```text
|
|
git revert 5ef8b5371d8990f0380acd939e71cd711289d429
|
|
```
|
|
|
|
J3.3 remains independently reversible; do not use its rollback for a J3.4
|
|
failure.
|
|
|
|
## Result
|
|
|
|
`RuntimeEntityObjectLifetime` now constructs and owns the one
|
|
`RuntimeEntityDirectory` and one live `ClientObjectTable`. `GameWindow`,
|
|
session routing, retained UI, interaction, and the graphical projection host
|
|
borrow those exact instances.
|
|
|
|
Accepted CreateObject mutation revalidates the exact canonical record and
|
|
`CreateIntegrationVersion` before, during, and after synchronous object-table
|
|
callbacks. Authoritative DeleteObject acceptance uses an exact, single-use
|
|
Runtime token: the active identity is retired first and retained-object
|
|
removal then commits before App resource teardown. Dormant/offscreen pruning
|
|
does not remove retained object information.
|
|
|
|
Final gates:
|
|
|
|
- Runtime: 118 passed;
|
|
- App: 3,762 passed / 3 skipped;
|
|
- complete Release solution: 8,453 passed / 5 skipped;
|
|
- exact-binary connected lifecycle/reconnect:
|
|
`logs/connected-world-gate-20260726-055713/report.json`;
|
|
- two sessions, seven checkpoints, graceful zero-code exits, zero failures,
|
|
zero render-shadow mismatches, zero pending deltas, and zero pending live
|
|
teardown/publication/retirement work.
|
|
|
|
## Final J3 goal
|
|
|
|
J3.4 is not the end of J3. J3.5 must publish one ordered per-session
|
|
entity/object delta stream and make graphical/direct hosts consume the same
|
|
committed facts without a second queue. J3.6 then failure-injects and proves
|
|
the complete no-window entity/object lifecycle, zero leaked records,
|
|
tombstones, projections, container entries, queued deltas, or subscriptions,
|
|
plus Release and connected parity. That J3.6 proof is the clean boundary before
|
|
J4 moves inventory transactions, vitals, enchantments, magic, and other
|
|
gameplay-state services.
|