fix(world): restore portal and distant use lifecycles
This commit is contained in:
parent
c730632075
commit
1a14812c44
18 changed files with 401 additions and 196 deletions
|
|
@ -89,6 +89,30 @@ Copy this block when adding a new issue:
|
|||
|
||||
---
|
||||
|
||||
## #245 — Distant Use stayed busy without turning or walking
|
||||
|
||||
**Status:** DONE — 2026-07-25; connected gate passed
|
||||
**Severity:** HIGH
|
||||
**Filed:** 2026-07-25
|
||||
**Component:** interaction transport / local motion completion ownership
|
||||
|
||||
**Root cause / resolution:** Retail sends Use before any approach and ACE
|
||||
authoritatively returns the required MoveTo chain. After restoring that order,
|
||||
the live trace showed ACE's MoveTo target resolved correctly but remained
|
||||
behind one orphan login Ready node. `LiveEntityAnimationPresenter` had guarded
|
||||
`MotionDoneTarget` installation by the temporary spatial projection, even
|
||||
though the exact logical live-record animation runtime still owned the
|
||||
PartArray. The PartArray consumed Ready without popping the
|
||||
`MotionInterpreter`. Completion is now bound to the logical animation owner,
|
||||
and player-controller construction drains any pre-controller PartArray queue
|
||||
before publishing its interpreter. Focused tests cover withdrawn spatial
|
||||
projection, replacement owners, immediate one-shot Use, and busy transfer to
|
||||
UseDone. The connected distant-NPC gate turns, walks, uses, and releases busy.
|
||||
|
||||
**Research:** `docs/research/2026-07-23-retail-use-busy-ownership-pseudocode.md`
|
||||
|
||||
---
|
||||
|
||||
## #234 — Monster assessment held busy and examination used the shared panel
|
||||
|
||||
**Status:** DONE — 2026-07-23; corrected creature presentation visual gate pending
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ accepted-divergence entries (#96, #49, #50).
|
|||
| AD-23 | Live entities with `ServerGuid != 0` and null `ParentCellId` are culled (ClipSlotCull) while indoor clip routing is active; retail objects are always cell-resident (synchronous add-to-cell at creation) | `src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs:484` | Phase U.4 policy: parentless = unresolved indoors, equivalent to retail's not-in-any-visible-cell ⇒ not drawn, *given membership resolves promptly* | An entity whose membership lags (late CreateObject hydration, resolver hiccup) blinks invisible while the player is indoors, even in plain sight | retail per-cell object lists in PView traversal |
|
||||
| AD-24 | EnvCell shell geometry content-deduplicated and instanced; retail draws each CEnvCell's own structure directly | `src/AcDream.Core/Rendering/Wb/EnvCellGeometryIdentity.cs` | Phase A8 retained WB's 31× hash; the 2026-07-24 full-DAT gate proved a real collision (`0x00030175`/`0x01BC0105`), so App+Bake now share a namespaced FNV-1a tuple identity and the bake rejects any full-tuple collision | A future collision outside the installed full-DAT gate could still merge different shells at runtime; the stronger 59-bit payload makes this extremely unlikely, and every bake fails loudly rather than publishing it | retail `PView::DrawCells` → per-cell drawing_bsp (cited at the former renderer `:319`) |
|
||||
| AD-25 | **REMOTE-DR sweep only** (the player half retired 2026-07-07 by the #182 verbatim rebuild): the remote dead-reckoning post-resolve still reflects velocity with the airborne-before-AND-after suppression; retail bounces unless grounded→grounded-and-not-sledding. The PLAYER path now runs the ported `handle_all_collisions` (`PhysicsObjUpdate`) with retail's `should_reflect` rule — the micro-bounce spiral it guarded is gone (contact is committed BEFORE the reflect and the small-velocity-zero is ungated) | `src/AcDream.App/Physics/RemotePhysicsUpdater.cs` (remote sweep post-resolve, #173 block) | The remote DR sweep hasn't been rebuilt yet (it has no fsf/SetPositionInternal chain); the old airborne-only suppression keeps remote landings from micro-bouncing on the remote landing-snap gate | Remote landing-reflection behavior (slope-landing momentum) won't reproduce; retire when the remote-DR sweep gets the same UpdateObjectInternal rebuild as the player | `handle_all_collisions` pc:282699-282715; ACE PhysicsObj.cs:2656-2721 |
|
||||
| AD-27 | Use/PickUp action fired on natural moveto completion via the `MoveToComplete` client-addition seam (retail's `CleanUpAndCallWeenie` contains no weenie call in this build and notifies nothing on arrival); retail sends the action once (server MoveToChain callback completes it). The companion `MoveToCancelled` seam only withdraws the waiting client presentation/action. | `src/AcDream.App/Interaction/SelectionInteractionController.cs` (`OnNaturalMoveToComplete`/`OnMoveToCancelled`); `src/AcDream.App/Rendering/GameWindow.cs` (player MoveTo seam binding); `src/AcDream.Core/Physics/Motion/MoveToManager.cs` (`MoveToComplete`/`MoveToCancelled`) | ACE's server-side chain may have timed out by the time our body arrives; the close-range deferred send hits ACE's WithinUseRadius fast-path. R4-V5 re-anchored from the deleted B.6 `AutoWalkArrived` event — same fires-on-arrival-only contract; cancellation/error only clears retained intent. | If the server's chain has NOT timed out, the action executes twice — door toggles open-then-closed, use-once interactions double-fire; protocol noise on non-ACE servers | ACE CreateMoveToChain / WithinUseRadius; `MoveToManager::CleanUpAndCallWeenie` 00529650 §7e (no weenie call) |
|
||||
| AD-27 | PickUp fires on natural moveto completion via the `MoveToComplete` client-addition seam (retail's `CleanUpAndCallWeenie` contains no weenie call in this build and notifies nothing on arrival). The companion `MoveToCancelled` seam only withdraws the waiting pickup presentation/action. **Use retired 2026-07-25:** `ItemHolder::UseObject` sends `Event_UseEvent` before `CPlayerSystem::UsingItem`; acdream now does the same and leaves approach to ACE's authoritative MoveToChain. | `src/AcDream.App/Interaction/SelectionInteractionController.cs` (`OnNaturalMoveToComplete`/`OnMoveToCancelled`); `src/AcDream.App/Input/PlayerModeController.cs` (player MoveTo seam binding); `src/AcDream.Core/Physics/Motion/MoveToManager.cs` (`MoveToComplete`/`MoveToCancelled`) | ACE's server-side pickup chain may have timed out by the time our body arrives; the close-range deferred send hits ACE's WithinUseRadius fast-path. | If the server's chain has not timed out, pickup may execute twice or produce protocol noise on non-ACE servers | ACE CreateMoveToChain / WithinUseRadius; `MoveToManager::CleanUpAndCallWeenie` 00529650 §7e (no weenie call); `ItemHolder::UseObject` 0x00588A80 |
|
||||
| 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 |
|
||||
|
|
@ -134,7 +134,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| AP-20 | Sub-pixel view-polygon vertex merge fixed at 1080p-reference NDC units (2/1080); retail merges at ~1 actual screen pixel | `src/AcDream.App/Rendering/PortalProjection.cs:179` | Unit approximation whose coarseness only strengthens convergence — the merge is the flood's fixpoint floor (replaced MaxReprocessPerCell=16) | At 4K+ a legitimately visible 1–2 px sliver aperture collapses to degenerate and rejects — a thin/distant doorway stops admitting its flood slightly earlier than retail | `Render::copy_view` 0x0054dfc0 |
|
||||
| AP-21 | Entity translucency retains the invented α<0.05 fragment discard. World GfxObj/Setup instances now apply their DAT AlphaBlend/Additive/InvAlpha factors through the retail shared alpha queue, but sealed off-screen WbDrawDispatcher consumers (paperdoll/UI Studio) retain the old immediate normal-alpha pass for all three kinds | `src/AcDream.App/Rendering/Shaders/mesh_modern.frag`; `src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs` (`DrawDeferredAlphaBatch` versus immediate Phase 8) | World presentation needed exact per-surface blend for spell/particle density and translucent-object intersections. Off-screen object previews are isolated render targets and have not shown an authored additive entity surface that justifies splitting their compact immediate pass | A faint world fringe below 5% alpha is discarded; a hypothetical additive/inverse-alpha paperdoll or UI Studio entity composites darker than retail inside that private viewport | `D3DPolyRender::SetSurface`; `D3DPolyRender::RenderMeshSubset`; SurfaceType.Additive → D3DBLEND_ONE |
|
||||
| AP-22 | Invented `setup.Radius` cylinder (height = Height or Radius×2) for shapeless live entities; shape + height formula not from the retail shape walk | `src/AcDream.App/Physics/LiveEntityCollisionBuilder.cs`; `src/AcDream.Core/Physics/ShadowShapeBuilder.cs` | ShadowShapeBuilder (faithful walk) only emits CylSphere/Sphere/Part-BSP; the legacy cylinder preserves prior behavior so rare decorative props don't lose collision | Those props collide with an invented footprint (especially the Radius×2 height guess) — slides/blocks at non-retail distances | `find_obj_collisions` → `CPartArray::FindObjCollisions` pc:286236 |
|
||||
| AP-23 | Invented per-type use-radius heuristic (3 m creatures / 2 m doors-lifestones-portals-corpses / 0.6 m rest) for close-range gating + the speculative local TurnToObject/MoveToObject install through the player's MoveToManager (R4-V5; retail's client use flow issues the same manager calls, §9a/§9b). **R5-V3 narrowed it: the speculative install now threads the target's REAL setup radius/height (`GetSetupCylinder`, same as the wire mt-6 route) and the player's own radius is real — only the use-radius BUCKETS remain invented** (retail reads the object's UseRadius property) | `src/AcDream.App/Interaction/WorldSelectionQuery.cs` (`TryGetApproach`/`GetUseRadius`); `src/AcDream.App/Interaction/PlayerInteractionMovementSink.cs` (`BeginApproach`) | ACE broadcasts nothing actionable on the close branch (WithinUseRadius shortcut); the true radius arrives only on the far MoveToObject branch — a local stand-in is required | A target whose real UseRadius differs from the bucket misjudges the gate — Use/PickUp deferred for a completion that never comes, or fires early into a server "too far" | ACE Player_Move.cs:66; wire MoveToObject (type 6) carries the true radius; `CPhysicsObj::TurnToObject/MoveToObject` callers §9a/§9b |
|
||||
| AP-23 | Invented per-type pickup-radius heuristic (3 m creatures / 2 m doors-lifestones-portals-corpses / 0.6 m rest) for close-range gating plus the speculative local TurnToObject/MoveToObject install through the player's MoveToManager. **R5-V3 narrowed it:** the install threads the target's real Setup radius/height (`GetSetupCylinder`, same as wire mt-6) and the player's real radius; only the radius buckets remain invented. **Use retired from this seam 2026-07-25** and now sends immediately. | `src/AcDream.App/Interaction/WorldSelectionQuery.cs` (`TryGetApproach`/`GetUseRadius`); `src/AcDream.App/Interaction/PlayerInteractionMovementSink.cs` (`BeginApproach`) | The retained pickup presentation reserves a destination slot before the authoritative transfer; its close branch still needs an arrival boundary | A target whose real UseRadius differs from the bucket misjudges the pickup gate — pickup waits forever or fires early into a server "too far" | ACE Player_Move.cs:66; wire MoveToObject (type 6) carries the true radius; `CPhysicsObj::TurnToObject/MoveToObject` callers §9a/§9b |
|
||||
| ~~AP-24~~ | **RETIRED 2026-07-11** — matching v11.4186 x86 disassembly recovered `ATTACK_POWERUP_TIME=1.0` seconds and `DUAL_WIELD_POWERUP_TIME=0.8` seconds from the operands loaded by `GetPowerBarLevel`; jump and combat now share those constants. | `src/AcDream.Core/Combat/CombatModel.cs`; `src/AcDream.App/Input/PlayerMovementController.cs`; `src/AcDream.App/Combat/CombatAttackController.cs` | — | — | `ClientCombatSystem::GetPowerBarLevel @ 0x0056ADE0`; static data `0x007CEFC8/0x007CEFD0` |
|
||||
| AP-25 | Run/Jump skill pushed to movement = attributeBonus + Init + Ranks — no augmentations, multipliers, or vitae | `src/AcDream.Core.Net/GameEventWiring.cs:346` | Closest to ACE's CreatureSkill.Current short of porting the full Aug/Multiplier/Vitae chain (K-fix7/13) | A character with augs or post-death vitae predicts wrong local run speed / jump arc — dying would NOT slow the local player though the server moves them slower: drift + snap-back | ACE CreatureSkill.Current; ACE Skill.cs (Jump=22, Run=24) |
|
||||
| AP-26 | DDD interrogation answered with an empty dat-version list (count=0); retail reports actual dat iteration state | `src/AcDream.Core.Net/Messages/DddInterrogationResponse.cs:18` | ACE is satisfied by the empty ack; pattern from holtburger | A dat-patching-enabled server could push a full patch or reject on version mismatch — the lie is harmless only while the server never acts on it | DDD flow 0xF7E5/0xF7E6 |
|
||||
|
|
|
|||
|
|
@ -22,10 +22,14 @@ cursor owners. Its connected state gate passed. A named-retail correction now
|
|||
treats reset/absent useability zero as usable. The connected gate then exposed
|
||||
the delivery defect beneath that classifier: carried Use had been routed
|
||||
through the world-object approach query and silently cancelled because
|
||||
inventory objects have no spatial entity. Owned direct-use now sends
|
||||
immediately, while the registered AD-27 approach seam remains world-only;
|
||||
shared AutoWear reports the exact overlapping worn item that must be removed.
|
||||
That correction is user-confirmed. The requested adjacent cooldown follow-up
|
||||
inventory objects have no spatial entity. Ordinary Use now sends immediately
|
||||
for both owned and world objects, matching `ItemHolder::UseObject`; ACE owns
|
||||
distant approach and AD-27 remains pickup-only. The connected distant-NPC
|
||||
gate also corrected the logical animation-owner callback across temporary
|
||||
spatial projection withdrawal, so ACE MoveTo turns, walks, and completes Use
|
||||
without leaving the UI busy. Shared AutoWear reports the exact overlapping
|
||||
worn item that must be removed. These corrections are user-confirmed. The
|
||||
requested adjacent cooldown follow-up
|
||||
ports the optional public shared-cooldown metadata, the authoritative
|
||||
cooldown-enchantment lookup, the exact ten-step bucket formula, and DAT
|
||||
sprites `0x060067CF..0x060067D8` through the shared retained `UiItemSlot`
|
||||
|
|
|
|||
|
|
@ -86,10 +86,11 @@ The connected selection-state gate passed. Its follow-up exposed three deeper
|
|||
shared-policy/delivery defects, now corrected from named retail plus the
|
||||
matching binary. `ItemUses::IsUseable @ 0x004FCCC0` tests only `USEABLE_NO`;
|
||||
reset/absent value zero is usable. `ItemHolder::UseObject @ 0x00588A80` sends a
|
||||
carried direct-use item immediately; acdream had incorrectly routed that
|
||||
packet through a world approach lookup, where Blackmoor's Favor has no spatial
|
||||
entity and was silently cancelled. Owned Use now bypasses that query while
|
||||
world-object Use retains the registered AD-27 approach seam. AutoWear applies
|
||||
Use event immediately for both owned and world objects; acdream had
|
||||
incorrectly routed the packet through a local approach lookup, where
|
||||
Blackmoor's Favor has no spatial entity and was silently cancelled. Every
|
||||
ordinary Use now sends once and ACE owns any authoritative MoveTo chain.
|
||||
AD-27 remains only for client-side pickup completion. AutoWear applies
|
||||
`CPlayerSystem::AutoWearIsLegal @ 0x0055EF40` through the same double-click and
|
||||
toolbar-hand path, resolves the overlapping worn object from the
|
||||
retail-ordered equipment projection, and emits the exact system line
|
||||
|
|
|
|||
|
|
@ -446,6 +446,57 @@ state through `CPhysicsObj::set_state` `0x00514DD0`. Separately,
|
|||
`CPhysicsObj::UpdatePositionInternal` `0x00512C30` skips `CPartArray::Update`
|
||||
while Hidden, so ordinary animation never advances behind the portal viewport.
|
||||
|
||||
### Destination simulation resumes before the portal viewport exits
|
||||
|
||||
The retail destination has two different release edges. They must not share
|
||||
one acdream gate:
|
||||
|
||||
```text
|
||||
SmartBox::DoSetState(player, visible):
|
||||
set_state(...)
|
||||
waiting_for_teleport = false
|
||||
|
||||
SmartBox::UseTime(next frame):
|
||||
if not cell_manager.blocking_for_cells:
|
||||
position_update_complete = true
|
||||
CObjectMaint::UseTime()
|
||||
CPhysics::UseTime()
|
||||
|
||||
gmSmartBoxUI::UseTime:
|
||||
if SmartBox::teleport_in_progress() changed from true to false:
|
||||
EndTeleportAnimation()
|
||||
|
||||
gmSmartBoxUI::EndTeleportAnimation @ 0x004D65A0:
|
||||
teleportAnimState = TAS_TUNNEL_CONTINUE
|
||||
teleportTransitionStartTime = Timer::cur_time
|
||||
clear temporary FOV override
|
||||
|
||||
gmSmartBoxUI::UseTime, at least two seconds later:
|
||||
TAS_TUNNEL_CONTINUE -> TAS_TUNNEL_FADE_OUT
|
||||
...
|
||||
TAS_TUNNEL_FADE_OUT -> TAS_WORLD_FADE_IN
|
||||
hide portal viewport
|
||||
show SmartBox world viewport
|
||||
```
|
||||
|
||||
Therefore `TAS_TUNNEL_CONTINUE` is not a continued cell-loading freeze. The
|
||||
destination world is already ticking behind the private portal viewport for at
|
||||
least two seconds. That hidden interval is when the player pose, queued
|
||||
Hidden/UnHide PhysicsScripts, attached emitters, and destination presentation
|
||||
settle. Only the later `TUNNEL_FADE_OUT -> WORLD_FADE_IN` edge changes which
|
||||
viewport is drawn.
|
||||
|
||||
Holding acdream's world-generation quiescence until the viewport swap is a
|
||||
behavioral error: the first destination physics/effect tick occurs on the
|
||||
first visible world frame, producing a character pop plus leftover recall or
|
||||
purple materialization effects. The faithful split is:
|
||||
|
||||
- end world simulation/audio quiescence when the accepted destination is
|
||||
materialized and `TunnelContinue` begins;
|
||||
- retain the destination streaming/resource reservation and keep drawing only
|
||||
the portal viewport until the later viewport-swap edge;
|
||||
- keep LoginComplete on the final one-second `WorldFadeIn -> Off` edge.
|
||||
|
||||
ACE schedules lifestone recall teleport after the MotionTable-reported action
|
||||
length. With the installed human DATs, the boundary is about 15.06024 seconds;
|
||||
floating-point conversion can leave acdream at frame 149.999998 of the
|
||||
|
|
|
|||
|
|
@ -55,11 +55,11 @@ both pass `IsUseable`.
|
|||
|
||||
For a direct-use item, `ItemHolder::UseObject` then sends
|
||||
`CM_Inventory::Event_UseEvent`, increments the UI busy count, and calls
|
||||
`CPlayerSystem::UsingItem`. That latter presentation function handles local
|
||||
pickup/wield/container state; it does not require a carried source to have a
|
||||
world position. Therefore an owned inventory object must cross the wire
|
||||
directly. Only world objects participate in acdream's registered AD-27 local
|
||||
approach compatibility seam.
|
||||
`CPlayerSystem::UsingItem`. `Event_UseEvent` itself is the network send
|
||||
(`0xF7B1/0x36`); the latter function handles local pickup/wield/container
|
||||
presentation. Therefore neither an owned item nor a world object may be held
|
||||
behind a client-side movement completion. ACE owns the authoritative approach
|
||||
chain.
|
||||
|
||||
```text
|
||||
AutoWearIsLegal(itemId, out alreadyWorn, silent):
|
||||
|
|
@ -113,9 +113,10 @@ AutoWear(itemId, out alreadyWorn, silent):
|
|||
ordinary Use request directly from its inventory identity; it is never sent
|
||||
through the world-approach query. ACE remains authoritative for its spell
|
||||
and consumption outcome.
|
||||
- `SelectionInteractionController` keeps the AD-27 turn/approach seam only for
|
||||
non-owned spatial objects. Owned self, equipped, and nested-container sources
|
||||
have no live world pose and send immediately.
|
||||
- `SelectionInteractionController` sends accepted ordinary Use immediately for
|
||||
both owned and spatial objects. It does not install speculative local
|
||||
TurnToObject/MoveToObject movement; ACE's authoritative movement packets use
|
||||
the shared retail `MoveToManager`.
|
||||
- `AutoWieldController` remains the shared inventory/paperdoll equip owner. It
|
||||
resolves AutoWear blockers from `ClientObjectTable`'s retail-ordered
|
||||
equipment projection and emits the retail literal through system chat.
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
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.
|
||||
`You can only move or use one item at a time` gate from an accepted object/NPC
|
||||
Use through its authoritative completion.
|
||||
|
||||
## Retail oracle
|
||||
|
||||
|
|
@ -44,10 +44,26 @@ There is no retail timeout and movement cancellation is not a substitute for
|
|||
- 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.
|
||||
The connected 2026-07-25 regression trace proved why the old AD-27 Use branch
|
||||
was incorrect: a pending local Ready animation could starve the speculative
|
||||
TurnToObject, so no Use packet ever reached ACE. Retail has no such dependency.
|
||||
`ItemHolder::UseObject` emits `CM_Inventory::Event_UseEvent` first; the event
|
||||
packs and sends opcode `0x36` in `CM_Inventory::Event_UseEvent @ 0x006AC3B0`.
|
||||
Only then does retail call `CPlayerSystem::UsingItem`. ACE owns any required
|
||||
authoritative `CreateMoveToChain`. The Use half of AD-27 is therefore retired;
|
||||
its remaining client completion adaptation applies only to pickup.
|
||||
|
||||
The immediate-send rerun then proved a distinct presentation-owner defect:
|
||||
ACE sent the correct MoveTo and acdream resolved its target, but a login Ready
|
||||
node remained at the head of the local `MotionInterpreter`. During login
|
||||
hydration the player's logical animation runtime remained current while its
|
||||
spatial projection was temporarily withdrawn. The presenter consumed the
|
||||
zero-tick PartArray motion but its spatial-only guard withheld the
|
||||
`MotionDoneTarget`, so the interpreter never popped that node. The correction
|
||||
binds completion to the exact logical live-record animation owner and drains
|
||||
the pre-controller PartArray queue before publishing a candidate interpreter.
|
||||
The connected distant-NPC gate then turned, walked, sent no duplicate Use, and
|
||||
completed through authoritative UseDone.
|
||||
|
||||
## Ported ownership pseudocode
|
||||
|
||||
|
|
@ -62,37 +78,15 @@ RequestUse(object, reservation):
|
|||
reservation.CancelBeforeDispatch()
|
||||
return
|
||||
|
||||
if object is carried or equipped by the player:
|
||||
send Use immediately
|
||||
transfer reservation to UseDone
|
||||
return
|
||||
|
||||
if world identity/approach is invalid:
|
||||
if a non-owned world object is not usable:
|
||||
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
|
||||
|
|
@ -117,10 +111,16 @@ 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`.
|
||||
The 2026-07-25 Alcott trace captured the failure before correction:
|
||||
|
||||
```text
|
||||
[input] UseSelected Press
|
||||
[autowalk-pending] count=1 nodes=[0x41000003/ctx=0/jump=0x0]
|
||||
[autowalk-target] object=0x8000D085 status=Ok ...
|
||||
```
|
||||
|
||||
There was no `[B.4b] use`, no `UseDone`, and therefore no possible
|
||||
authoritative release of the busy reference. Focused tests now pin that close
|
||||
and far world Use both dispatch immediately, never install speculative local
|
||||
movement, never duplicate on a later movement callback, and retain busy
|
||||
ownership until exactly one `UseDone`.
|
||||
|
|
|
|||
|
|
@ -17,12 +17,16 @@ retained-source re-gate is tracked separately below.
|
|||
|
||||
## Retail and protocol anchors
|
||||
|
||||
- `gmSmartBoxUI::UseTime @ 0x004D6E30`: the
|
||||
- `SmartBox::DoSetState` clears `waiting_for_teleport` when the player becomes
|
||||
visible. The next `SmartBox::UseTime` resumes `CObjectMaint` and `CPhysics`
|
||||
while the portal viewport may still be active.
|
||||
- `gmSmartBoxUI::UseTime @ 0x004D6E30`: the later
|
||||
`TunnelFadeOut -> WorldFadeIn` edge hides the portal viewport, shows the
|
||||
world viewport, releases cell blocking, and starts the final one-second
|
||||
WorldFadeIn state.
|
||||
- The later WorldFadeIn completion sends LoginComplete and ends logical
|
||||
teleport state. Viewport release and protocol completion are distinct.
|
||||
world viewport, releases the destination render reservation, and starts the
|
||||
final one-second WorldFadeIn state.
|
||||
- WorldFadeIn completion sends LoginComplete and ends logical teleport state.
|
||||
Materialization, viewport release, and protocol completion are three
|
||||
distinct edges.
|
||||
- `gmPaperDollUI::RedressCreature @ 0x004A3BC0` creates the private inventory
|
||||
object only when absent and a SmartBox player is available; temporary player
|
||||
absence does not delete an existing private object.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue