fix(streaming): preserve portal destination ownership
Detach old-world spatial ownership atomically, prioritize destination retirement dependencies, and reveal the viewport at the retail transition edge. Give private paperdoll views independent mesh ownership and retain dormant ACE entities so portal revisits preserve server objects without extending active GPU lifetimes.
This commit is contained in:
parent
2c848d4167
commit
823936ec31
57 changed files with 2551 additions and 324 deletions
|
|
@ -452,6 +452,13 @@ 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.
|
||||
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
|
||||
`KnownObjects` and omit a later CreateObject on revisit. A matching landblock
|
||||
load re-enters the ordinary timestamp- and generation-gated hydration path;
|
||||
F747, a newer incarnation, or session reset removes the cold snapshot. Dormant
|
||||
records own no frame-time, physics, render, effect, audio, or GPU resource.
|
||||
`LiveEntityPresentationController` drains those accepted transitions only after
|
||||
the renderer/effect owner is ready: Hidden suppresses the retained root's mesh,
|
||||
collision, interaction, radar, and target eligibility while preserving the
|
||||
|
|
@ -735,9 +742,14 @@ liveness, and spatial reconciliation stop; world-space audio is silenced; UI,
|
|||
network/event/command delivery, portal presentation, destination streaming, and
|
||||
readiness continue. Canonical live records remain retained until physical
|
||||
teardown converges, so quiescence neither reconstructs server identity nor
|
||||
invents Hidden/UnHide transitions. Full-window and shared-origin retirement
|
||||
advance only from `StreamingController.Tick` through stable resident and
|
||||
per-owner entity/stage cursors on the frame's one meter.
|
||||
invents Hidden/UnHide transitions. A shared-origin recenter atomically removes
|
||||
the complete old spatial generation and captures one exact receipt per
|
||||
landblock before the origin changes. Expensive script, physics, render, and GL
|
||||
release then advances only from `StreamingController.Tick` through stable
|
||||
per-owner entity/stage cursors on the frame's one meter. Publication remains
|
||||
fenced only by an older receipt for the same canonical landblock; the active
|
||||
destination's exact receipt may advance ahead of unrelated cleanup without
|
||||
reordering the remaining FIFO.
|
||||
|
||||
Accepted publication is likewise retained at its exact priority-queue head.
|
||||
`LandblockPresentationPipeline` meters render, physics, static, building, and
|
||||
|
|
@ -767,9 +779,14 @@ The Slice E connected closeout is recorded in
|
|||
`WorldRevealCoordinator` is the single App-layer owner of each login or portal
|
||||
reveal lifetime. It composes the canonical `WorldRevealReadinessBarrier` with
|
||||
generation-scoped lifecycle observation and the streaming destination
|
||||
reservation, so begin, preparation, readiness, materialization, world
|
||||
visibility, completion, cancellation, and reserved capacity cannot be wired
|
||||
independently. The barrier joins `StreamingController`/`GpuWorldState`
|
||||
reservation plus a generation-scoped render-resource profile, so begin,
|
||||
preparation, readiness, materialization, viewport release, protocol completion,
|
||||
cancellation, and reserved capacity cannot be wired independently. While the
|
||||
normal world viewport is withheld, mesh and composite upload owners may admit
|
||||
more small destination items per frame, but retain the ordinary 8 MiB byte,
|
||||
array, buffer, and mipmap ceilings. The exact profile ends at the retail
|
||||
portal-to-world viewport swap; it does not reduce the readiness radius or
|
||||
reveal incomplete content. The barrier joins `StreamingController`/`GpuWorldState`
|
||||
static-mesh publication, `WbDrawDispatcher` composite-texture warmup, and
|
||||
`PhysicsEngine` destination residency. Login's sky-only gate and portal-space
|
||||
transit consume the same predicate; they differ only in presentation. There is
|
||||
|
|
@ -822,6 +839,12 @@ and before retained UI. The shared dispatcher is reset to no world-cell clips
|
|||
and no world point lights for this pass. Chat, gameplay windows, toolbar,
|
||||
cursor, and input continue normally above it. The synthetic object never
|
||||
enters `LiveEntityRuntime`, collision, picking, radar, or server GUID state.
|
||||
Portal, paperdoll, and creature-appraisal viewports share
|
||||
`SyntheticEntityMeshReferenceOwner` but own independent leases. World
|
||||
landblock retirement therefore cannot evict a mesh still used by retained UI.
|
||||
The paperdoll keeps its last successful private object while the SmartBox
|
||||
player is temporarily unavailable and clears it only at the character-session
|
||||
boundary, matching `gmPaperDollUI::RedressCreature`.
|
||||
|
||||
`TeleportViewPlaneController` is the App-layer projection adapter for retail
|
||||
`SmartBox::SetOverrideFovDistance` / `Render::set_vdst`. It captures the active
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| AP-65 | **PickupEvent (0xF74A) no longer evicts the weenie from `ClientObjectTable`** — only `DeleteObject (0xF747)` evicts, matching the retail `object_table`-vs-`weenie_object_table` split. An item another player picks up near you (only ever gets `PickupEvent`, never `DeleteObject`) lingers as a data-only entry (`ContainerId 0`, not in any view) until teleport/relog `Clear()`. | `src/AcDream.Core.Net/ObjectTableWiring.cs` (EntityDeleted handler); `src/AcDream.Core.Net/WorldSession.cs` (PickupEvent branch) | The weenie entry for nearby pickups is a harmless data ghost — no UI shows it (no container, not wielded). Retail evicts it from `weenie_object_table` when the object fully leaves interest range via `DeleteObject`; acdream defers to teleport/relog clear. | Slight memory growth in long sessions if many world items are picked up by other players near you; item data ghosts cannot cause functional issues because no UI queries `ContainerId 0`. | `CACObjectMaint::DeleteObject` / `SmartBox::HandleDeleteObject`; ACE `Player_Inventory.cs TryDequipObjectWithNetworking` |
|
||||
| ~~AP-66~~ | **RETIRED 2026-07-13 — authored paperdoll empty-slot presentation.** The earlier “no silhouettes” conclusion inspected the ItemList elements' own media but missed `UIElement_ItemList::InternalCreateItem`, which clones a distinct `UIElement_UIItem` catalog prototype for each location. All 21 supported jewelry, weapon, ammo, shield, clothing, cloak, trinket, and armor lists now resolve their exact `ItemSlot_Empty` surface from live DAT; `PostInit` confirms non-armor lists remain visible while the nine armor lists toggle with Slots. | `src/AcDream.App/UI/Layout/PaperdollSlotBackgrounds.cs`; `ItemListCellTemplate.cs`; `PaperdollController.cs` | — | — | `gmPaperDollUI::GetLocationInfoFromElementID @ 0x004A37F0`; `PostInit @ 0x004A5360`; `UIElement_ItemList::InternalCreateItem @ 0x004E3570`; `LayoutDesc 0x21000037` |
|
||||
| AP-68 | acdream keeps the 128 nearest-to-CAMERA point lights live (`MaxGlobalLights=128`, `BuildPointLightSnapshot`) and selects per cell CAMERA-INDEPENDENTLY (by the cell's own bounds), so a building interior stays lit at any distance within a town; retail keeps only the 40 nearest-to-PLAYER static lights (`Render::max_static_lights=0x28`, distance-sorted replace-farthest `insert_light`) and re-bakes a cell when its live light set changes, so distant interiors are baked dark and "light up" only as the player approaches and their torches enter the live 40. INTENTIONAL — acdream's always-lit interiors are the preferred behavior (no 1999-era light-budget pop-in); user-confirmed 2026-06-20. | `src/AcDream.Core/Lighting/LightManager.cs` (`MaxGlobalLights=128`, `BuildPointLightSnapshot`, `SelectForObject`) | The retail pop-in is a fixed-function light-budget artifact, not an intended aesthetic; revert to retail by clamping the global set to 40 + distance-to-player sort if ever desired | Distant town interiors are lit in acdream where retail's are dark until approached — a deliberate, user-preferred divergence | `Render::max_static_lights` 0x28; `insert_light` 0x0054d1b0 (distance-sorted, replace-farthest); bake re-trigger `SetStaticLightingVertexColors` cache `burnedInStaticLights != num_static_lights` |
|
||||
| AP-69 | acdream now preserves one accepted live record across rebucketing and ports retail's 25-second leave-visibility destruction lifecycle. Spatially resident records cancel expiry; otherwise the ACE compatibility boundary uses holtburger's conservative 384-unit distance envelope and retains attached/container/wielder/parent-owned objects. Expiry routes through the exact generation-safe F747 teardown. DIVERGENCE: the fallback does not yet derive visibility from retail/ACE ObjCell PVS (`SeenOutside` plus `VisibleCells`), and trade/container preview retention has no separate lifecycle flag. | `src/AcDream.App/World/LiveEntityRuntime.cs`; `src/AcDream.App/World/LiveEntityLivenessController.cs`; `src/AcDream.App/Streaming/GpuWorldState.cs`; `LiveEntityHydrationController.OnPrune` | Prevents stale portal destinations from accumulating animation/effect/render owners while retaining loaded long-view objects and canonical identity | A nonresident object outside 384 units that remains visible through an unusual long EnvCell PVS could expire early; a future preview-only object with no parent/container ownership could also expire. Replace the compatibility predicate when exact ObjCell PVS and preview lifetimes are available | `CPhysicsObj::prepare_to_leave_visibility` 0x00511F40; `CPhysicsObj::prepare_to_enter_world` 0x00511FA0; `CObjectMaint::AddObjectToBeDestroyed` 0x00508F70; `CObjectMaint::UseTime` 0x005089B0; `docs/research/2026-07-18-retail-object-liveness-and-mesh-reclamation-pseudocode.md` |
|
||||
| AP-69 | acdream preserves one accepted active record across rebucketing and ports retail's 25-second leave-visibility destruction lifecycle. Spatially resident records cancel expiry; otherwise the ACE compatibility boundary uses holtburger's conservative 384-unit distance envelope and retains attached/container/wielder/parent-owned objects. Expiry uses the exact generation-safe active teardown, then retains only a cold `EntitySpawn` because ACE can keep the GUID in `KnownObjects` and omit CreateObject on revisit; explicit F747/new generation/session reset removes it. DIVERGENCE: retail can delete the complete object under its visibility protocol; the fallback does not yet derive visibility from retail/ACE ObjCell PVS (`SeenOutside` plus `VisibleCells`), and trade/container preview retention has no separate lifecycle flag. | `src/AcDream.App/World/LiveEntityRuntime.cs`; `src/AcDream.App/World/LiveEntityLivenessController.cs`; `src/AcDream.App/World/DormantLiveEntityStore.cs`; `LiveEntityHydrationController.OnPrune` | Prevents stale portal destinations from accumulating animation/effect/render owners while still allowing doors, signs, portals, and other ACE-known objects to rematerialize when the server does not resend them | Dormant data-only snapshots can grow with every unique ACE destination until F747 or session reset; a nonresident object outside 384 units that remains visible through an unusual long EnvCell PVS could expire early; a future preview-only object with no parent/container ownership could also expire. Replace the compatibility predicate when exact ObjCell PVS and preview lifetimes are available | `CPhysicsObj::prepare_to_leave_visibility` 0x00511F40; `CPhysicsObj::prepare_to_enter_world` 0x00511FA0; `CObjectMaint::AddObjectToBeDestroyed` 0x00508F70; `CObjectMaint::UseTime` 0x005089B0; ACE `KnownObjects`; `docs/research/2026-07-18-retail-object-liveness-and-mesh-reclamation-pseudocode.md` |
|
||||
| AP-71 | **`CEnvCell::find_env_collisions` omits the `CObjCell::check_entry_restrictions` gate** — retail's `CEnvCell::find_env_collisions` (pc:309576) calls `CObjCell::check_entry_restrictions` (pc:309576) FIRST and returns `COLLIDED` when an access-locked cell's `restriction_obj` entity rejects the mover (`CanMoveInto` fails AND `CanBypassMoveRestrictions` is false). acdream's `FindEnvCollisions` (`src/AcDream.Core/Physics/TransitionTypes.cs:2088`) goes straight to BSP collision. | `src/AcDream.Core/Physics/TransitionTypes.cs:2088` | **No access-restriction cell data exists in acdream.** `CellPhysics` (`src/AcDream.Core/Physics/PhysicsDataCache.cs:540`) has no `restriction_obj` field; `DatReaderWriter` does not model per-cell access locks; no weenie-object-table exists on the client for the gate's `CanMoveInto` / `CanBypassMoveRestrictions` dispatch. The gap is **inert** in all dev content (ACE starter area has no access-locked env cells). | If access-locked dungeon cells are ever modeled (dat + wire), the player will walk through the restriction barrier without being blocked — wrong PK/housing gating. | `CObjCell::check_entry_restrictions` pc:309576; `CEnvCell::find_env_collisions` pc:309573–309597 |
|
||||
| AP-72 | **Cursor art falls back to OS standard cursors when dat resolution fails** — retail always renders MediaDescCursor / EnumIDMap-resolved dat cursor art; acdream's `RetailCursorManager.Apply` falls back to Silk `StandardCursor` (IBeam/crosshair/not-allowed/…) when the EnumIDMap chain or RenderSurface decode fails, and `RetailCursorResolver`/`RetailCursorManager` permanently negative-cache the failed enum/surface id for the session. | `src/AcDream.App/Rendering/RetailCursorManager.cs:47` (`ApplyStandard`), `RetailCursorResolver.cs:47` (negative cache) | Fallback triggers only when the dat lacks the asset — nominal EoR dats always resolve the 0x27/0x28/0x29 chain; an OS cursor keeps the UI usable rather than showing nothing. | A dat-read or decode regression silently shows OS-native cursors instead of surfacing an error — masked failure class; check the `[D.2b]` cursor log lines before suspecting art. | `ClientUISystem::UpdateCursorState` 0x00564630 |
|
||||
| AP-74 | **UseDone WeenieError text comes from a hardcoded subset map, not the portal String tables** — retail resolves the 0x01C7 UseDone error code through the client String tables into the canonical line ("You are not trained in healing!"); acdream's `WeenieErrorText.For` hardcodes the handful of codes the current use/heal flows produce (0x001D/0x04EB/0x04FC/0x04FE, texts phrased after the ACE enum names) with a generic code-carrying fallback. | `src/AcDream.Core.Net/Messages/WeenieErrorText.cs` | Every refusal is now visible; only unmapped wording deviates, and those lines retain the raw code. Retire by porting the String-table lookup (#202). | An unmapped WeenieError shows a generic line instead of retail's exact sentence | retail String-table error lookup; ACE `WeenieError.cs` values |
|
||||
|
|
|
|||
|
|
@ -1632,8 +1632,11 @@ port in any phase — no separate listing here.
|
|||
> shadow referee, production cutover, and acceptance matrix. The user
|
||||
> explicitly approved Slices F–L, including the F/G and J gates, on
|
||||
> 2026-07-24. Slice F and G0–G4 are implemented; 46,599 automated G4
|
||||
> comparisons have zero mismatches. Its physical-display visual gate and G5
|
||||
> old-path deletion remain pending, with exact rollback
|
||||
> comparisons have zero mismatches. The 2026-07-25 connected visual gate
|
||||
> accepted the retained scene after independent portal scheduling/private-view/
|
||||
> ACE-revisit ownership regressions were corrected; G5 old-path deletion and
|
||||
> production-only profiling are now active. The historical exact cutover
|
||||
> rollback remains
|
||||
> `git revert ef1d263337997bb030eadb7b8e71d73dc659907a`. Slice H is complete:
|
||||
> retained UI/frame work, exact bounded light selection, and
|
||||
> pooled/borrowed/direct network I/O pass 8,292 Release tests / 5 skips and the
|
||||
|
|
|
|||
|
|
@ -108,13 +108,15 @@ publication, retirement, upload, warmup, or resource backlog. Capped/uncapped
|
|||
CPU p99 is 34.7%/30.4% below Slice A and maximum frame allocation is
|
||||
84.7%/82.2% lower. The user explicitly approved Slices F–L, including both
|
||||
the F/G ECS and Slice J gameplay-owner gates, on 2026-07-24. Slice F and
|
||||
G0–G4 are implemented; G4's automated gate has zero mismatches while its
|
||||
physical-display visual gate and G5 deletion remain pending. Slice H is
|
||||
G0–G4 are implemented; G4's automated gate has zero mismatches and its
|
||||
2026-07-25 connected visual gate passes after the portal scheduling/private
|
||||
viewport/ACE revisit regression correction. G5 deletion and production-only
|
||||
profiling are active. Slice H is
|
||||
complete: retained UI/frame work, exact bounded light selection, and
|
||||
pooled/borrowed/direct network I/O pass 8,292 Release tests / 5 skips and the
|
||||
seven-checkpoint connected lifecycle/reconnect gate. Slice I is active under
|
||||
[`2026-07-25-modern-runtime-slice-i.md`](2026-07-25-modern-runtime-slice-i.md).
|
||||
The exact G4 visual rollback remains
|
||||
The historical exact G4 visual rollback remains
|
||||
`git revert ef1d263337997bb030eadb7b8e71d73dc659907a`.
|
||||
|
||||
Slice 1's 18-cell favorite-spell overflow bar is user-accepted. Slice 2's
|
||||
|
|
|
|||
|
|
@ -423,6 +423,40 @@ frame allocation fell 84.7%/82.2%. Relative to Slice C, CPU p99 improved
|
|||
21.4%/16.1% and largest frame allocation fell 33.5%/9.7%. Full evidence:
|
||||
[`../research/2026-07-24-slice-e-cost-budgeted-streaming-report.md`](../research/2026-07-24-slice-e-cost-budgeted-streaming-report.md).
|
||||
|
||||
### Post-closeout portal regression correction — 2026-07-25
|
||||
|
||||
The first retained-scene visual pass exposed four scheduling/ownership defects
|
||||
that the original E6 route did not isolate:
|
||||
|
||||
- a 25x25 shared-origin window admitted 625 detach operations over many
|
||||
frames even though no intermediate old spatial subset was observable;
|
||||
- destination publication could sit behind an unrelated retirement receipt,
|
||||
small-mesh upload count, or composite upload count while most byte budget
|
||||
remained unused;
|
||||
- private paperdoll/appraisal meshes borrowed world ownership and the
|
||||
paperdoll discarded its private object during temporary player
|
||||
unavailability;
|
||||
- ACE could retain an expired GUID in `KnownObjects`, omit CreateObject on
|
||||
revisit, and leave doors, signs, portals, and NPCs permanently absent.
|
||||
|
||||
The correction atomically swaps the old spatial generation into exact deferred
|
||||
receipts, advances only the destination's same-key receipt out of order, and
|
||||
orders destination worker/publication work first. A reveal-generation render
|
||||
profile raises only the small-object mesh count from 8 to 64 and composite
|
||||
count from 16 to 64; all 8 MiB byte/array/buffer/mipmap ceilings remain
|
||||
unchanged. Ordinary work still advances when no destination completion exists.
|
||||
Private viewports now own independent mesh leases and the paperdoll survives
|
||||
temporary SmartBox absence. `DormantLiveEntityStore` retains only cold accepted
|
||||
spawn data after active teardown so an ACE revisit can hydrate through the
|
||||
ordinary generation-safe transaction.
|
||||
|
||||
The world-availability edge now ends at retail's portal/world viewport swap;
|
||||
the one-second `WorldFadeIn` protocol tail still sends LoginComplete and closes
|
||||
teleport state later. The user confirmed faster portal transit, continuous
|
||||
paperdoll presentation, and persistent server-spawned objects after repeated
|
||||
portal travel. Full evidence:
|
||||
[`../research/2026-07-25-portal-regression-closeout.md`](../research/2026-07-25-portal-regression-closeout.md).
|
||||
|
||||
## 10. Acceptance
|
||||
|
||||
Slice E closes only when:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Date:** 2026-07-24
|
||||
|
||||
**Status:** approved 2026-07-24; Slice F complete, Slice G active
|
||||
**Status:** approved 2026-07-24; Slice F and G4 complete, G5 active
|
||||
|
||||
**Program:** `docs/plans/2026-07-24-modern-runtime-architecture.md`
|
||||
|
||||
|
|
@ -27,8 +27,8 @@ retail-faithful gameplay
|
|||
| G1 — scene-query replacement | complete | Exact `e346f8bb` passed capped and uncapped nine-stop routes plus uncapped dense Arwic: 29,392 + 29,025 + 1,522 same-frame candidate comparisons, zero mismatches, equal counts/digests at every checkpoint, exact binary/source identity, and graceful shutdown. Evidence: `docs/research/2026-07-25-slice-g1-scene-query-candidate-gate.md`. |
|
||||
| G2 — packed dispatcher input | complete | Exact `f9829d5f` passed capped and uncapped nine-stop routes plus dense Arwic. Candidate order, packed input, classified output, and selection each completed 16,879 + 17,282 + 770 comparisons with zero mismatch; journal/rejection counts were zero and shutdown graceful. Evidence: `docs/research/2026-07-25-slice-g2-packed-dispatcher-gate.md`. |
|
||||
| G3 — retained classification/storage | complete | Exact `6a026c5a` passed capped and uncapped nine-stop routes plus dense Arwic with 17,069 + 16,827 + 757 exact comparisons in every channel, zero mismatch, warm cross-frame reuse, and graceful shutdown. Evidence: `docs/research/2026-07-25-slice-g3-retained-classification-gate.md`. |
|
||||
| G4 — production cutover | automated gate complete; visual gate pending | Exact cutover `ef1d263337997bb030eadb7b8e71d73dc659907a` makes the retained frame product the production entity source at the existing five retail PView stages. Exact `03b10183` passed capped/uncapped nine-stop and uncapped dense-Arwic automation with 46,599 total product comparisons, zero mismatch, every reveal ready, and graceful shutdown. The old route referee remains until the user visual gate passes. Evidence: `docs/research/2026-07-25-slice-g4-production-cutover-automated-gate.md`. |
|
||||
| G5 | pending | Physical performance/lifecycle closeout follows the user visual gate. |
|
||||
| G4 — production cutover | complete | Exact cutover `ef1d263337997bb030eadb7b8e71d73dc659907a` makes the retained frame product the production entity source at the existing five retail PView stages. Exact `03b10183` passed capped/uncapped nine-stop and uncapped dense-Arwic automation with 46,599 total product comparisons and zero mismatch. The 2026-07-25 connected visual pass accepted the retained scene after its independent portal scheduling/ownership regressions were corrected. Evidence: `docs/research/2026-07-25-slice-g4-production-cutover-automated-gate.md` and `docs/research/2026-07-25-portal-regression-closeout.md`. |
|
||||
| G5 | active | The accepted visual gate unblocks production-only performance/lifecycle closeout and old-route deletion. |
|
||||
|
||||
The exact pre-F/G runtime rollback anchor remains `e7d9d6fa`. F0 is
|
||||
non-drawing diagnostic infrastructure and therefore is not a visual-cutover
|
||||
|
|
@ -646,9 +646,12 @@ the scene journal was empty, source and binary hashes matched, and every
|
|||
process closed gracefully. The gate corrected whole-retained-world composite
|
||||
warmup discovery by sourcing the exact published 3×3 destination neighborhood;
|
||||
that correction is draw-path independent. Fixed-camera artifacts are captured.
|
||||
The user visual gate is the only remaining G4 acceptance item, so old-route
|
||||
deletion and G5 production-only profiling remain blocked. Evidence:
|
||||
`docs/research/2026-07-25-slice-g4-production-cutover-automated-gate.md`.
|
||||
The user completed the connected visual gate on 2026-07-25. The retained scene
|
||||
itself remained exact; the pass exposed independent portal scheduling, private
|
||||
viewport ownership, and ACE revisit-liveness defects. Their root-cause
|
||||
correction is recorded in
|
||||
`docs/research/2026-07-25-portal-regression-closeout.md`. G4 is accepted and
|
||||
old-route deletion plus G5 production-only profiling are unblocked.
|
||||
|
||||
### G5 — Physical performance and lifecycle gate
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
**Status:** COMPLETE (2026-07-25)
|
||||
**Parent plan:** `2026-07-24-modern-runtime-architecture.md`, Slice H
|
||||
**Prerequisite state:** Slice G4 production cutover is automated-gated; its
|
||||
user visual gate remains pending.
|
||||
**Prerequisite state at execution:** Slice G4 production cutover was
|
||||
automated-gated; its user visual gate later passed on 2026-07-25.
|
||||
|
||||
## 1. Safety boundary
|
||||
|
||||
|
|
@ -11,8 +11,8 @@ Slice H removes work which scales with presentation rate. It does not change
|
|||
retail gameplay rules, visible-cell authority, light membership, packet
|
||||
ordering, acknowledgement timing, heartbeat placement, or UI presentation.
|
||||
|
||||
The G4 old-route referee remains available until the user accepts the visual
|
||||
gate. Slice H must not delete or alter that rollback boundary.
|
||||
Slice H did not delete or alter the G4 old-route referee while its visual gate
|
||||
was pending. That gate has since passed; G5 owns the final deletion boundary.
|
||||
|
||||
If the G4 visual result is wrong, revert only:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Modern runtime Slice I — flat collision assets and zero-allocation physics
|
||||
|
||||
**Status:** I0 COMPLETE — retail oracle, representative graph fixtures, and
|
||||
four-mover allocation baselines fixed; I1 reusable scratch is next
|
||||
four-mover allocation baselines fixed; accepted G5 closeout now precedes I1
|
||||
**Parent:** `2026-07-24-modern-runtime-architecture.md`, Slice I
|
||||
**Purpose:** remove parsed DAT object graphs and steady collision allocations
|
||||
without changing one retail collision decision, float, comparison, or traversal
|
||||
|
|
|
|||
|
|
@ -55,6 +55,15 @@ The portal viewport is a replacement for the world viewport. It is not a
|
|||
full-screen UI overlay: normal retained UI continues to update and draw above
|
||||
it.
|
||||
|
||||
Private retained-UI viewports also keep their own objects. In particular,
|
||||
`gmPaperDollUI::RedressCreature @ 0x004A3BC0` creates
|
||||
`m_pInventoryObject` only when it is absent and the SmartBox player is
|
||||
available; a temporarily unavailable world player does not clear an already
|
||||
constructed inventory object. `gmPaperDollUI::~gmPaperDollUI @ 0x004A3590`
|
||||
removes that private object at the UI/session lifetime boundary. Therefore a
|
||||
portal-space world quiescence may defer a paperdoll redress, but it must not
|
||||
empty and later reconstruct the visible private paperdoll.
|
||||
|
||||
## Begin/end and scene visibility
|
||||
|
||||
```text
|
||||
|
|
@ -96,6 +105,36 @@ distance, hides the portal viewport, shows the world viewport, clears the
|
|||
teleport object's sequence animations, plays `Sound_UI_ExitPortal`, and enters
|
||||
`WorldFadeIn`. That following state restores the ordinary game view distance.
|
||||
|
||||
The viewport swap is also the end of the destination-cell blocking edge, not
|
||||
the end of the complete teleport protocol:
|
||||
|
||||
```text
|
||||
TunnelFadeOut completes:
|
||||
destination cells are already available
|
||||
release SmartBox world blocking
|
||||
hide portal viewport
|
||||
show world viewport
|
||||
enter WorldFadeIn
|
||||
|
||||
WorldFadeIn completes one second later:
|
||||
send LoginComplete
|
||||
clear teleport-in-progress
|
||||
enter Off
|
||||
```
|
||||
|
||||
The two edges must remain distinct in an asynchronous client. Holding world
|
||||
blocking until `LoginComplete` creates a one-second clear-color/void interval
|
||||
after the tunnel viewport has already been hidden. Conversely, marking the
|
||||
whole teleport complete at the viewport swap loses retail's final
|
||||
`WorldFadeIn` protocol state.
|
||||
|
||||
The asynchronous renderer may temporarily spend otherwise-idle resource
|
||||
capacity on the withheld destination, but that policy belongs to the exact
|
||||
reveal generation. Acdream raises only the count of small mesh/composite
|
||||
uploads while keeping the ordinary byte, array, buffer, and mipmap ceilings.
|
||||
The profile ends at the viewport swap above. It never changes the required
|
||||
destination radius, the readiness predicate, or the resulting pixels.
|
||||
|
||||
## Camera motion
|
||||
|
||||
The camera rolls around its own AC `+Y` forward axis. It does not yaw around
|
||||
|
|
|
|||
|
|
@ -59,12 +59,35 @@ preview-owned objects. Sources:
|
|||
- [ACE Player teleport tracking](https://github.com/ACEmulator/ACE/blob/650c5b75ae909957feaf58db320e46be16502653/Source/ACE.Server/WorldObjects/Player_Tracking.cs)
|
||||
- [Holtburger liveness](https://github.com/merklejerk/holtburger/blob/main/crates/holtburger-world/src/state/liveness.rs)
|
||||
|
||||
acdream uses the same compatibility boundary: currently spatially resident
|
||||
acdream uses the same visibility boundary: currently spatially resident
|
||||
records are visible; otherwise a 384-unit 3-D global-landblock distance is the
|
||||
conservative fallback. The distance is an adaptation, not a claimed retail
|
||||
constant, and remains documented in divergence AP-69 until exact ObjCell/PVS
|
||||
membership drives the deadline.
|
||||
|
||||
There is an additional ACE compatibility boundary. Retail can destroy the
|
||||
complete client object because its server/client visibility protocol later
|
||||
recreates that object. ACE may retain the GUID in `KnownObjects` across a
|
||||
normal teleport and consequently omit the later `CreateObject`. Deleting
|
||||
acdream's only accepted `EntitySpawn` at the 25-second deadline therefore
|
||||
made doors, signs, portals, NPCs, and other server objects permanently absent
|
||||
when a destination was revisited.
|
||||
|
||||
The modern client separates those two costs:
|
||||
|
||||
- the 25-second deadline destroys every active projection, collision,
|
||||
animation, physics, script, light, particle, and render owner;
|
||||
- a cold `EntitySpawn` snapshot remains in a data-only dormant table;
|
||||
- reloading its authoritative landblock routes that snapshot through the
|
||||
ordinary generation-safe CreateObject hydration transaction;
|
||||
- an explicit F747, a newer INSTANCE timestamp, or session reset removes the
|
||||
dormant snapshot.
|
||||
|
||||
This is deliberately not active-object retention: dormant entries execute no
|
||||
per-frame work and own no GPU resources. It restores the former retained
|
||||
spawn-table contract required by ACE while preserving the resource reclamation
|
||||
that fixed the portal-run FPS collapse.
|
||||
|
||||
## Port pseudocode
|
||||
|
||||
```text
|
||||
|
|
@ -93,14 +116,30 @@ once per second:
|
|||
for each due candidate:
|
||||
re-read canonical record
|
||||
if GUID and generation still match:
|
||||
route through the normal accepted DeleteObject lifecycle
|
||||
capture its latest accepted EntitySpawn
|
||||
run the normal generation-safe active teardown
|
||||
retain only the cold EntitySpawn
|
||||
|
||||
when a landblock becomes resident:
|
||||
for each dormant spawn in that canonical landblock:
|
||||
route it through ordinary CreateObject hydration
|
||||
remove the dormant entry only after the generation is accepted
|
||||
|
||||
on explicit DeleteObject:
|
||||
delete the exact active or dormant generation
|
||||
remove its ClientObject qualities
|
||||
|
||||
on session reset:
|
||||
clear active runtime and dormant snapshots
|
||||
```
|
||||
|
||||
Routing expiry through `LiveEntityRuntime.UnregisterLiveEntity` is important:
|
||||
it performs the same generation gate, object-table removal, equipped-child
|
||||
cleanup, spatial withdrawal, animation/physics/effect teardown, and render
|
||||
resource release as an authoritative F747. A second teardown path would drift
|
||||
and recreate the original leak.
|
||||
it performs the same generation gate, equipped-child cleanup, spatial
|
||||
withdrawal, animation/physics/effect teardown, and render resource release as
|
||||
an authoritative F747. Unlike F747, local visibility expiry preserves the
|
||||
small object-quality record and cold spawn because ACE may never resend them.
|
||||
An actual F747 removes both. A second active-resource teardown path would
|
||||
drift and recreate the original leak.
|
||||
|
||||
## Reclaimable shared-buffer contract
|
||||
|
||||
|
|
|
|||
|
|
@ -172,8 +172,11 @@ begin destination generation:
|
|||
silence old-generation audio
|
||||
reject old generation from collision, picking, radar, and targeting
|
||||
|
||||
at the shared-origin transaction boundary:
|
||||
atomically swap the complete old spatial generation out of reach
|
||||
retain one exact teardown receipt per old landblock
|
||||
|
||||
over later update/resource-maintenance frames:
|
||||
detach old landblocks through exact retry receipts
|
||||
release scripts/effects/lights/plugin projections
|
||||
release CPU owners
|
||||
admit renderer retirement; physical GL release remains fence delayed
|
||||
|
|
@ -200,6 +203,48 @@ Release-later never means run-later: an old owner may retain memory until its
|
|||
cursor runs, but it cannot keep simulating, colliding, rendering, targeting, or
|
||||
emitting audible work after the generation-quiesce edge.
|
||||
|
||||
The shared-origin commit barrier is the completion of **spatial detachment**,
|
||||
not completion of every deferred resource release:
|
||||
|
||||
```text
|
||||
capture exact old landblock/entity teardown receipts
|
||||
clear the old loaded/pending/traversal/collision-visible spatial indexes
|
||||
repark nonpersistent live projections by their canonical landblock
|
||||
rescue the persistent local-player projection
|
||||
publish the resulting live visibility edges once
|
||||
|
||||
once that single spatial-generation swap commits:
|
||||
recenter the shared coordinate origin
|
||||
configure and enqueue the destination window
|
||||
|
||||
on later frames:
|
||||
destination publication uses its reserved work lane
|
||||
old retirement receipts use remaining cleanup capacity
|
||||
a publication for landblock X waits only while X has an older receipt
|
||||
the exact receipt for destination X may advance ahead of unrelated FIFO
|
||||
ordinary publication continues when no destination result is available
|
||||
```
|
||||
|
||||
The per-landblock publication fence makes key-based terrain, physics,
|
||||
EnvCell, building, mesh-reference, and static-presentation cleanup safe: a
|
||||
late old receipt cannot remove a replacement owner with the same landblock
|
||||
key. Live objects are different. Their logical owner survives spatial
|
||||
withdrawal and may be reprojected immediately at the destination, so deferred
|
||||
landblock retirement must never clear a live object's lighting or
|
||||
translucency by reusable local ID. Live cell-scoped lighting is withdrawn by
|
||||
the incarnation-aware projection-visibility edge; logical translucency stays
|
||||
with the live record until its actual teardown. Landblock receipts clean only
|
||||
DAT-static entity presentation.
|
||||
|
||||
This swap is deliberately one **atomic oversized progress operation** in the
|
||||
modern frame meter. Splitting it into 625 budgeted landblock detach admissions
|
||||
created a five-second portal stall even though the old generation was already
|
||||
quiesced and no intermediate subset was observable. The atomic work scans the
|
||||
small live-projection index, clears the old spatial maps, and transfers their
|
||||
existing entity lists into receipts; it does not synchronously destroy every
|
||||
static resource. Expensive script, renderer, physics, light, plugin, and GL
|
||||
cleanup still advances through the ordinary retryable receipt cursors.
|
||||
|
||||
## Port constraints
|
||||
|
||||
1. Preserve existing exact retry receipts; successful stages never replay.
|
||||
|
|
|
|||
105
docs/research/2026-07-25-portal-regression-closeout.md
Normal file
105
docs/research/2026-07-25-portal-regression-closeout.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# Portal regression closeout — 2026-07-25
|
||||
|
||||
## Scope
|
||||
|
||||
The first connected retained-scene visual pass reported:
|
||||
|
||||
1. portal transit remained in the tunnel too long;
|
||||
2. a clear/void interval appeared between tunnel exit and destination world;
|
||||
3. the inventory paperdoll disappeared and rebuilt during transit;
|
||||
4. doors, signs, portals, NPCs, and other server-spawned objects disappeared
|
||||
after repeated portal travel.
|
||||
|
||||
This note records the root causes and the accepted correction. It does not
|
||||
reduce world radius, texture quality, particles, readiness, or retail
|
||||
presentation.
|
||||
|
||||
## Retail and protocol anchors
|
||||
|
||||
- `gmSmartBoxUI::UseTime @ 0x004D6E30`: the
|
||||
`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.
|
||||
- `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.
|
||||
- `gmPaperDollUI::~gmPaperDollUI @ 0x004A3590` owns final private-object
|
||||
destruction.
|
||||
- `CPhysicsObj::prepare_to_leave_visibility @ 0x00511F40`,
|
||||
`CObjectMaint::AddObjectToBeDestroyed @ 0x00508F70`, and
|
||||
`CObjectMaint::UseTime @ 0x005089B0` define retail's delayed active-object
|
||||
destruction.
|
||||
- `CPhysicsObj::set_hidden @ 0x00514C60` plays typed UnHide at intensity 1
|
||||
when materializing. The destination purple/pink effect is DAT-driven retail
|
||||
behavior, not a generic exit effect to suppress.
|
||||
|
||||
## Root causes and corrections
|
||||
|
||||
### Shared-origin retirement head-of-line blocking
|
||||
|
||||
The 25x25 old spatial window was detached as hundreds of independently metered
|
||||
operations. No intermediate subset was observable because the complete old
|
||||
generation was already quiesced, but the shared origin could not change until
|
||||
all admissions finished.
|
||||
|
||||
`GpuWorldState.DetachAllForOriginRecenter` now performs one atomic spatial
|
||||
generation swap and returns exact per-landblock teardown receipts. Deferred
|
||||
resource release remains budgeted. A destination publication waits only on an
|
||||
older receipt for that same landblock; that exact receipt may advance ahead of
|
||||
unrelated cleanup while the remaining retirement FIFO order stays unchanged.
|
||||
|
||||
### Destination scheduling and upload utilization
|
||||
|
||||
Near worker jobs, retained publication receipts, mesh uploads, and composite
|
||||
uploads could each serialize destination work behind unrelated work. The
|
||||
destination neighborhood is now ordered first at worker and publication
|
||||
boundaries. When a destination completion actually exists, ordinary
|
||||
publication is temporarily preempted; when it does not exist or failed,
|
||||
ordinary Near/Far work continues under the same budget.
|
||||
|
||||
`WorldRevealRenderResourceScheduler` owns an exact reveal-generation profile:
|
||||
|
||||
- mesh item count: 8 -> 64;
|
||||
- composite item count: 16 -> 64;
|
||||
- upload bytes: unchanged at 8 MiB;
|
||||
- array allocation, mipmap, buffer upload/allocation/copy, and new-resource
|
||||
ceilings: unchanged.
|
||||
|
||||
The profile ends at the portal/world viewport swap. The complete canonical
|
||||
render/composite/collision readiness predicate remains mandatory.
|
||||
|
||||
### Private retained-UI ownership
|
||||
|
||||
The paperdoll and creature appraisal used shared mesh data without independent
|
||||
references, so old-world retirement could evict their meshes. Both now use
|
||||
`SyntheticEntityMeshReferenceOwner`, with transactional acquisition and
|
||||
retryable release. `PaperdollFramePresenter` retains the last successful
|
||||
private object across temporary SmartBox unavailability and equivalent
|
||||
appearance refreshes; character-session reset is the explicit clear edge.
|
||||
|
||||
### ACE KnownObjects revisit
|
||||
|
||||
The active 25-second liveness teardown was correct for resource reclamation,
|
||||
but ACE may keep the GUID in `KnownObjects` and omit CreateObject when the
|
||||
client revisits that landblock. Deleting acdream's final accepted spawn
|
||||
snapshot therefore made the object permanently absent.
|
||||
|
||||
`DormantLiveEntityStore` retains only the accepted data snapshot after every
|
||||
active owner is destroyed. Landblock residency re-enters ordinary hydration
|
||||
with the retained timestamp gate. Exact F747, a newer instance generation, and
|
||||
session reset remove the dormant snapshot. Dormant objects own no physics,
|
||||
animation, script, effect, light, audio, render, collision, or per-frame work.
|
||||
This ACE adaptation remains explicit in divergence AP-69.
|
||||
|
||||
## Gates
|
||||
|
||||
- Focused portal/retirement/reveal/private-viewport/dormant tests: green.
|
||||
- Complete App Release suite: 3,813 passed / 3 skipped.
|
||||
- Release solution build: green with zero warnings.
|
||||
- Complete Release solution suite: 8,322 passed / 5 skipped.
|
||||
- User connected visual gate:
|
||||
- paperdoll remains visible;
|
||||
- portal transit is materially faster;
|
||||
- server-spawned objects remain after repeated travel.
|
||||
Loading…
Add table
Add a link
Reference in a new issue