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
|
|
@ -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