feat(render): S2 chunk 6 — particle emitters draw by their own cell (add_particle_shadow_to_cell)
Owner G2 finding: the purple cloud around an arriving character no longer drew. The server keeps the player Hidden until acdream sends LoginComplete at reveal completion (retail-correct); the Hidden-state script's emitters spawn in the arrival cell and are view-eligible when the world appears, but the walk drew 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 current cell, drawn at that cell's turn regardless of the parent's hidden state (add_shadows_to_cells 0x00514aed skips the flood for state & 0x1000). Port: ParticleSystem keeps a per-pass cell -> renderable-handles index (maintained at every renderable/OwnerCellId change) and CopyRenderableEmittersInCell; ParticleRenderer.DrawForCell; the walk draws particles BY CELL at the existing turns (interior CellParticles, landscape LandscapeCellParticles), the events fire for every visited cell, and every owner-union particle path is deleted (UnionOwners/UnionNewOwners for particles, the outdoor drawn-owner dedupe, the executor's owner classification sets, the context ParticleOwnerIds members). The post-replay per-cell pass double-submitted the root flood's emitters and is deleted: an emitter draws once, at its cell's replay turn. AD-117 item 4 becomes a port note (the index lives in the particle system; an emitter is not a physics object in acdream). The temporary [pes-spawn]/[pes-vis] traces are removed and the ACDREAM_DUMP_PLAYSCRIPT row restored. Verified: timed arrival route logs/selfgate-20260903-062522-haze-chunk6, frame h02-arrive-400ms shows the cloud at the character in Facility Hub. Gates (Release): Core 4,987/4,987; Content 214/214; Runtime 1,884/1,884; App hermetic lane 6,760/6,760; App InstalledDat 217 pass / 2 pre-existing #383. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
2d20ee917b
commit
f6b4584bf3
14 changed files with 400 additions and 336 deletions
|
|
@ -111,7 +111,7 @@ readiness/requeue adaptation. See
|
|||
|
||||
| # | Divergence | Where (file:line) | Why it is safe / justified | Risk if assumption breaks | Retail oracle |
|
||||
|---|---|---|---|---|---|
|
||||
| AD-117 | **Filed 2026-09-03 at the Campaign OVERHAUL S2 review fix round.** Three residual Contract A/B readings (the row's original item 1 — a render-only owner's no-cell-array SetPosition commit republishing at its destination cell alone, `ShadowObjectRegistry.RefreshPositionRows` — was verified statically the same night as retail's own mechanism: `CObjCell::find_cell_list` 0x0052b4e0 with `num_sphere == 0` adds only the current cell (interior `add_cell` at 0x0052b563; outdoor `CLandCell::add_all_outside_cells` 0x00533630 `arg2 <= 0` branch) and skips the transit walk (`arg2 != 0` gate at 0x0052b576); it is a port, not a deviation). (2) `ShadowShapeBuilder.FromStaticRenderParts` uses the visual-AABB circumsphere as the per-portal cheap-reject sphere for a part with no physics BSP, where retail uses `gfxobj->physics_sphere` else `drawing_sphere` (pc:310147-310152) — strictly larger, so it can only WIDEN membership. (3) `PublishRetailPartEntries` publishes part rows into every CELLARRAY id, including an unloaded neighbour cell `CEnvCell::find_transit_cells` added with a null owner, where retail's `add_shadows_to_cells` (pc:282850) zeroes that shadow's cell and skips `AddPartsShadow` until the cell loads. (4) The `state & 0x1000` particle branch (`add_particle_shadow_to_cell` 0x00514a70: the object's own cell only, never clip planes) is not ported. | `src/AcDream.Core/Physics/ShadowObjectRegistry.cs` (`RefreshPositionRows` render-only branch, `PublishRetailPartEntries`), `src/AcDream.Core/Physics/ShadowShapeBuilder.cs` (`FromStaticRenderParts` non-BSP arm) | (2) A cheap reject that fires less often admits a superset; the admitting tests (`Plane::intersect_box`, `box_intersects_cell`) are ported exactly. (3) The extra rows are unreachable by the walk until the cell is resident and `RefloodLandblock` converges them at hydration. (4) `ParticleRenderer` owns emitter placement and no production path registers an emitter, so the branch has no input. | (2) A decorative non-BSP part admitted to a neighbouring cell retail's cheap reject would have dropped — a draw clipped by that cell's portal planes, at worst a sliver. (3) A one-frame draw into a cell that just hydrated before its reflood ran. (4) If an emitter is ever routed through the registry it would flood as a bbox object instead of its own cell only. | `CPhysicsObj::SetPositionInternal` 0x00515330 (pc:283530-283541), `CPhysicsObj::add_shadows_to_cells` 0x00514ae0 (pc:282837-282875), `CEnvCell::find_transit_cells` 0x0052cae0 (pc:310147-310217), `CPhysicsObj::add_particle_shadow_to_cell` 0x00514a70, `CObjCell::find_cell_list` 0x0052b4e0. |
|
||||
| AD-117 | **Filed 2026-09-03 at the Campaign OVERHAUL S2 review fix round.** Three residual Contract A/B readings (the row's original item 1 — a render-only owner's no-cell-array SetPosition commit republishing at its destination cell alone, `ShadowObjectRegistry.RefreshPositionRows` — was verified statically the same night as retail's own mechanism: `CObjCell::find_cell_list` 0x0052b4e0 with `num_sphere == 0` adds only the current cell (interior `add_cell` at 0x0052b563; outdoor `CLandCell::add_all_outside_cells` 0x00533630 `arg2 <= 0` branch) and skips the transit walk (`arg2 != 0` gate at 0x0052b576); it is a port, not a deviation). (2) `ShadowShapeBuilder.FromStaticRenderParts` uses the visual-AABB circumsphere as the per-portal cheap-reject sphere for a part with no physics BSP, where retail uses `gfxobj->physics_sphere` else `drawing_sphere` (pc:310147-310152) — strictly larger, so it can only WIDEN membership. (3) `PublishRetailPartEntries` publishes part rows into every CELLARRAY id, including an unloaded neighbour cell `CEnvCell::find_transit_cells` added with a null owner, where retail's `add_shadows_to_cells` (pc:282850) zeroes that shadow's cell and skips `AddPartsShadow` until the cell loads. (4) PORTED at Campaign OVERHAUL S2 chunk 6: an emitter now owns exactly one draw membership in its own current cell via `ParticleSystem`'s per-pass cell index (`CopyRenderableEmittersInCell`), matching `add_particle_shadow_to_cell` 0x00514a70's own-cell-only, no-clip-planes rule and drawn at that cell's own walk turn independent of its attached owner's registry membership. The residual is architectural, not behavioral: the membership index lives in `ParticleSystem` rather than as a `ShadowObjectRegistry` row, because an emitter is not a `CPhysicsObj` in acdream and never registers with the shadow registry at all. | `src/AcDream.Core/Physics/ShadowObjectRegistry.cs` (`RefreshPositionRows` render-only branch, `PublishRetailPartEntries`), `src/AcDream.Core/Physics/ShadowShapeBuilder.cs` (`FromStaticRenderParts` non-BSP arm), `src/AcDream.Core/Vfx/ParticleSystem.cs` (per-pass cell index, item 4) | (2) A cheap reject that fires less often admits a superset; the admitting tests (`Plane::intersect_box`, `box_intersects_cell`) are ported exactly. (3) The extra rows are unreachable by the walk until the cell is resident and `RefloodLandblock` converges them at hydration. (4) `ParticleSystem`'s cell index is maintained at every point renderable state or `OwnerCellId` changes (`RefreshRenderableIndex`, `UpdateEmitterOwnerCell`), so an emitter's draw membership always matches its live cell regardless of its attached owner's suspended/hidden registry state. | (2) A decorative non-BSP part admitted to a neighbouring cell retail's cheap reject would have dropped — a draw clipped by that cell's portal planes, at worst a sliver. (3) A one-frame draw into a cell that just hydrated before its reflood ran. (4) None behavioral — a code-location note only: if particle emitters are ever modeled as registry-backed physics objects, this cell index should be retired in favor of a genuine `ShadowObjectRegistry` row rather than kept as a parallel mechanism. | `CPhysicsObj::SetPositionInternal` 0x00515330 (pc:283530-283541), `CPhysicsObj::add_shadows_to_cells` 0x00514ae0 (pc:282837-282875), `CEnvCell::find_transit_cells` 0x0052cae0 (pc:310147-310217), `CPhysicsObj::add_particle_shadow_to_cell` 0x00514a70, `CObjCell::find_cell_list` 0x0052b4e0. |
|
||||
| AD-116 | **Filed 2026-09-03 at Campaign OVERHAUL S2 chunk 5 (consumer cutover).** `WalkProductionWorldData.ResolveCellView`'s borrowed per-cell view treats an entity the registry HAS flooded into its retail CELLARRAY (so `ShadowObjectRegistry.GetRetailPartEntriesInCell` names it) but whose `RenderProjectionRecord` `RenderSceneQuery.TryGetByLocalEntityId` cannot resolve yet as contributing to NO cell for that frame — it is silently skipped rather than falling back to its authored parent cell or an outdoor root-position cell (both deleted this chunk). Every distinct entity id this happens for in one frame is counted once in `WalkProductionWorldData.UnregisteredRenderMembershipCount` and, when nonzero, reported by one print-only `[walk-membership]` line at the start of the next `BeginFrame`, gated on `RenderingDiagnostics.ProbeFacilityStairsEnabled`. | `src/AcDream.App/Rendering/Walk/WalkProductionWorldData.cs` (`ResolveCellView`, `UnregisteredRenderMembershipCount`, the `BeginFrame` diagnostic line) | Retail has no such gap at all: `CEnvCell::init_static_objects` installs the CELLARRAY before a static is ever drawable, and Contract B's collision (`shadow_object_list`) and render (`shadow_part_list`) products are ONE transaction, so they can never race. acdream's registry (the physics publisher) and its presentation scene (the projection journal) are two independently incremental pipelines fed off the same Create/appearance edge, so a transient one-frame window where the registry runs first is possible during streaming — the same class of race AD-49's residency reasoning already accepts for `CellTransit`'s own outdoor seed. Contributing NOTHING for that one frame matches retail's own rule ("an object not yet in a cell is not drawn") more closely than the deleted parent-cell/root-position fallbacks did, which could draw an object at a cell its real CELLARRAY does not actually include. | If the presentation journal's apply cadence ever falls more than one frame behind the registry's registration (not merely a same-frame ordering race), an entity would stay missing for several consecutive frames instead of appearing on the very next one — `UnregisteredRenderMembershipCount` staying nonzero across consecutive frames (not a single one-frame spike) is the signal that this row's "transient" premise has broken and needs re-investigation, not a widened fallback. | `CEnvCell::init_static_objects`; `CPartArray::AddPartsShadow` 0x00517e40 (`docs/research/2026-09-01-overhaul/oh1-construction-landscape-contract.md` Contract B) |
|
||||
| AD-115 | **Filed 2026-08-25 at Campaign AS slice AS2 review fix round (F16), classification: intentional.** `AppraisalUiController.BuildCharacterTitleDisplay` composes examination element `0x10000151` (Profession/title): when Int 261 `CharacterTitleId` is absent/unresolvable AND String 5 `Template` is also absent, it returns an empty string, and `ClearCreatureText` has already blanked the element for this `ApplyCreature` call, so the element stays cleared. Retail never clears `0x10000150`/`0x10000151`/`0x10000152` anywhere — neither `CharExamineUI::Show @0x004AB5D0` nor `BasicCreatureExamineUI::Init @0x004AB9C0` writes an empty string to those elements — so in this exact case retail would keep showing the PREVIOUS assessed target's title text on screen instead of clearing it. | `src/AcDream.App/UI/Layout/AppraisalUiController.cs` (`BuildCharacterTitleDisplay`, `ClearCreatureText`) | Deliberate improvement over retail's quirk: a stale leftover title from a prior target reads as more confusing/wrong to a player than a blank line for the current one; review F16 (2026-08-25) accepted the clear-on-no-source behavior as intentional. | None expected — this is a deliberate, reviewed divergence, not a game-feel regression; a future retail-faithfulness audit assuming `0x10000151` always mirrors retail's persistent stale-text behavior would be surprised to see it clear instead when the current target's title can't be resolved. | `CharExamineUI::Show @0x004AB5D0`; `BasicCreatureExamineUI::Init @0x004AB9C0` |
|
||||
| AD-114 | **Filed 2026-08-25 at Campaign AS slice AS2, owner-ruled 2026-08-25 (verbatim "we animate it, and I like it").** acdream's examination-window preview (`CreatureAppraisalFramePresenter` / `RetailCreatureAppraisalCloneFactory`) shares the assessed target's already-resolved live MeshRefs and re-synchronizes them every frame, so the preview clone plays the SAME current animated pose the live target is actually doing right now (attack, cast, run, idle, ...). Retail's `BasicCreatureExamineUI::Init @0x004AB9C0` instead clones the selected physics object ONCE, fixes its heading at 191.367905°, and lets its own private `CreatureMode` animate that clone independently — decoupled from whatever the live target is currently doing. | `src/AcDream.App/Rendering/CreatureAppraisalPresentation.cs` (`CreatureAppraisalFramePresenter`, `RetailCreatureAppraisalCloneFactory`) | Explicit owner direction, 2026-08-25 (`docs/plans/2026-08-25-assess-window-parity-campaign.md`: "The animated 3D paperdoll is an INTENTIONAL acdream deviation... Keep it"), noted alongside the owner's own observation that retail's static-clone colors are buggy — porting the decoupled-motion clone would not even be a faithfulness win here. | None expected — a deliberate, user-approved visual improvement over retail's decoupled clone motion, not a game-feel divergence; a future faithfulness audit assuming the preview mirrors retail's independent `CreatureMode` cycle would be surprised to see it track the live target's pose instead. | `BasicCreatureExamineUI::Init @0x004AB9C0`; `docs/plans/2026-08-25-assess-window-parity-campaign.md` |
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ $env:ACDREAM_FRAME_HISTORY = "$scratch\frames.csv"
|
|||
|---|---|---|---|---|---|
|
||||
| `ACDREAM_CAPTURE_PLAYER_QUANTA` | `=<path>` (any non-whitespace path) | Opt-in JSON-Lines trace of every admitted player physics quantum (position/orientation/velocity/contact-plane snapshots at each stage boundary of `CPhysicsObj::UpdateObjectInternal`) | Appends+flushes one JSON line per physics quantum to the file (real file I/O on the physics tick when enabled); disabled path costs one static string null/empty check, no allocation. Read once into a mutable static property (settable via `ResetForTest`) rather than a typed options object. | unset (disabled, zero-alloc) | `PlayerPhysicsQuantumCapture` static class (`AcDream.Runtime/Gameplay/PlayerPhysicsQuantumCapture.cs:22`) |
|
||||
| `ACDREAM_DUMP_MOTION` | `=1` | prints `UM`/`[UM_STALE]`/`[MOTIONDONE]`/`VU.land`/raw-hex wire dump lines tracing inbound `UpdateMotion` handling, remote ground-contact edges, and motion-done callbacks (bug-a/#32 stuck-cast subthread is temporary; core trace is long-lived) | print-only, but the raw-site reads in `LiveEntityNetworkUpdateController.cs` and `UpdateMotion.cs` fire on EVERY inbound motion/UM event (not cached) — `Environment.GetEnvironmentVariable` call per packet even when off; `UpdateMotion.cs`'s branch additionally builds a `StringBuilder` hex dump when on. Rule-5 violation (raw reads outside a diagnostics-owner class) at 5+ call sites | off | THREE independent readers: `PhysicsDiagnostics.DumpMotionEnabled` (owner, appears unconsumed — see Notes), `AnimationPresentationDiagnostics.FromEnvironment()` (App owner record, cached once at startup, consumed by `LiveEntityAnimationPresenter`), and raw `Environment.GetEnvironmentVariable` reads scattered across `LiveEntityNetworkUpdateController.cs` (4 sites) + `Core.Net/Messages/UpdateMotion.cs:163` + `Runtime/Physics/RuntimeRemotePhysicsUpdater.cs:630` |
|
||||
| `ACDREAM_DUMP_PLAYSCRIPT` | `="1"` (ordinal) | Traces PhysicsScript playback: missing/empty script resolution, malformed `StartTime` entries, and other `[pes]`-prefixed hook-dispatch events; TEMPORARY (2026-09-03 portal-haze investigation, delete with it): `[pes-spawn]` per emitter spawn (owner, handle, owner cell, hidden-presentation state) from `ParticleHookSink` and `[pes-vis]` per renderable-index flip (view eligibility, presentation visibility, owner cell) from `ParticleSystem`, both through the vfx diagnostic sink | print-only (`Console.WriteLine`) at all 4 runner use sites (`:85-86,136,300,328`) plus the two temporary vfx traces | unset (off) | `PhysicsScriptRunner.DiagEnabled` (`PhysicsScriptRunner.cs:61-62`) — per-instance settable property seeded from the env var, not a shared static diagnostics-owner class; the two temporary traces read the same env var once into a private static (`ParticleSystem.DiagEnabled`, `ParticleHookSink.SpawnTraceEnabled`) |
|
||||
| `ACDREAM_DUMP_PLAYSCRIPT` | `="1"` (ordinal) | Traces PhysicsScript playback: missing/empty script resolution, malformed `StartTime` entries, and other `[pes]`-prefixed hook-dispatch events | print-only (`Console.WriteLine`) at all 4 use sites (`:85-86,136,300,328`) | unset (off) | `PhysicsScriptRunner.DiagEnabled` (`PhysicsScriptRunner.cs:61-62`) — per-instance settable property seeded from the env var, not a shared static diagnostics-owner class |
|
||||
| `ACDREAM_DUMP_SURFACES` | `="1"` (ordinal) | One-shot (per session) surface-format histogram dump for the atlas-opportunity audit — fires once after `_dumpFrameCounter>=600` OnRender ticks AND `_uploadMetadata.Count>=100` uploaded textures; writes to the host diagnostics directory | Doc comment claims "Zero cost when off" but `_uploadMetadata[name]=(w,h,fmt)` (`TextureCache.cs:1042`) is written **unconditionally on every texture upload regardless of the flag** — real (small) always-on dictionary-write cost. `TickSurfaceHistogramDumpIfEnabled` also re-reads `Environment.GetEnvironmentVariable` every OnRender frame (not cached) until the one-shot fires. Dump-write failures are caught and logged to stderr, not fatal. | unset (off) | `TextureCache` (`TextureCache.cs:102-113` fields, gate at `TextureCache.cs:802-812`, dump at `TextureCache.cs:814-829`), Phase N.6 slice 1 |
|
||||
| `ACDREAM_FRAME_PROF` | `=1` | master toggle for the frame profiler: CPU frame time, GPU time samples, per-stage CPU attribution, per-frame alloc/GC, `[frame-prof]` report every ~5 s (doc: "permanent apparatus ... do not strip with session probes") | when on, samples `GC.GetAllocatedBytesForCurrentThread()` and stage-scope timing every frame (cheap, by design); its own XML doc claims a GPU-query self-disable tied to `ACDREAM_WB_DIAG=1` that `FrameProfiler.cs` says no longer exists — see Notes #1; startup-only in practice (its DebugPanel/DebugVM mirror is unreachable — #434)| false (off) | `RenderingDiagnostics.FrameProfEnabled` / `FrameProfiler` |
|
||||
| `ACDREAM_PROBE_ENVCELL` | `=1` | emits one `[envcells]` line per indoor frame: `CellsRendered`/`TrianglesDrawn` + ourBldgs/otherBldgs/filter counts (phase a8 relic; its own render pass was removed but the probe was kept) | print-only; implicitly turned on whenever `ACDREAM_PROBE_VIS` is on (getter is `_probeEnvCellEnabled \ | \ | `RenderingDiagnostics.ProbeEnvCellEnabled` (backing field OR'd with `ProbeVisibilityEnabled`) |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue