docs(runtime): close J3.3 and hand off J3.4

Record exact projection-store ownership, automated and connected evidence, rollback chain, and the executor-ready canonical object-table plan. Reconcile milestones, roadmap, architecture, divergence pointers, AGENTS/CLAUDE, and durable handoff state.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-25 22:29:34 +02:00
parent e937cc36df
commit d9bf4c4960
15 changed files with 757 additions and 114 deletions

View file

@ -209,7 +209,12 @@ src/
ordered deltas, clock, generation + teardown
Session/ -> J2 canonical session lifetime, ordered
inbound routing + retryable teardown
-> J3 identity, properties, and object-table extraction active
Entities/
RuntimeEntityDirectory.cs -> sole GUID/incarnation/local-ID authority
RuntimeEntityRecord.cs -> presentation-free accepted entity state
InboundPhysicsStateController.cs -> retail timestamp/snapshot authority
ParentAttachmentState.cs -> generation-exact parent relations
-> J3.1-J3.3 complete; J3.4 canonical object-table ownership next
-> may reference Core, Core.Net, Content, and Plugin.Abstractions only
-> must never reference App, UI, Silk.NET, OpenAL, Arch, or ImGui
@ -230,7 +235,8 @@ src/
RemoteTeleportHook.cs -> ordered retail teleport teardown seam
RemoteTeleportPlacement.cs -> collision-seated SetPosition transition commit
World/
LiveEntityRuntime.cs -> canonical identity/state/body/projectile/spatial ownership
LiveEntityRuntime.cs -> exact-key App projection/lifecycle host
LiveEntityProjectionStore.cs -> materialized sidecars by RuntimeEntityKey
RetailInboundEventDispatcher.cs -> update-thread packet/frame FIFO barrier
LiveEntityPresentationController.cs -> Hidden/NoDraw/effect/collision presentation
LiveEntityTeardown.cs -> failure-isolated multi-owner lifecycle drain
@ -447,26 +453,38 @@ Ownership by phase:
## GameEntity: The Unified Entity (target refactor)
`LiveEntityRuntime` is now the shipped bridge to this target. It owns one
`LiveEntityRecord` per accepted server-object incarnation, ServerGuid-to-local-id
translation, accepted snapshots/timestamp gates, the canonical `PhysicsBody`,
animation/remote-motion/projectile components, parent-event state,
effect-profile defaults, and exact logical
teardown. Live `PhysicsDesc` effect fields replace Setup defaults on that same
record; rebucketing never recreates them. `GpuWorldState`
owns spatial buckets only: register/rebucket/unregister are separate operations,
logical teardown removes the exact `WorldEntity` incarnation rather than every
projection sharing its server GUID, and per-GUID/session mutation epochs prevent
a callback from resurrecting an outer CreateObject after delete/reset. Per-GUID
epoch tombstones remain until session clear, so delete-then-create cannot repeat
an outer operation's epoch (the ABA problem). Resource registration is an atomic
boundary, superseded cleanup failures surface at the runtime boundary, and
visibility observers are failure-isolated without interrupting canonical commits.
Landblock reloads reuse the same `WorldEntity` without replaying renderer or
script creation. Its canonical materialized view remains stable across pending
landblocks, while a separate visible-only view feeds radar, picking, status, and
targeting. Raw server PhysicsState and the final state produced by retail's
ordered Lighting/NoDraw/Hidden side effects are stored separately.
The shipped bridge is now split at the Runtime/App boundary.
`AcDream.Runtime.Entities.RuntimeEntityDirectory` owns the only current
server-GUID/incarnation map, Runtime local-ID allocation/reverse lookup,
accepted snapshots and timestamp gates, parent state, session/operation
versions, and exact tombstones. `RuntimeEntityRecord` is presentation-free.
`LiveEntityRuntime` is the App projection/lifecycle host.
`RegisterLiveEntity` first creates or refreshes canonical Runtime state without
an App record. `MaterializeLiveEntity` claims the Runtime local ID and creates
one graphical `LiveEntityRecord` sidecar in `LiveEntityProjectionStore`, keyed
by exact `RuntimeEntityKey` (local ID plus `INSTANCE_TS`). The sidecar owns the
App animation, remote-motion, projectile, hydration, and effect presentation
state. `RuntimeEntityRecord` deliberately still carries the canonical
`PhysicsBody`/physics-host facts until their coherent J5 move.
Live `PhysicsDesc` effect fields replace Setup defaults on that same exact
sidecar; rebucketing never recreates it.
`GpuWorldState` owns spatial buckets only and retains the exact projection key
through loaded/pending movement, landblock retirement/reload, origin recenter,
quiescence, and visibility transition delivery. Register, rebucket, withdraw,
and unregister remain separate operations. Logical teardown removes the exact
`WorldEntity` incarnation rather than every projection sharing its server GUID.
Runtime per-GUID/session operation versions prevent a callback from
resurrecting an outer CreateObject after delete/reset, and exact tombstones
prevent ABA reuse. Resource registration is an atomic boundary, superseded
cleanup failures surface at the runtime boundary, and visibility observers are
failure-isolated without interrupting canonical commits. Landblock reloads
reuse the same `WorldEntity` without replaying renderer or script creation.
Focused exact-key materialized queries remain stable across pending landblocks;
focused visible queries feed radar, picking, status, and targeting. Raw server
PhysicsState and the final state produced by retail's ordered
Lighting/NoDraw/Hidden side effects are stored separately.
When retail's 25-second leave-visibility deadline expires, active ownership
still ends completely. The ACE adapter retains only the accepted
`EntitySpawn` in `DormantLiveEntityStore`, because ACE can keep that GUID in
@ -494,10 +512,11 @@ does not depend on Content or a database implementation.
Runtime effect delivery is owned by App-layer `EntityEffectController`. It
retains pre-materialization F754/F755 packets in one mixed FIFO per server GUID,
resolves the canonical local ID through `LiveEntityRuntime` only after
renderer/resources/profile are ready, and resolves typed/default/part-default
hooks through the live owner's current `EntityEffectProfile`. It does not own a
second GUID map. The same profile publishes Setup/static or network/live
asks Runtime for the current canonical incarnation, then resolves the exact
App projection only after renderer/resources/profile are ready. It resolves
typed/default/part-default hooks through that projection's current
`EntityEffectProfile`; it does not own a second GUID map. The same profile
publishes Setup/static or network/live
SoundTable ownership at readiness and on every same-generation PhysicsDesc
replacement, including present-zero clearing. Core `PhysicsScriptRunner` mirrors retail's one
serial FIFO per owner: duplicate plays append, owners progress independently,
@ -920,7 +939,8 @@ Current movement/collision ownership:
- **B.3** is shipped MVP history: first collision resolver foundation.
- **L.1/R6** own animation/motion parity and the complete retail object-frame
update order over canonical `LiveEntityRuntime` records.
update order over canonical Runtime records plus exact-key App projection
sidecars.
- **L.2** owns the remaining movement and collision conformance:
`docs/plans/2026-04-29-movement-collision-conformance.md`.
- **G.3 is shipped**: dungeon streaming, portal-space presentation, collision,

