acdream/docs/research/2026-07-24-retail-streaming-retirement-pseudocode.md
Erik 823936ec31 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.
2026-07-25 08:35:12 +02:00

260 lines
9.1 KiB
Markdown

# Retail landblock/object retirement and portal blocking — pseudocode
**Purpose:** Oracle boundary for Modern Runtime Slice E.
## Named-retail sources
- `CLandBlock::destroy_static_objects @ 0x0052FA50`
- `CLandBlock::Destroy @ 0x0052FAA0`
- `CObjectMaint::DestroyObjects @ 0x00508C30`
- `CObjectMaint::AddObjectToBeDestroyed @ 0x00508F70`
- `SmartBox::UseTime @ 0x00455410`
- `gmSmartBoxUI::UseTime @ 0x004D6E30`
The Sept 2013 named pseudo-C is the behavioral oracle. WorldBuilder has no
equivalent live-client teardown scheduler; its extracted mesh/cache owners are
implementation references only.
## `CLandBlock::destroy_static_objects`
```text
for i = 0 .. num_static_objects - 1:
object = static_objects[i]
if object != null:
object.leave_world()
delete object
num_static_objects = 0
```
The operation is synchronous in retail. Each object leaves the world before
its memory is destroyed.
## `CLandBlock::Destroy`
```text
destroy_static_objects()
destroy_buildings()
if landblock_info != null:
landblock_info.Release()
landblock_info = null
closest = invalid
lbi_exists = false
direction = unknown
if draw_array != null:
delete[] draw_array
draw_array = null
draw_array_size = 0
```
Retail does not expose a partially active destroyed landblock. The visible,
spatial, ticking, and memory lifetimes end in one call.
## `CObjectMaint::DestroyObjects`
```text
for every physics object:
object.exit_world()
object.leave_world()
remove object from object_table
remove object from null_object_table
remove object from destruction schedule
object.unset_parent()
object.unparent_children()
delete object
for every weenie object:
remove from weenie tables
delete object
destroy all object/null/weenie/inventory tables
destroy lost-cell table
```
Again, execution is complete once begun. Parent/child and spatial withdrawal
precede physical deletion.
## `CObjectMaint::AddObjectToBeDestroyed`
```text
remove any prior destruction schedule for object_id
deadline = Timer.cur_time + 25 seconds
destruction_table[object_id] = deadline
destruction_priority_queue.Insert(deadline, object_id)
```
This queue delays *when* complete object destruction begins. It is not
precedent for leaving an object active while destroying it one field at a
time. Acdream's 25-second nonresident live-object lifetime already ports this
separate behavior.
## `SmartBox::UseTime`
```text
if cell_manager.blocking_for_cells == false:
if not all_cells_available and CheckPrefetchStatus():
CellManager.UpdateLoadPoint()
if player exists and player has a cell:
CellManager.ChangePosition(player.position)
if player exists and not waiting_for_teleport
and not position_update_complete:
position_update_complete = true
has_been_teleported = true
ObjectMaint.UseTime()
Physics.UseTime()
GameTime.UseTime()
LScape.UseTime()
Ambient.UseTime()
else:
CellManager.CheckPrefetchStatus()
SceneTool.Think()
drain inbound SmartBox event queue
CommandInterpreter.UseTime()
Render.CalcDegLevel()
```
While retail is blocked for destination cells, the old world does not advance
object maintenance, physics, landscape, game time, or ambient sound. UI/event
dispatch and the portal presentation continue. This is the retail basis for
Slice E's immediate old-generation quiesce.
## `gmSmartBoxUI::UseTime` portal wait presentation
The portal `CreatureMode` remains live while `SmartBox` is blocked. Its camera
continues one random roll segment at a time:
```text
if now >= rotationStart + rotationDuration:
currentAngle = rotationEndAngle
rotationStart = now
rotationDuration = RandomUniform(0.6, 1.8) seconds
rotationStartAngle = currentAngle
rotationEndAngle = RandomUniform(0, 360) degrees
ECM_UI.SendNotice_DisplayStringInfo(
channel = 0x1A,
text = "In Portal Space - Please Wait...")
else:
level = UIGlobals.GetAnimLevel(
(now - rotationStart) / rotationDuration)
currentAngle = Lerp(
rotationStartAngle,
rotationEndAngle,
level / 1024)
CreatureMode.SetCameraDirection_Degrees((0, currentAngle, 0))
```
The notice is a centered UI display string, not chat. Slice E's asynchronous
adaptation delays its first projection until the recorded five-second reveal
stall threshold, then repeats it on the same rotation-segment boundary while
the authored tunnel keeps animating.
## Acdream asynchronous adaptation
Retail performs each teardown synchronously and can synchronously block cell
loading. Acdream prepares content on a worker and must not spend an unbounded
render/update frame reproducing that transaction. The faithful observable
contract is therefore split at a different boundary:
```text
begin destination generation:
mark prior world generation unavailable
replace normal world viewport with portal presentation
freeze old-generation object/static/effect simulation
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:
release scripts/effects/lights/plugin projections
release CPU owners
admit renderer retirement; physical GL release remains fence delayed
in parallel, under reserved destination budgets:
admit destination completions
publish collision and required Near render state
request/upload required assets
reveal only when the one destination-generation readiness state proves:
collision root ready
required Near scene ready
required composite textures ready
camera/player identity belongs to destination generation
if the portal reveal is still incomplete after five seconds:
keep the DAT-authored portal scene animating
display the centered retail wait notice
emit one generation-scoped diagnostic
do not materialize early
```
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.
2. FIFO is stable within priority and generation.
3. A time limit is checked between atomic owner operations. If one atomic
operation itself exceeds its ceiling, diagnostics name that operation; it
is then split at its natural cursor rather than interrupted mid-mutation.
4. No deferred callback may mutate GL from a worker.
5. Destination-critical work receives a reserved share of each frame; it does
not bypass all budgets.
6. A reveal delay beyond retail's five-second fade ceiling keeps the portal
tunnel active and displays the retail wait cue. It never reveals an
incomplete world.