fix(runtime): restore interaction completion ownership

Preserve prepublication local motion completion, require the PartArray enter-world lifecycle port, and balance deferred Use busy ownership across dispatch and cancellation. Reconcile the completed GameWindow connected gates and add regression coverage.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-23 05:51:51 +02:00
parent 5c955c36ec
commit f9736ece6c
26 changed files with 675 additions and 68 deletions

View file

@ -30,9 +30,9 @@ What does NOT go here:
automated closeout are complete. The 1,622-line native shell has typed
startup, frame, callback, and shutdown owners; 293 focused tests, the
7,823/5 Release suite, the lifecycle/reconnect route, two canonical
nine-stop soaks, and the Slice-7 framebuffer comparison pass. Only the
user's final connected visual matrix remains before the campaign is marked
complete in [`docs/architecture/code-structure.md`](architecture/code-structure.md).
nine-stop soaks, and the Slice-7 framebuffer comparison pass. The user's
connected visual matrix passed 2026-07-23, so the campaign is complete in
[`docs/architecture/code-structure.md`](architecture/code-structure.md).
- **Separate rendering gate:** `#225`, lifestone/particle alpha ordering. Its
connected performance, lifetime, and unattended portal routes pass.
- **Carried behavior debt:** `#153` far-teleport unstreamed-edge arrival and
@ -7600,6 +7600,40 @@ outdoors at the angle that previously erased it.
# Recently closed
## #234 — [DONE 2026-07-23] Cancelled close-range Use could strand the busy cursor
**Closed:** 2026-07-23
**Severity:** HIGH
**Component:** interaction / retained UI / local approach
**Resolution:** `ItemInteractionController` acquired retail's shared busy
reference before `SelectionInteractionController` completed a close-range
turn. If that pre-wire approach was cancelled, failed to install, lost its
session, or crossed an entity-incarnation boundary, no Use packet existed and
therefore ACE could never return the `UseDone` needed to release the count.
An explicit, generation-bound `ItemUseRequestReservation` now owns that
boundary. Successful wire dispatch transfers ownership to authoritative
`UseDone`; every pre-dispatch cancellation path releases the reservation
locally and idempotently. Session reset invalidates late reservations so an
old callback cannot decrement a new session. Focused tests pin cancellation,
failed movement installation, stale identity, synchronous turn completion,
successful dispatch, and late post-reset resolution.
The connected report itself completed normally: the log contained the
successful NPC Use and `UseDone(0)`, and a managed heap snapshot afterward
showed busy count zero with no pending inventory or auto-wield transaction.
That evidence separated an ordinary in-flight rejection from the real latent
pre-wire leak.
**Connected gate:** Passed 2026-07-23. Cancelling the close-range turn no
longer strands the busy cursor, and the next NPC/item Use succeeds normally.
**Research:**
[`docs/research/2026-07-23-retail-use-busy-ownership-pseudocode.md`](research/2026-07-23-retail-use-busy-ownership-pseudocode.md).
---
## #229 — [DONE 2026-07-20] Initial login revealed an incomplete world
**Closed:** 2026-07-20

View file

@ -2,8 +2,8 @@
**Status:** Living document. Created 2026-05-16; implementation reconciliation
completed 2026-07-21; Slices 18 and their automated closeout landed by
2026-07-22. The final connected visual matrix remains before new M4 subsystems
enter the App layer.
2026-07-22. The connected visual matrix passed 2026-07-23; the campaign is
complete and new M4 subsystems may enter through the extracted owners.
**Purpose:** Describe the desired structural state of the App layer,
explain the rules we've adopted, and lay out the safe extraction
sequence from today's reality (one 15,723-line `GameWindow.cs` at the
@ -758,7 +758,9 @@ with graceful exits; deterministic framebuffer captures preserve accepted
Slice 7 geometry, UI/paperdoll layering, private viewports, depth/alpha, and
reveal. The clean solution build retains only the 17 test-project warnings
tracked by #228, and all three final corrected-diff reviews are clean. Only the
user's final connected visual matrix remains.
user's connected visual matrix passed 2026-07-23, including first-login radar,
movement/combat, shared retained panels, recall/portal presentation, and
graceful reconnect.
### 4.4 Exit criteria

View file

@ -1,6 +1,6 @@
# acdream — strategic roadmap
**Status:** Living document. Updated 2026-07-22. **M3 landed; M4 is next after the final `GameWindow` visual handoff.** M3's retail casting/UI, R6 locomotion/collision/projectile/teleport/radar rebaseline, deterministic fresh-login/portal world lifecycle, and final two-client portal observer flow are user-gated. All eight slices of the behavior-preserving ownership campaign in [`docs/architecture/code-structure.md`](../architecture/code-structure.md) and their automated closeout are complete; only the user's connected visual matrix remains. Issue #225's lifestone/particle alpha comparison remains a separate rendering visual gate.
**Status:** Living document. Updated 2026-07-23. **M3 landed; M4 is active.** M3's retail casting/UI, R6 locomotion/collision/projectile/teleport/radar rebaseline, deterministic fresh-login/portal world lifecycle, and final two-client portal observer flow are user-gated. All eight slices of the behavior-preserving ownership campaign in [`docs/architecture/code-structure.md`](../architecture/code-structure.md), their automated closeout, and the user's connected visual matrix are complete. Issue #225's lifestone/particle alpha comparison remains a separate rendering visual gate.
**Purpose:** One source of truth for where the project is and where it's going. Every observed defect or missing feature has a named phase that owns it; when something looks wrong in-game, look here to find the phase that'll address it. Implementation details live in per-phase specs under `docs/superpowers/specs/`, not in this file.
---
@ -116,7 +116,8 @@ snapshots and closes #232 through two clean fresh-process nine-stop routes.
The shell is 1,622 raw lines. All 293 focused tests, App Release 3,451/3, the
complete Release suite 7,823/5, connected lifecycle/reconnect, both canonical
soaks, three corrected-diff reviews, and the exact Slice-7 framebuffer
comparison pass. Only the user's final connected visual matrix remains.
comparison pass. The user's connected visual matrix passed 2026-07-23 and the
structural program is complete.
This is a behavior-preserving structural program. Severe regressions still get
root-cause fixes in separate commits; ordinary feature work resumes with M4

View file

@ -74,8 +74,9 @@ without loosening the process thresholds. The automated Slice 8 closeout is
complete: `GameWindow` is 1,622 raw lines; 293 focused tests, App Release
3,451/3, the complete Release suite 7,823/5, lifecycle/reconnect, both
canonical soaks, all three corrected-diff reviews, and the Slice-7 framebuffer
comparison pass. Only the user's final connected visual matrix remains before
new M4 feature bodies begin.
comparison pass. The user's connected visual matrix passed 2026-07-23,
including the post-closeout radar correction, so new M4 feature bodies may
begin through the extracted owners.
Carried:
#153 far-teleport residual, #116 slide-response, the remaining R6 ownership cleanup plus

View file

@ -0,0 +1,117 @@
# Retail Use busy-reference ownership
Date: 2026-07-23
Scope: the retained UI busy cursor and the
`You can only move or use one item at a time` gate while an object/NPC Use
crosses acdream's close-range turn/approach boundary.
## Retail oracle
Named Sept-2013 retail symbols:
- `ItemHolder::UseObject @ 0x00588A80`
- `ClientUISystem::Handle_Item__UseDone @ 0x00564900`
- `CM_Item::DispatchUI_UseDone @ 0x006A8510`
- `ClientUISystem::UsageCallback @ 0x00565B20`
`ItemHolder::UseObject` checks the 200 ms throttle and
`IsPlayerReadyToMakeInventoryRequest`, emits `CM_Inventory::Event_UseEvent`,
increments `ClientUISystem::m_cBusy`, and then calls
`CPlayerSystem::UsingItem`. The confirmation callback has the same order.
`CM_Item::DispatchUI_UseDone` routes event `0x01C7` to
`Handle_Item__UseDone`, which decrements exactly one busy reference and updates
the cursor when the count reaches zero.
Therefore the invariant is:
```text
one accepted Use request that reached the wire
owns one busy reference
until one authoritative UseDone arrives
```
There is no retail timeout and movement cancellation is not a substitute for
`UseDone` after dispatch.
## ACE and protocol cross-reference
- ACE `GameActionUseItem` and `Player_Use.TryUseItem` confirm that the Use
request owns the server-side operation and that every completed/rejected
operation returns `UseDone`.
- ACE `Player_Move.CreateMoveToChain` confirms that the server may approach a
distant target before executing that operation.
- holtburger `inventory/actions.rs` and `inventory/events.rs` independently
confirm the one Use action / one UseDone event protocol pair.
The existing close-range compatibility seam is recorded as divergence AD-27:
acdream may finish its local turn before emitting Use. That seam creates a
real pre-wire interval which retail does not have, so it needs explicit
ownership rather than pretending a server completion is already owed.
## Ported ownership pseudocode
```text
BeginAcceptedUse(object):
reservation = BusyOwner.Reserve()
busyCount += 1
RequestUse(object, reservation)
RequestUse(object, reservation):
if session/identity/approach is invalid:
reservation.CancelBeforeDispatch()
return
if a close-range turn must finish first:
pending = (exact object incarnation, approach token, reservation)
if movement installation fails:
pending.reservation.CancelBeforeDispatch()
return
if SendUse(object) succeeds:
reservation.MarkDispatched()
else:
reservation.CancelBeforeDispatch()
OnNaturalApproachCompletion(pending):
if pending incarnation is stale:
pending.reservation.CancelBeforeDispatch()
else if SendUse(pending.object) succeeds:
pending.reservation.MarkDispatched()
else:
pending.reservation.CancelBeforeDispatch()
OnApproachCancelledOrTargetRemoved(pending):
pending.reservation.CancelBeforeDispatch()
reservation.MarkDispatched():
// Ownership transfers to the server completion. Keep busyCount unchanged.
make later local cancellation inert
reservation.CancelBeforeDispatch():
// No packet exists, therefore no UseDone can arrive for this reservation.
busyCount -= 1
make duplicate cancellation inert
OnUseDone(error):
busyCount -= 1
if busyCount == 0:
update cursor
OnSessionReset():
invalidate the reservation generation
busyCount = 0
```
The reservation is idempotent and generation-bound. A late callback from a
retired session cannot decrement a new session's busy reference.
## Regression evidence
The connected trace showed successful NPC Use packets and matching
`UseDone(0)`. A managed heap snapshot after the final completion showed
`ItemInteractionController._busyCount == 0`, no pending inventory request, and
no pending auto-wield transaction. The permanent failure path was instead the
pre-wire close-range branch: it incremented busy before local movement, but
cancellation or stale identity discarded the pending send, leaving no packet
and therefore no possible `UseDone`.