checkpoint(render): portal-haze root cause — emitters need own-cell membership (S2 chunk 6 contract)
Instrumented arrivals show the Hidden-state cloud's emitters spawn in the arrival cell, become view-eligible when the world appears, and are never drawn: the walk draws an owner's emitters only through the owner's registry rows, and a hidden owner's shadow is suspended. Retail's CPhysicsObj::add_particle_shadow_to_cell (0x00514a70) gives an emitter one shadow in its own cell, drawn at that cell's turn regardless of the parent's hidden state. S2 chunk 6 (packet §10) ports that: per-cell emitter draw membership, owner-union particle path deleted. Route timing fixed so the arrival screenshots anchor on the reveal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
7ec72765d7
commit
2d20ee917b
4 changed files with 71 additions and 3 deletions
File diff suppressed because one or more lines are too long
|
|
@ -717,6 +717,66 @@ lead-verified against the source before any edit).**
|
||||||
| A10 | `_cellStatics`/`_cellDynamics` + `GetCellCount`/`CopyCellTo`/`GetCellStaticCount`/`CopyCell*To` had zero production callers | deleted | `ArchRenderSceneTests` updated |
|
| A10 | `_cellStatics`/`_cellDynamics` + `GetCellCount`/`CopyCellTo`/`GetCellStaticCount`/`CopyCell*To` had zero production callers | deleted | `ArchRenderSceneTests` updated |
|
||||||
| A11/A12, R5–R7 | live list handed out; probe-flag coupling; unloaded-neighbour rows; circumsphere cheap reject; particle branch | documented (AD-117 items 2–4; S5 tail) | — |
|
| A11/A12, R5–R7 | live list handed out; probe-flag coupling; unloaded-neighbour rows; circumsphere cheap reject; particle branch | documented (AD-117 items 2–4; S5 tail) | — |
|
||||||
|
|
||||||
|
## 10. Chunk 6 — particle emitter membership (owner G2 finding, 2026-09-03)
|
||||||
|
|
||||||
|
**Finding.** After a portal the purple cloud around the arriving character no
|
||||||
|
longer shows. Instrumented run `logs/selfgate-20260903-054153-haze-candidate-2`
|
||||||
|
(`ACDREAM_DUMP_PLAYSCRIPT=1`, temporary `[pes-spawn]`/`[pes-vis]` traces):
|
||||||
|
the server keeps the player HIDDEN until acdream sends LoginComplete at reveal
|
||||||
|
`complete` (retail-correct); the Hidden-state script's 14 emitters spawn in
|
||||||
|
the arrival cell right after `materialized`, become view-eligible at
|
||||||
|
`world-visible`, and are never drawn (frame `h02-arrive-400ms`: room visible,
|
||||||
|
no character, no cloud), then the UnHide script stops them at `complete`.
|
||||||
|
Cause: the walk draws an owner's emitters only when the owner's registry
|
||||||
|
rows are in the cell (`WalkFrameDriver` `CellParticles`/`StaticParticles`
|
||||||
|
owner unions → `DrawForOwners`), and a hidden owner's shadow is suspended.
|
||||||
|
Before chunk 5 the deleted parent-cell fallback bucketed the hidden player
|
||||||
|
anyway, which is why the cloud used to draw. The ledger's "particle emitters
|
||||||
|
never reach the registry — no production path" (AD-117 item 4) was wrong:
|
||||||
|
this is that path.
|
||||||
|
|
||||||
|
**Retail truth.** `CPhysicsObj::add_particle_shadow_to_cell` 0x00514a70: an
|
||||||
|
emitter (`state & 0x1000`) owns exactly ONE shadow object in ITS OWN current
|
||||||
|
cell (`this->cell`), `CObjCell::add_shadow_object(cell, shadow, 1)` and
|
||||||
|
`CPartArray::AddPartsShadow(part_array, this->cell, 1)` (no clip planes); the
|
||||||
|
`add_shadows_to_cells` branch at 0x00514aed skips the CELLARRAY flood for it.
|
||||||
|
It is drawn at that cell's object turn like any object, independent of its
|
||||||
|
parent's hidden state — that IS the cloud around a hidden, portalling player.
|
||||||
|
|
||||||
|
**Contract (behavior change).** An emitter's draw membership is its own cell:
|
||||||
|
1. `ParticleSystem` keeps a per-pass `cell → renderable handles` index,
|
||||||
|
maintained where `RefreshRenderableIndex` adds/removes and where
|
||||||
|
`UpdateEmitterOwnerCell` changes the cell (an emitter is in exactly one
|
||||||
|
cell; `OwnerCellId` is the owner's pose-registry cell, which is kept while
|
||||||
|
the owner is hidden). New API `CopyRenderableEmittersInCell(pass, cellId,
|
||||||
|
destination)`; no per-frame allocation after warmup.
|
||||||
|
2. `ParticleRenderer.DrawForCell(camera, cameraWorldPos, pass, cellId, clipSlot)`
|
||||||
|
beside `DrawForOwners`.
|
||||||
|
3. The walk draws particles BY CELL at the existing turns: interior
|
||||||
|
`CellParticles` (cell id), landscape `StaticParticles` (land cell id;
|
||||||
|
building-shell turns draw the building's interior cells' emitters at their
|
||||||
|
own cell turns instead), the post-replay interior flood-cell pass
|
||||||
|
(`RetailPViewRenderer` `_cellParticleOwnerScratch`), and look-in cells
|
||||||
|
(`DrawBuildingLookInDynamics`). Every owner-union path for particles
|
||||||
|
(`UnionOwners`/`UnionNewOwners` over `GetCellStatics`/`GetCellDynamics`/
|
||||||
|
`GetOutdoorStatics`/`GetOutdoorDynamics` for particle purposes,
|
||||||
|
`_outdoorParticleOwnersDrawnThisFrame`, `_particleClassifications.Outdoor`/
|
||||||
|
`Visible`, `RetailPViewCellSliceContext.ParticleOwnerIds`,
|
||||||
|
`RetailPViewLandscapeStaticParticleContext.ParticleOwnerIds`) is deleted —
|
||||||
|
an emitter is in one cell, so no cross-cell dedupe is needed.
|
||||||
|
`DrawUnattachedSceneParticles` (owner 0) is unchanged this chunk.
|
||||||
|
4. `ApplyRetailView`'s eligibility (owner cell in the visible set + range)
|
||||||
|
stays as the retail `ShouldDrawParticles` gate.
|
||||||
|
5. Register: AD-117 item 4 becomes a PORT note (own-cell membership tracked by
|
||||||
|
the particle system's cell index rather than a registry row — the emitter
|
||||||
|
is not a physics object in acdream; record that residual in the row).
|
||||||
|
6. Tests: cell index add/remove/move and "hidden owner's emitter still
|
||||||
|
enumerated by cell" (Core); executor/walk draws by cell (App, replace the
|
||||||
|
owner-id fixtures); the probe route `route-portal-haze.txt` frame
|
||||||
|
`h02-arrive-400ms` must show the cloud (lead runs it).
|
||||||
|
Temporary probes (`[pes-spawn]`, `[pes-vis]`, the `HiddenTraceEnabled` field)
|
||||||
|
are deleted in the same commit as the fix.
|
||||||
|
|
||||||
Verified-correct list from both reviews (kept for S5): Contract A dispatch,
|
Verified-correct list from both reviews (kept for S5): Contract A dispatch,
|
||||||
`find_bbox_cell_list` shape, `AddPartsShadow` order/clip-plane rule,
|
`find_bbox_cell_list` shape, `AddPartsShadow` order/clip-plane rule,
|
||||||
movement from the transition's array, keep-when-empty, child inheritance
|
movement from the transition's array, keep-when-empty, child inheritance
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,9 @@ public sealed class LiveEntityPresentationController : IDisposable
|
||||||
public const uint UnHideScriptType = 0x75u;
|
public const uint UnHideScriptType = 0x75u;
|
||||||
public const uint HiddenScriptType = 0x76u;
|
public const uint HiddenScriptType = 0x76u;
|
||||||
|
|
||||||
|
private static readonly bool HiddenTraceEnabled =
|
||||||
|
Environment.GetEnvironmentVariable("ACDREAM_DUMP_PLAYSCRIPT") == "1";
|
||||||
|
|
||||||
private readonly LiveEntityRuntime _liveEntities;
|
private readonly LiveEntityRuntime _liveEntities;
|
||||||
private readonly ShadowObjectRegistry _shadows;
|
private readonly ShadowObjectRegistry _shadows;
|
||||||
private readonly Func<uint, uint, float, bool> _playTyped;
|
private readonly Func<uint, uint, float, bool> _playTyped;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
# Portal-exit haze probe route (2026-09-03, owner finding at the G2 check).
|
# Portal-exit haze probe route (2026-09-03, owner finding at the G2 check).
|
||||||
# Teleport, then shoot at short intervals after the world reveals so the
|
# Teleport, let the client ENTER portal space first (sleep), then wait for
|
||||||
# UnHide (0x75) script's emitters are in frame if they draw at all.
|
# the world to come back and shoot at short intervals so the UnHide (0x75)
|
||||||
|
# script's emitters are in frame if they draw at all.
|
||||||
|
|
||||||
wait world-visible 180000
|
wait world-visible 180000
|
||||||
sleep 3000
|
sleep 3000
|
||||||
|
|
@ -8,6 +9,7 @@ screenshot h00-before 15000
|
||||||
|
|
||||||
# Facility Hub pose (interior arrival)
|
# Facility Hub pose (interior arrival)
|
||||||
command /teleloc 8A02015E 60.251431 -38.848198 -5.995000 0.995004 0.000000 0.000000 -0.099833
|
command /teleloc 8A02015E 60.251431 -38.848198 -5.995000 0.995004 0.000000 0.000000 -0.099833
|
||||||
|
sleep 1500
|
||||||
wait world-visible 90000
|
wait world-visible 90000
|
||||||
screenshot h01-arrive-0ms 15000
|
screenshot h01-arrive-0ms 15000
|
||||||
sleep 400
|
sleep 400
|
||||||
|
|
@ -18,9 +20,11 @@ sleep 1000
|
||||||
screenshot h04-arrive-2000ms 15000
|
screenshot h04-arrive-2000ms 15000
|
||||||
sleep 2000
|
sleep 2000
|
||||||
screenshot h05-arrive-4000ms 15000
|
screenshot h05-arrive-4000ms 15000
|
||||||
|
sleep 4000
|
||||||
|
|
||||||
# Holtburg house pose (a second arrival, outdoor landblock)
|
# Holtburg house pose (a second arrival, outdoor landblock)
|
||||||
command /teleloc A9B4013F 134.555893 11.423566 94.005005 0.996917 0.000000 0.000000 -0.078459
|
command /teleloc A9B4013F 134.555893 11.423566 94.005005 0.996917 0.000000 0.000000 -0.078459
|
||||||
|
sleep 1500
|
||||||
wait world-visible 90000
|
wait world-visible 90000
|
||||||
screenshot h11-arrive-0ms 15000
|
screenshot h11-arrive-0ms 15000
|
||||||
sleep 400
|
sleep 400
|
||||||
|
|
@ -29,5 +33,6 @@ sleep 600
|
||||||
screenshot h13-arrive-1000ms 15000
|
screenshot h13-arrive-1000ms 15000
|
||||||
sleep 1000
|
sleep 1000
|
||||||
screenshot h14-arrive-2000ms 15000
|
screenshot h14-arrive-2000ms 15000
|
||||||
|
sleep 3000
|
||||||
|
|
||||||
close-client
|
close-client
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue