fix(streaming): retire stale portal-region entities
Port retail's 25-second leave-visibility lifetime over canonical live records, retaining spatially resident and owned entities while using the conservative ACE visibility envelope for nonresident records. Route expiry through the normal generation-safe F747 teardown so animations, effects, physics, and render owners unwind symmetrically. Replace the append-only modern mesh buffer with coalescing vertex/index ranges and upload each mesh's vertices once instead of once per material. Released zero-reference meshes can now reuse GPU ranges after portal and cache churn. A connected five-region round trip returned animation ownership to baseline, recreated the starting region on revisit, and held normal FPS. Release build succeeds and all 5,927 tests pass with five intentional skips. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
2cbf34a668
commit
3971997689
13 changed files with 918 additions and 117 deletions
|
|
@ -79,20 +79,39 @@ order inside one instanced draw, with only DAT blend-mode changes splitting a
|
|||
run. The identical location recovered to about 153 FPS / 6.6 ms in the
|
||||
connected Release client.
|
||||
|
||||
A second, genuinely cumulative portal regression remained: ACE retains
|
||||
`KnownObjects` across normal teleports, while acdream retained every old
|
||||
`LiveEntityRecord` indefinitely. Each destination therefore left animation,
|
||||
effect, and render owners active; after enough trips the render thread blocked
|
||||
inside the GL driver and C95B fell persistently to 3–12 FPS. The client now
|
||||
ports retail's 25-second leave-visibility destruction queue, using current
|
||||
spatial residency plus holtburger's conservative 384-unit ACE envelope until
|
||||
exact ObjCell PVS is available. Expiry uses the normal generation-safe F747
|
||||
teardown. The modern `GlobalMeshBuffer` also allocates one vertex range per
|
||||
mesh, coalesces released ranges, and reuses them on cache eviction instead of
|
||||
duplicating vertices per material and growing forever. A connected five-region
|
||||
round trip returned live/animation ownership to baseline, recreated C95B on
|
||||
revisit, and held its normal 60–80 FPS.
|
||||
|
||||
**Files:** `src/AcDream.App/Rendering/RetailAlphaQueue.cs`;
|
||||
`src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs`;
|
||||
`src/AcDream.App/Rendering/ParticleRenderer.cs`;
|
||||
`src/AcDream.App/Rendering/Shaders/particle.vert`;
|
||||
`src/AcDream.App/Rendering/Shaders/particle.frag`;
|
||||
`src/AcDream.App/Rendering/RetailPViewRenderer.cs`.
|
||||
`src/AcDream.App/Rendering/RetailPViewRenderer.cs`;
|
||||
`src/AcDream.App/World/LiveEntityLivenessController.cs`;
|
||||
`src/AcDream.App/Rendering/Wb/GlobalMeshBuffer.cs`.
|
||||
|
||||
**Research:**
|
||||
`docs/research/2026-07-18-retail-shared-alpha-list-pseudocode.md`.
|
||||
`docs/research/2026-07-18-retail-shared-alpha-list-pseudocode.md`;
|
||||
`docs/research/2026-07-18-retail-object-liveness-and-mesh-reclamation-pseudocode.md`.
|
||||
|
||||
**Acceptance:** At a translucent lifestone, smoke or flame behind the crystal
|
||||
is attenuated by it while an effect in front remains bright. The lifestone's
|
||||
own transparency, nearby candle/portal particles, indoor/outdoor PView
|
||||
transitions, paperdoll rendering, and portal-space rendering do not regress.
|
||||
Repeated distant portal trips do not accumulate live/animation owners or
|
||||
degrade FPS, and revisiting an expired region restores its objects.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -169,7 +169,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 | On a landblock unload/reload, acdream retains the accepted live record and the same `WorldEntity`. Full unload moves non-persistent live projections to that landblock's pending bucket; Near→Far demotion drops only dat-static layers and leaves live projections resident. Neither path replays logical registration or reconstructs from a stale CreateObject. ACE will NOT re-broadcast objects whose guid remains in its per-player `KnownObjects` set, matching retail's retained object-table model. DIVERGENCE: acdream has NO retail 25 s / 384 m visibility cull — the retained record is pruned ONLY by an explicit server `DeleteObject` (0xF747) or session teardown. (#138) | `src/AcDream.App/World/LiveEntityRuntime.cs`; `src/AcDream.App/Streaming/GpuWorldState.cs`; `GameWindow.RehydrateServerEntitiesForLandblock`; `StreamingController` (`onLandblockLoaded`, Loaded + Promoted) | Stable logical identity is retail-faithful and prevents duplicated meshes, scripts, emitters, or stale-spawn teleports while remaining independent of an ACE re-broadcast | An object the server silently stopped tracking WITHOUT a `DeleteObject` (e.g. a creature that wandered out of PVS) can reappear at its last-known position, where retail's 25 s cull would have dropped it — a stale ghost until a real `DeleteObject` or the next session. Close it by porting holtburger's 25 s/384 m `ACE_DESTRUCTION_TIMEOUT` self-cull | `CPhysicsObj::change_cell` 0x00513390; `CPhysicsObj::leave_world` 0x005155A0; ACE `ObjectMaint.KnownObjects` never cleared on teleport (`Physics/Common/ObjectMaint.cs`, `WorldObjects/Player_Tracking.cs`); holtburger client 25 s cull `liveness.rs` `ACE_DESTRUCTION_TIMEOUT_SECS` |
|
||||
| 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`; `GameWindow.OnLiveEntityPruned` | 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-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 |
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@ their render transaction could publish (#218).
|
|||
| Component | What it does |
|
||||
|---|---|
|
||||
| `MinimapRenderer` | Top-down minimap |
|
||||
| `GlobalMeshBuffer` | Shared GPU mesh buffer |
|
||||
| `GlobalMeshBuffer` | Shared GPU mesh buffer; one reclaimable vertex/index allocation per mesh, released by `ObjectMeshManager` eviction |
|
||||
| `GpuResourceManager` | GPU resource lifecycle |
|
||||
| `InstanceData` | Instanced draw data |
|
||||
| `TextureHelpers` | INDEX16, P8, BGRA, DXT decode + alpha (canonical port) |
|
||||
|
|
|
|||
|
|
@ -558,9 +558,11 @@ behavior. Estimated 17–26 days focused work, 3–5 weeks calendar.
|
|||
|
||||
- **Missile/portal VFX campaign Step 6 implemented and independently reviewed 2026-07-14.** Pure Core now owns the retail projectile clock/integration/sweep primitive: 0.2-second catch-up quanta, 50-unit/second clamp, final-state acceleration, world-space omega, complete 3-D AlignPath, scaled Setup-local collision spheres, terrain/BSP/object continuous sweeps, exact `OBJECTINFO::missile_ignore`, self-shadow rejection, elastic/inelastic response, and correct full-cell rebasing across loaded landblocks. App live ownership and authoritative corrections remain Step 7. TS-2 is retired; ordinary missiles add PathClipped but not PerfectClip, so AP-83/AP-91 remain dormant.
|
||||
- **Missile/portal VFX campaign Step 7 implemented and independently reviewed 2026-07-14.** `ProjectileController` now attaches the Step 6 body to the canonical `LiveEntityRecord` after materialization and advances arrows, bolts, and spell missiles without another GUID map, renderer registration, or stale-spawn reconstruction. It commits predicted frames through `WorldEntity.SetPosition`, republishes static effect roots, and keeps collision shadows plus canonical full-cell buckets synchronized. A predicted crossing into an unloaded bucket suspends the body and shadow in that same quantum; pending/pickup residence retains the shadow registration for exact re-entry, and hydration plus the 96-unit retail activity gate restart at the current clock without backlog. Fresh State/Vector/Position/Movement packets correct or stop the same body after retail timestamp gates; SetState reaches the canonical body before optional projectile acquisition, and a non-finite local receipt clock cannot consume first classification. Removing Missile retains ordinary active-body physics (delegating to MovementManager when present), while both owners share the body and incarnation-scoped live-record cell. CreateObject vectors are installed only at body construction, never replayed by later SetState. Delete, reset, and GUID reuse use normal logical teardown; ACE remains authoritative for impact, damage, effects, and deletion.
|
||||
- **Missile/portal VFX campaign Step 8 implemented and independently reviewed 2026-07-14.** `LiveEntityRuntime` now distinguishes raw server PhysicsState from retail's side-effect-derived final state, beginning from constructor state `0x00400C08` and applying `set_state` in Lighting → NoDraw → Hidden order. `LiveEntityPresentationController` keeps Hidden objects logically alive while suppressing root mesh, collision, picking, radar, status, and new target acquisition; typed `PS_Hidden`/`PS_UnHide` plays remain DAT-driven, direct equipped children inherit NoDraw, particles/lights survive, and repeated/stale state cannot replay a transition. A normal visible CreateObject never fabricates UnHide. Effect-owner preparation is separate from pending F754/F755 replay so construction-state effects always run first. Remote fresh-teleport/cell-less placement now executes the exact ordered `teleport_hook` action bundle and a full-cell hard placement before contact/airborne routing, preventing an airborne correction from restoring the old location. Projectile Hidden state pauses the retained body without consuming its active identity or accumulating a clock backlog. TS-43 is retired; AP-69 remains because the separate 25-second/384-metre liveness cull is still unported.
|
||||
- **Missile/portal VFX campaign Step 8 implemented and independently reviewed 2026-07-14.** `LiveEntityRuntime` now distinguishes raw server PhysicsState from retail's side-effect-derived final state, beginning from constructor state `0x00400C08` and applying `set_state` in Lighting → NoDraw → Hidden order. `LiveEntityPresentationController` keeps Hidden objects logically alive while suppressing root mesh, collision, picking, radar, status, and new target acquisition; typed `PS_Hidden`/`PS_UnHide` plays remain DAT-driven, direct equipped children inherit NoDraw, particles/lights survive, and repeated/stale state cannot replay a transition. A normal visible CreateObject never fabricates UnHide. Effect-owner preparation is separate from pending F754/F755 replay so construction-state effects always run first. Remote fresh-teleport/cell-less placement now executes the exact ordered `teleport_hook` action bundle and a full-cell hard placement before contact/airborne routing, preventing an airborne correction from restoring the old location. Projectile Hidden state pauses the retained body without consuming its active identity or accumulating a clock backlog. TS-43 is retired; AP-69 remains for exact ObjCell-PVS/preview-retention parity after the 2026-07-18 liveness port.
|
||||
- **Missile/portal VFX campaign Step 9 automated hardening implemented and independently reviewed 2026-07-14; final two-client visual gate pending.** A deterministic 96-owner App fixture drives canonical missiles and effect owners through projectile updates, repeated DAT-effect scheduling, loaded↔pending↔loaded landblock churn, light/particle withdrawal and recovery, accepted deletes, same-GUID generation reuse, never-created F754 queues, and session reset. It asserts balanced logical render registration and zero residual records/bodies/projectiles, spatial buckets/rescues, shadows (including suspended registrations), effect profiles/packets, PhysicsScript FIFOs/anchors/delayed calls, particle bindings/logical IDs/render-pass owners, poses, light-controller/sink/manager owners, and stale record component references. A companion twelve-cycle gate drives exact recall motion `0x10000153` through AnimationSequencer/CallPES, Hidden, deferred remote placement, hydration, and UnHide; a second 96-owner `EntitySpawnAdapter` gate balances actual mesh-adapter reference counts without GL. The pass fixed undrained persistent rescue retention, delayed stale visibility edges, GUID-scoped teardown, create resurrection after a nested delete/reset, non-atomic resource registration, double teardown from a re-entrant session-clear callback, observer failure stranding later visibility edges, and stale outer projection transactions overwriting callback-created replacements. Teardown now removes exact projection references and generation/local-ID owners, uses per-GUID/session lifetime epochs plus per-record projection tokens, drains visibility fan-out before aggregating failures, and finishes or supersedes canonical commits before surfacing observer errors. Core remains GL/backend-free, panels remain on UI abstractions, and projectiles still draw through ordinary `WbDrawDispatcher` live-entity submission rather than a global projectile pass. AP-69 and TS-49 remain; AP-83/AP-91 now explicitly describe only a future mover that enables PerfectClip.
|
||||
|
||||
- **Portal lifetime/performance correction 2026-07-18; user visual gate pending.** `LiveEntityLivenessController` ports retail's 25-second leave-visibility destruction queue over canonical live records, cancels expiry for spatially resident or owned objects, and uses holtburger's conservative 384-unit envelope where ACE supplies no client-visible PVS leave event. Expiry reuses the exact generation-safe F747 teardown. The modern `GlobalMeshBuffer` now uploads vertices once per mesh and owns coalescing vertex/index ranges released by zero-reference LRU eviction. A connected five-region route returned animation ownership to baseline, reclaimed/reused GPU ranges, recreated the starting region on revisit, and prevented the prior persistent 3–12 FPS collapse. AP-69 is narrowed rather than retired because exact ObjCell PVS and explicit preview lifetimes remain.
|
||||
|
||||
**Reference docs:** `docs/research/retail-ui/00-master-synthesis.md` + slices 01-06. Every AC-specific behavior has a decompiled FUN_ / DAT_ citation.
|
||||
|
||||
**Acceptance:** chat messages display in a retail-style panel, health/stamina/mana orbs fill correctly, attributes panel shows player stats, inventory opens with drag-drop working, and sound plays on hit/footstep.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
# Retail object liveness and modern mesh reclamation
|
||||
|
||||
## Symptom and measured cause
|
||||
|
||||
After several distant portal transitions, acdream could fall from normal
|
||||
frame rates to 3–12 FPS and remain there. Lightweight ownership counters
|
||||
showed that each destination left its server objects in `LiveEntityRuntime`:
|
||||
the live-record count, animated-owner count, particle/effect ownership, and
|
||||
materialized render owners grew monotonically. The render thread eventually
|
||||
blocked in the native GL driver even though the current destination submitted
|
||||
only a small number of visible particles.
|
||||
|
||||
The modern shared mesh buffers had a second cumulative ownership defect. The
|
||||
old `GlobalMeshBuffer.Append` copied the complete vertex array once for every
|
||||
material batch and only advanced append offsets. `ObjectMeshManager` eviction
|
||||
removed cache entries and textures but could not return those vertex/index
|
||||
ranges. This was not the first-frame collapse mechanism, but it guaranteed
|
||||
that portal and cache churn permanently consumed GPU address space.
|
||||
|
||||
## Named-retail oracle
|
||||
|
||||
The retail client owns one object table and separates leaving visibility from
|
||||
logical destruction:
|
||||
|
||||
- `CPhysicsObj::prepare_to_leave_visibility` (`0x00511F40`) removes shadows,
|
||||
leaves the cell, and calls `CObjectMaint::AddObjectToBeDestroyed` for the
|
||||
object and each child.
|
||||
- `CObjectMaint::AddObjectToBeDestroyed` (`0x00508F70`) replaces any existing
|
||||
deadline with `Timer::cur_time + 25.0`, then inserts the GUID in the ordered
|
||||
destruction queue.
|
||||
- `CPhysicsObj::prepare_to_enter_world` (`0x00511FA0`) cancels the queued
|
||||
destruction for the object and its children before re-entry.
|
||||
- `CObjectMaint::UseTime` (`0x005089B0`) rebuilds the visible-object table at
|
||||
one-second intervals and drains every due destruction entry in time order.
|
||||
- `CObjectMaint::DeleteObject` (`0x00508460`) performs symmetric teardown:
|
||||
exit/leave world, remove object/null-object table ownership, remove the
|
||||
destruction entry, unparent, unparent children, then destroy the object.
|
||||
- `SmartBox::Reset` (`0x00453BF0`) destroys the complete object table only for
|
||||
a true session/reset path. A portal is not a session reset.
|
||||
|
||||
The 25-second mechanism is exact retail behavior. The client decides that an
|
||||
object left visibility from ObjCell/PVS membership, not from a raw distance
|
||||
constant.
|
||||
|
||||
## ACE compatibility boundary
|
||||
|
||||
ACE retains a player's `KnownObjects` across normal teleports. Its optional
|
||||
`teleport_visibility_fix` is disabled by default and is explicitly described
|
||||
as a non-retail automated workaround. Therefore a client cannot depend on an
|
||||
F747 `DeleteObject` when an old destination leaves visibility.
|
||||
|
||||
Holtburger independently implements the client half with the exact retail
|
||||
25-second timeout and a conservative 384-world-unit distance envelope while
|
||||
exact ACE ObjCell visibility is unavailable. It also treats scene-nearby
|
||||
objects as visible and retains held, equipped, container, wielder, parent, and
|
||||
preview-owned objects. Sources:
|
||||
|
||||
- [ACE ObjectMaint](https://github.com/ACEmulator/ACE/blob/650c5b75ae909957feaf58db320e46be16502653/Source/ACE.Server/Physics/Common/ObjectMaint.cs)
|
||||
- [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
|
||||
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.
|
||||
|
||||
## Port pseudocode
|
||||
|
||||
```text
|
||||
once per second:
|
||||
if player has no authoritative world position:
|
||||
do nothing
|
||||
|
||||
for each live record with a world position except the player:
|
||||
retained = record is attached
|
||||
OR has container owner
|
||||
OR has wielder owner
|
||||
OR has physics parent
|
||||
|
||||
visible = record has a resident spatial projection
|
||||
OR global_3d_distance(player, record) <= 384
|
||||
|
||||
if visible OR retained:
|
||||
cancel record deadline
|
||||
else if no deadline exists for this GUID + generation:
|
||||
deadline = now + 25 seconds
|
||||
else if deadline <= now:
|
||||
emit (GUID, generation) prune candidate
|
||||
|
||||
discard deadlines for records no longer in the canonical table
|
||||
|
||||
for each due candidate:
|
||||
re-read canonical record
|
||||
if GUID and generation still match:
|
||||
route through the normal accepted DeleteObject lifecycle
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Reclaimable shared-buffer contract
|
||||
|
||||
```text
|
||||
upload one mesh:
|
||||
collect non-empty material index batches in authored order
|
||||
allocate one contiguous vertex range
|
||||
allocate one contiguous index range
|
||||
upload vertices exactly once
|
||||
append each index batch inside that index range
|
||||
record each batch's first-index and the shared base-vertex
|
||||
|
||||
evict one zero-reference mesh:
|
||||
release its index range
|
||||
release its vertex range
|
||||
coalesce adjacent free ranges
|
||||
release atlas texture ownership
|
||||
|
||||
if no contiguous range fits:
|
||||
grow the GL buffer
|
||||
copy through the allocator high-water mark
|
||||
extend/coalesce the trailing free range
|
||||
allocate again
|
||||
```
|
||||
|
||||
The range allocator uses best fit to preserve larger holes, rejects overlap or
|
||||
double release before changing accounting, and keeps buffer resizing on the
|
||||
existing update/render thread.
|
||||
|
||||
## Connected verification
|
||||
|
||||
A Release client was driven through C95B → 3032 → F682 → 0905 → 0904 → C95B.
|
||||
Old-region deadlines expired after 25 seconds; live and animation-owner counts
|
||||
fell instead of accumulating. Released mesh ranges became reusable. Returning
|
||||
to C95B rebuilt its objects correctly, and the location remained at its normal
|
||||
roughly 60–80 FPS instead of the prior persistent 3–12 FPS state.
|
||||
Loading…
Add table
Add a link
Reference in a new issue