View file

@ -47,8 +47,9 @@ At the campaign baseline, `GameWindow` was the single object that:
- Composes the shipped `LiveSessionController`/host/router boundary; it no
longer owns a parallel session, command bus, subscription lifetime, connect,
character-entry, reconnect, or graceful-close body.
- Owns the adapters that hydrate canonical `LiveEntityRuntime` records into
animation, collision, rendering, and DAT-backed appearance resources.
- Owns the adapters that hydrate canonical Runtime entity records into
exact-key App projection sidecars and their animation, collision, rendering,
and DAT-backed appearance resources.
- Composes the shipped `WorldSelectionQuery` and
`SelectionInteractionController`; it no longer owns world-picking,
selection intent, Use/PickUp, or auto-walk deferral algorithms.
@ -298,7 +299,8 @@ What `GameWindow` loses:
- Live connect/enter/logout and event-subscription bodies → completed inside
`LiveSessionController` / `LiveSessionEventRouter`.
- Live-object hydration adapters and final animated-part presentation → focused
world/render owners over canonical `LiveEntityRuntime` records.
world/render owners over exact-key App projection sidecars backed by
canonical Runtime records.
- `WorldPicker`, target queries, and selection-driven Use/PickUp/auto-walk →
`WorldSelectionQuery` + `SelectionInteractionController`. Core
`SelectionState` remains the injected session owner.
@ -310,27 +312,33 @@ What `GameWindow` loses:
The eventual `GameEntity` aggregation (target state described in
`acdream-architecture.md` §"GameEntity: The Unified Entity") happens
**after** `LiveEntityRuntime` is the single owner of entity state.
Until then, the parallel-dicts problem is bounded inside one class
instead of spread across `GameWindow`.
**after** `RuntimeEntityDirectory` is the single owner of canonical entity
state. That boundary is now shipped. App's former parallel presentation
dictionaries are bounded by one exact-key projection store rather than spread
across `GameWindow`.
`LiveEntityRuntime` is now that single boundary. It composes
`InboundPhysicsStateController` for the nine-channel retail timestamp gates and
latest accepted immutable CreateObject snapshot, owns the canonical local ID
and optional runtime components, and separates logical registration from
spatial projection. It also retains raw PhysicsState separately from the final
state produced by retail's ordered side effects;
`LiveEntityPresentationController` projects those transitions into draw,
collision, effect, child-NoDraw, and target visibility without becoming a
second lifetime or GUID owner. `ParentAttachmentState` is runtime-owned and keys unresolved
relations by child and parent generation. `Rendering/Vfx/EntityEffectController`
owns the focused mixed F754/F755 pending FIFO, effect profiles, typed-table
resolution, and a readiness set; canonical ServerGuid-to-local-ID translation
always stays in `LiveEntityRuntime`. `EntityScriptActivator` uses the same
canonical `WorldEntity.Id` as rendering and physics; the disjoint static ID
allocators fail fast instead of wrapping into another landblock's namespace.
All other non-Parent packet
families still need the future general queue tracked by divergence AD-32.
`RuntimeEntityDirectory` owns the only active server-GUID/incarnation map,
Runtime local-ID allocation/reverse lookup, accepted immutable CreateObject
snapshot and nine-channel timestamp gates, parent relations, operation
versions, and exact teardown tombstones. `RuntimeEntityRecord` is
presentation-free. `LiveEntityRuntime` is the App projection/lifecycle host:
it creates a `LiveEntityRecord` sidecar only after Runtime local-ID claim and
stores it in `LiveEntityProjectionStore` by exact `RuntimeEntityKey`.
Hydration, animation, effects, lights, equipped children, render identity,
movement observations, liveness, teardown, and spatial worksets borrow that
same exact identity. `LiveEntityPresentationController` projects Runtime state
transitions into draw, collision, effects, child-NoDraw, and target visibility
without becoming a second canonical owner.
`Rendering/Vfx/EntityEffectController` owns the focused mixed F754/F755 pending
FIFO, effect profiles, typed-table resolution, and readiness markers. A GUID
is retained there only as pending wire-delivery metadata; current-incarnation
resolution always asks `RuntimeEntityDirectory` and then the exact projection
store. `EntityScriptActivator` uses the same Runtime-issued `WorldEntity.Id` as
rendering and physics; disjoint static ID allocators fail fast instead of
wrapping into another landblock's namespace. All other non-Parent pre-create
packet families still need the future general queue tracked by divergence
AD-32.
The per-frame object scheduler is extracted, but final animated-part
presentation is not. `LiveEntityAnimationScheduler` snapshots canonical spatial
@ -348,8 +356,8 @@ is deliberately separate: `RetailStaticAnimatingObjectScheduler` owns the
Static owners with Setup DefaultAnimation. Both schedulers are wired by
`GameWindow`, but neither owns GUID identity or logical resources.
The typed animation view fills its reusable snapshot and render-ID set from
`LiveEntityRuntime`'s concrete spatial dictionary, avoiding interface-enumerator
boxing on both update and render hot paths.
the exact-key projection workset, avoiding interface-enumerator boxing on both
update and render hot paths.
`RemoteInboundMotionDispatcher` similarly keeps UpdateMotion protocol behavior
outside `GameWindow`: GameWindow resolves the canonical record/body and the
optional PartArray sink, while one dispatcher owns retail's interrupt, style,
@ -408,7 +416,8 @@ not `GameWindow`: it retains at most one pending request per materialized
incarnation, scopes it by the live generation and accepted PositionSequence,
and asks `RemoteTeleportPlacement` to collision-seat the current body when the
destination projection is available. `GameWindow` supplies lifecycle and
shadow-sync callbacks only; canonical identity remains in `LiveEntityRuntime`.
shadow-sync callbacks only; canonical identity remains in
`RuntimeEntityDirectory`, and App placement retains the exact projection key.
Failed hydration restores the captured source and delegates an
incarnation-scoped shadow restore to `LiveEntityPresentationController` while
that source is unloaded, so Hidden/UnHide and teleport never become competing
@ -459,16 +468,16 @@ useful ordering seam, but its ownership status is **partial**.
| Area | Status | Current truth |
|---|---|---|
| Startup options | **Complete** | `RuntimeOptions` owns startup configuration (`eda936dc`). Remaining direct environment reads are legacy runtime diagnostics, not startup configuration. |
| Network session | **Complete** | `LiveSessionController` remains the sole resolve/create/Connect/selection/EnterWorld/Tick/stop/reconnect/disposal and current-session owner. `LiveSessionHost` owns App reset/selection/entry ordering plus create→attach route factories; `LiveSessionLifecycleHost` guards exact borrowed-session attachment. `LiveSessionEventRouter` and `LiveSessionCommandRouter` own exact inbound/outbound lifetimes, with retryable per-edge teardown before reset or replacement. `GameWindow` retains the controller, focused host, and typed domain binding factories—no mirrored session or reset plan. |
| Network session | **Complete Runtime ownership** | `RuntimeLiveSessionController` owns the sole `WorldSession` generation and resolve/create/Connect/selection/EnterWorld/Tick/stop/reconnect/disposal transaction. Runtime route owners preserve exact inbound/outbound ordering and retryable teardown. App supplies immutable options, graphical/domain callbacks, and one borrowed inertable UI command projection—no mirrored session or reset plan (`75930787`). |
| World environment | **Complete ownership** | `WorldEnvironmentController` solely owns WorldTime, the loaded DAT sky, current day group, Weather, server synchronization, AdminEnvirons bridging, and debug time/weather cycles. `GameWindow` keeps ABI-compatible readonly aliases to the same clock/weather instances and composes one render source plus the two live-session callbacks. TS-54/TS-55 register the remaining centered UI sound and full fog/ambient/radar behavior gaps. |
| Live identity/lifetime | **Complete App integration** | `LiveEntityRuntime` owns incarnation identity, accepted snapshots/timestamps, runtime components, and logical/spatial lifetime. `LiveEntityHydrationController`, `LiveEntityRuntimeTeardownController`, and `LiveEntityNetworkUpdateController` own DAT-backed hydration, retryable cleanup, and accepted Position/Vector/State/Movement presentation without a second GUID owner (`aa90c646`). |
| Live identity/lifetime | **J3.3 complete** | `RuntimeEntityDirectory` owns the sole GUID/incarnation/local-ID identity, accepted snapshots/timestamps, parent state, operation versions, and tombstones. `LiveEntityProjectionStore` owns App graphical sidecars by exact `RuntimeEntityKey`; hydration, presentation components, `GpuWorldState` residence/visibility, and retryable teardown preserve that key without another GUID authority (`f46ddb5c`, `420e5eea`, `e937cc36`). |
| Inbound/object-frame order | **Complete App orchestration** | `UpdateFrameOrchestrator` owns the complete typed host phase graph; `RetailInboundEventDispatcher`, `RetailLiveFrameCoordinator`, `LiveObjectFrameController`, `LiveSpatialPresentationReconciler`, streaming/input/teleport/player-mode/camera owners preserve the accepted order. `GameWindow.OnUpdate` is one profiler-scoped handoff (`e91f3102`). |
| World reveal | **Complete** | `WorldRevealCoordinator` owns login/portal readiness, generation quiescence, and the exact streaming destination reservation. Login auto-entry and portal completion close only their active generation; stale completion cannot clear its replacement. Delayed portals retain the authored tunnel and centered retail wait notice rather than force-revealing incomplete content. Quiesced destination projections may become spatially resident for renderer preparation while all availability-gated gameplay consumers stay closed. |
| Retained gameplay UI | **Mostly complete feature ownership** | `RetailUiRuntime` and focused panel/controllers own layout and behavior. `GameWindow.OnLoad` still performs substantial service composition, which is allowed until the final composition cleanup. |
| Selection/interaction | **Complete** | `WorldSelectionQuery` owns read-only picking/classification/range queries; `SelectionInteractionController` owns selection intent, Use/PickUp transport, exact-incarnation queues, and auto-walk deferral; `ItemInteractionController` owns ItemHolder policy plus the shared retail inventory-request transaction. `GameWindow` retains construction and narrow lifecycle forwarding only. |
| Landblock presentation | **Complete** | `LandblockBuildFactory` owns the captured-origin DAT transaction; concrete render/physics/DAT-static publishers and `LandblockPresentationPipeline` own typed-meter publication and exact retryable retirement. `StreamingController` owns stable destination/control/unload/Near/Far queues and destination reservation. CPU mesh-cache restaging requires an exact live owner. `StreamingOriginRecenterCoordinator` serializes old-window retirement with teleport/session origin lifetimes. `GameWindow` retains construction and one pipeline field only (`c79d0a49`, closeout `4a205a3e`; Slice E closeout `91e82c3c`). |
| Render-frame orchestration | **Complete** | `RenderFrameOrchestrator` owns the GPU-flight, resource, world/PView/shared-alpha, private-presentation, diagnostics, screenshot, and recovery graph. `GameWindow.OnRender` takes one logical window-size snapshot and performs one immutable handoff (`9d7df1bf`). |
| Unified `GameEntity` | **Deferred target** | `LiveEntityRuntime` already supplies the critical canonical owner. Full type aggregation is a separate high-risk migration and is not required to make `GameWindow` thin. |
| Unified `GameEntity` | **Slice J active** | Canonical identity is now in Runtime and exact graphical sidecars are in App. J3.4 next moves `ClientObjectTable`; later J4J7 move coherent gameplay lifetime groups before one Runtime composition root. |
### 4.3 Revised extraction sequence

View file

@ -92,7 +92,7 @@ accepted-divergence entries (#96, #49, #50).
| AD-28 | Chat transcript (`UiText`) and input (`UiChatInput`) are two separate widget classes placed inside their dat-authored container panels; retail's `ChatInterface` uses a single mode-flagged `UIElement_Text` (Type-12) that switches between read and edit mode | `src/AcDream.App/UI/Layout/ChatWindowController.cs:135` (transcript) + `:150` (input) | `UIElement_Text` is inside keystone.dll with no PDB/decomp; a two-widget split is functionally equivalent (read-only scroll, editable input) and is the structural adaptation required by our UiElement architecture | A future consumer expecting a single widget for both read/write (e.g. a plugin calling the chat API and getting one widget back) must be written to the two-widget contract | `UIElement_Text` (Type-12) @ keystone.dll; `gmMainChatUI::PostInit` @0x4ce130 |
| AD-29 | `ClientObjectTable` fires global `ObjectAdded`/`ObjectUpdated`/`ObjectRemoved` events; consumers filter by guid on their end. Retail dispatches per-object via `NoticeRegistrar` observer dispatch — each UI cell observes only its specific object guid | `src/AcDream.Core/Items/ClientObjectTable.cs:48` (events); `src/AcDream.App/UI/Layout/ToolbarController.cs:115` (guid filter) | `NoticeRegistrar` is inside keystone.dll with no PDB/decomp; global broadcast + consumer-side filter is functionally equivalent for the current panel count and object volumes seen in practice | At high object counts (>1 000 objects), every `ObjectUpdated` wakes every subscribed consumer — O(n·m) notification cost instead of retail's O(1) per-observer dispatch; a consumer that forgets the guid filter processes all objects (a latent correctness bug) | `NoticeRegistrar` (keystone.dll, no PDB); retail per-object observer registration in `CObjectMaint` |
| AD-30 | Cell-march preserves seed landblock id when `TryGetTerrainOrigin` returns false for an outdoor seed (#145 D, 2026-06-22): `BuildCellSetAndPickContaining` returns `currentCellId` verbatim rather than marching via `blockOrigin=(0,0,0)`; retail never encounters this state (cells stored block-local, no streaming-gap concept) | `src/AcDream.Core/Physics/CellTransit.cs:765` | Equivalence argument: "preserve-verbatim when unregistered" is the same contract as `PhysicsEngine.Resolve`'s NO-LANDBLOCK branch; the player's cell stays the last known-correct cell until the landblock's terrain registers — no march, no lbX=0 wire | A body whose seed landblock is genuinely absent for >1 physics tick holds its last-known cell rather than discovering the true containing cell; transient only — corrects the instant terrain registers; an indoor seed is explicitly excluded from the guard (outdoor low < 0x100 gate) | `CObjCell::find_cell_list` + block-local storage (retail has no streaming gap); `TryGetTerrainOrigin` pc path |
| AD-32 | Movement, Position, State, Vector, Pickup, Delete, and ObjDesc packets for a FUTURE object incarnation are dropped until its CreateObject arrives; retail queues each blob for the not-yet-created object (`SmartBox::QueueBlobForObject`, dispatch return 4) and replays it after construction. F754/F755 and ParentEvent are no longer part of this divergence: `EntityEffectController` preserves one mixed effect FIFO per server GUID until the canonical local owner is ready, while runtime-owned `ParentAttachmentState` retains parent relations by generation. Older-incarnation state packets drop in both clients. | `src/AcDream.Core/Physics/PhysicsTimestampGate.cs` (`TryAcceptInstance`); `src/AcDream.App/World/InboundPhysicsStateController.cs`; `src/AcDream.App/World/LiveEntityRuntime.cs`; effect exception `src/AcDream.App/Rendering/Vfx/EntityEffectController.cs`; Parent exception `src/AcDream.App/World/ParentAttachmentState.cs` | acdream has no general per-object mixed queue for non-effect state packets yet. Dropping those state families is generation-safe: a future packet cannot mutate the old body or advance its stamps, and the newer CreateObject wholesale-seeds all nine channels. The canonical runtime remains the seam for widening the queue later. | The first queued non-effect state can be absent until a later authoritative update; effect and Parent packets retain retail order and ownership | `SmartBox::HandleSetState` 0x004533E0; `HandleVectorUpdate` 0x00453480; `HandleParentEvent` 0x004535D0; `HandlePickupEvent` 0x00453530; `HandleDeleteObject` 0x00451EA0; `HandleObjDescEvent` 0x00453340; `UnpackPositionEvent` 0x004542C0; F754/F755 dispatch and pending replay pc:357214-357239 |
| AD-32 | Movement, Position, State, Vector, Pickup, Delete, and ObjDesc packets for a FUTURE object incarnation are dropped until its CreateObject arrives; retail queues each blob for the not-yet-created object (`SmartBox::QueueBlobForObject`, dispatch return 4) and replays it after construction. F754/F755 and ParentEvent are no longer part of this divergence: `EntityEffectController` preserves one mixed effect FIFO per server GUID until the canonical local owner is ready, while runtime-owned `ParentAttachmentState` retains parent relations by generation. Older-incarnation state packets drop in both clients. | `src/AcDream.Core/Physics/PhysicsTimestampGate.cs` (`TryAcceptInstance`); `src/AcDream.Runtime/Entities/InboundPhysicsStateController.cs`; `src/AcDream.App/World/LiveEntityRuntime.cs`; effect exception `src/AcDream.App/Rendering/Vfx/EntityEffectController.cs`; Parent exception `src/AcDream.Runtime/Entities/ParentAttachmentState.cs` | acdream has no general per-object mixed queue for non-effect state packets yet. Dropping those state families is generation-safe: a future packet cannot mutate the old body or advance its stamps, and the newer CreateObject wholesale-seeds all nine channels. The canonical runtime remains the seam for widening the queue later. | The first queued non-effect state can be absent until a later authoritative update; effect and Parent packets retain retail order and ownership | `SmartBox::HandleSetState` 0x004533E0; `HandleVectorUpdate` 0x00453480; `HandleParentEvent` 0x004535D0; `HandlePickupEvent` 0x00453530; `HandleDeleteObject` 0x00451EA0; `HandleObjDescEvent` 0x00453340; `UnpackPositionEvent` 0x004542C0; F754/F755 dispatch and pending replay pc:357214-357239 |
| AD-33 | `CSequence.frame_number` at C# `double` (64-bit); retail is x87 `long double` (80-bit extended) — every frame-boundary comparison ran at extended precision on retail (Phase R1, 2026-07-02) | `src/AcDream.Core/Physics/Motion/CSequence.cs` (`FrameNumber`) | `double` is the widest C# float type; the R1 port removes ACE-style boundary epsilons so comparisons are exact-int against bare boundaries, minimizing ULP sensitivity (ACE's `float` is far worse) | A frame landing within 1 double-ULP of an integer boundary could classify differently than retail's 80-bit compare — sub-frame timing skew at pathological framerate×dt combinations | `acclient.h:30747` (`long double frame_number`) |
| AD-34 | Retail's intrusive `DLListBase`/`DLListData` lists are managed `LinkedList<T>`s; node identity via `LinkedListNode<>` references (Phase R1 anim list, 2026-07-02; extended R2-Q3 to `MotionTableManager.pending_animations`; extended R4-V2 to `MoveToManager.pending_actions` — whose node type, retail `MoveToManager::MovementNode`, is RENAMED `MoveToNode` to avoid colliding with R2's `Motion/MotionNode.cs` pending_motions node) | `src/AcDream.Core/Physics/Motion/CSequence.cs` (`_animList`); `src/AcDream.Core/Physics/Motion/MotionTableManager.cs` (`_pendingAnimations`); `src/AcDream.Core/Physics/Motion/MoveToManager.cs` (`_pendingActions`) + `MoveToNode.cs` | Same topology + cursor semantics (curr_anim/first_cyclic/tail-anchored scans are node references); unlink/delete becomes `Remove(node)`; conformance tests pin the surgery state tables | Any retail behavior depending on the 4 pointer adjustment or node memory reuse (none observed in the decomp) would diverge; a reader grepping for retail's `MovementNode` name must find it via this row | `acclient.h` DLListBase; `r1-csequence-decomp.md` §0; `r2-motiontable-decomp.md` §11; `r4-moveto-decomp.md` node factories §4a |
| AD-35 | `MotionTableManager.PerformMovement`'s unhandled-type default case returns the named sentinel `0xFFFFFFFF` (`MotionTableManagerError.NotHandled`); retail's compiled code leaks the `CSequence*` pointer reinterpreted as the return code (BN-confirmed artifact, dead/unreachable — callers gate on type first) (R2-Q3, 2026-07-02) | `src/AcDream.Core/Physics/Motion/MotionTableManager.cs` (`PerformMovement` default case) | No retail caller consults the return value for unhandled types (RawCommand/StopRawCommand/MoveTo\*/TurnTo\* route elsewhere); returning a stable non-zero sentinel preserves the only observable contract (non-zero = not success) without fabricating a pointer-shaped number | If a future port wires a caller that passes unhandled types AND branches on the exact return value, it would see `0xFFFFFFFF` where retail saw an arbitrary pointer — flag at that port | `PerformMovement` 0x0051c0b0 (`r2-motiontable-decomp.md` §11 default-case note) |