S4-c1 per docs/research/2026-09-01-overhaul/s4-depth-alpha-packet.md §6.
S3 chunk 2 already landed the persistent portalsDrawnCount latch, the
gated clear, the exit-seal counting, and the look-in isolation — this
chunk covers only what §1/§2 of the packet name as still owed: C0-C3.
C0 — far-punch depth constant (R1: DrawPortalPolyInternal @0x0059bc90's
tail). portal_depth.vert's punch branch carried the decimal 0.99999988,
which reinterprets as bits 0x3F7FFFFE — fifteen ULPs FARTHER from the
camera than retail's real constant, bits 0x3F7FFFEF. Now writes
`uintBitsToFloat(0x3F7FFFEFu)` so the exact bits survive the GLSL/SPIR-V
compiler instead of trusting a decimal literal to round-trip unchanged.
Recompiled via tools/compile-shaders.ps1 (glslc 1.4.350.0 backend
recorded, managed shaderc path used); portal_depth.vert.spv's SHA-256
re-pinned in VulkanShaderManifestTests
(51c60d0924d62c61548efcf5f9e7672a121b1b68ca0a06755e32f1a4d73a8acf,
was 4ac1c452e7ac0d08a32f67fb03f21229af2d1605baa81f407240a3626251dfd7).
T1 (new Fact PortalDepthVert_FarPunchConstant_MatchesRetailExactBits in
VulkanShaderManifestTests.cs): a SOURCE pin — reads portal_depth.vert's
punch line and reinterprets whatever literal it carries (uintBitsToFloat
hex or a plain decimal) as raw bits, asserts == 0x3F7FFFEF. Verified
against the PRE-CHANGE source by hand-reverting the line to
`clipPos.z = clipPos.w * 0.99999988;` and re-running just this test:
Assert.Equal() Failure: Values differ
Expected: 1065353199
Actual: 1065353214
(1065353199 = 0x3F7FFFEF, 1065353214 = 0x3F7FFFFE). Line restored and
the test re-confirmed green afterward. MUTATION: any other literal fails
the same way.
C1 — the ±12 local-input reject (R2: 0x59BCD6-0x59BD28 then
0x59BD40-0x59BD66). The Ghidra arbitration table in
oh1-depth-lifecycle.md governs over the pseudo-C's own nested-if reading
of the four x87 FCOM results (BinaryNinja's `test ah, 0x44` condition
synthesis is FPU-flag-ambiguous and reads backward at face value — see
feedback_bn_decomp_field_names.md on decompiler flag mush as an artifact
class, not semantics): the table's row says "whole poly on any
local-input x/y == +/-12 boundary is rejected before count/clip" — taken
as written, not re-derived from the pseudo-C's literal branch nesting.
Ported as one shared predicate,
WalkVisibilityMath.IsRejectedByPortalPolygonBoundaryGuard(ReadOnlySpan
<Vector3>): true iff any vertex's X or Y is exactly +12f/-12f (retail
tests LOCAL x/y before xformStart, the world transform). Wired at BOTH
producers that own the LOCAL polygon before it leaves cell/building
space:
- WalkFrameDriver.OnPunchGeometry (the walk's punch-event producer,
IWalkEventSink.OnPunchGeometry) — checked on the building-local
WalkPolygon.Vertices before TransformToWorld; a hit returns before
MarkIfGrown/any event append (retail's reject -> transform -> clip
-> count order).
- RetailPViewPassExecutor.DrawPortalDepthWrite (the exit-seal
enumeration behind DrawExitPortalMask, the sole caller) — checked on
cell.PortalPolygons[index]'s local vertices before the
Vector3.Transform loop; a hit `continue`s with no `submitted++`.
T2 (three layers):
1. WalkVisibilityMathTests.cs — direct unit tests of the predicate:
Boundary_guard_rejects_a_polygon_with_one_vertex_exactly_on_plus_minus_12
(Theory, x/y == +-12 each), Boundary_guard_admits_a_polygon_whose_
nearest_vertex_is_just_inside_12 (Theory, x/y == +-11.999),
Boundary_guard_rejects_the_whole_polygon_even_when_only_one_of_
several_vertices_hits_it, Boundary_guard_ignores_the_vertical_z_
component, Boundary_guard_admits_the_empty_polygon.
2. WalkFrameDriverTests.OnPunchGeometry_RejectsWholePolygonOnExact
PlusMinus12LocalVertex_ButPunchesJustInside — functional: feeds
OnPunchGeometry a polygon with a vertex at x=12 (no PunchFan/no
"PUNCH:" log line) then one at x=11.999 (punches normally,
leaf.Punches has exactly one entry, log has exactly one "PUNCH:3@v0").
3. RetailPViewPassExecutorTests.DrawPortalDepthWrite_RejectsDegenerate
LocalPolygons_BeforeTransformOrSubmission — a real functional test of
DrawPortalDepthWrite needs a live PortalDepthMaskRenderer the suite
has no fake for, so this is a compiled-call-graph pin (this file's
established pattern for exactly this situation): the guard call
precedes both the Vector3.Transform loop and
PortalDepthMaskRenderer.DrawDepthFan by IL offset, gated by a
conditional branch immediately after it.
MUTATION texts, all verified live during this session then reverted:
- OnPunchGeometry_RejectsWholePolygon... with the C1 guard deleted from
OnPunchGeometry:
Assert.Single() Failure: The collection contained 2 items
Collection: [WalkPolygon { Plane = WalkPlane { Normal = <0, 0, 1>, D = -3 }, Vertices = [<0, 0, 3>, <12, 0, 3>, <5, 5, 3>] }, WalkPolygon { Plane = WalkPlane { Normal = <0, 0, 1>, D = -3 }, Vertices = [<0, 0, 3>, <11.999, 0, 3>, <5, 5, 3>] }]
- DrawPortalDepthWrite_RejectsDegenerateLocalPolygons... with the C1
guard deleted from DrawPortalDepthWrite:
Expected call to WalkVisibilityMath.IsRejectedByPortalPolygonBoundaryGuard.
- Boundary_guard_admits_a_polygon_whose_nearest_vertex_is_just_inside_12
with the predicate widened to `MathF.Abs(x) >= 11.99f ||
MathF.Abs(y) >= 11.99f` (all four rows):
Assert.False() Failure
Expected: False
Actual: True
- Boundary_guard_rejects_a_polygon_with_one_vertex_exactly_on_plus_
minus_12 with the predicate narrowed to strict `x > 12f || x < -12f
|| y > 12f || y < -12f` (all four rows):
Assert.True() Failure
Expected: True
Actual: False
- Boundary_guard_rejects_the_whole_polygon_even_when_only_one_of_
several_vertices_hits_it with the guard checking only
localVertices[0] instead of looping every vertex:
Assert.True() Failure
Expected: True
Actual: False
C2 — no pipeline change for R3 (depth ALWAYS/write/no-cull, color writes
ENABLED with a zero-alpha SRCALPHA/INVSRCALPHA blend). acdream's
PortalDepthMaskRenderer.Rhi.cs:92,100 sets ColorWrite=false alongside
Blend=None; portal_depth.frag writes no color output at all. Provably
pixel-identical (retail's blend collapses to dst'=dst when srcAlpha is
fixed at 0, for any RGB) and the write mask is the SAFER mechanism going
forward (structurally blocks any future accidental color write,
independent of an authored zero-alpha invariant). Added register row
AD-119 to docs/architecture/retail-divergence-register.md (the next free
id after AD-118), citing DrawPortalPolyInternal @0x0059bc90 and
PortalDepthMaskRenderer.Rhi.cs; section 2's active-row count and running
header note updated (90 -> 91).
C3 — the truth table + cross-frame latch tests. The (root kind,
draw_landscape, outside-view count, previous count) table's cells are
mostly already covered by S3 chunk 2's own tests — this chunk adds only
the genuinely missing rows/cases, and leaves every existing test
untouched:
Pre-existing coverage (named, not reproduced):
- interior, ov==0, prior==0 ->
RunFrame_InteriorFloodWithNoExitView_SkipsLandscapeAndNeverFlushesClearsOrSeals
- interior, ov>0, prior==0 ->
RunFrame_InteriorFloodWithExitView_FreshDriverSkipsTheGatedClearThenDrawsSealsAndFloodCells
and OnInteriorFloodDrawTurn_FirstOvFrameSkipsClear_SecondFrameArmedByFirstsSealsClears
(its own frame 1)
- interior, ov>0, prior>0 (T4's "frame 1 seals N>0 -> frame 2
clears" half) ->
OnInteriorFloodDrawTurn_FirstOvFrameSkipsClear_SecondFrameArmedByFirstsSealsClears
(its own frame 2)
- T4's "frame 1 seals 0 -> frame 2 does not clear" half (repeated
across three consecutive ov>0 frames, subsuming the two-frame
case) -> OnInteriorFloodDrawTurn_FloodWithNoExitPortal_NeverClearsAcrossFrames
- one look-in isolated from the root latch ->
LookInDrawCells_NeitherArmsNorConsumesThePortalsDrawnCounter
No further T4 test was added — the two existing facts above already
prove both halves of the two-consecutive-frames latch case exactly.
New rows added this chunk:
- WalkFrame_OutdoorRoot_NeverFiresTheInteriorClearSealMachinery: root
kind == OUTDOOR. RetailFrameWalk.WalkFrame's outdoor branch
((cameraCellId & 0xFFFF) < 0x100) calls DrawLandscape directly and
never calls DrawInside/OnInteriorFloodDrawTurn at all, so the whole
LFLUSH/stamp/CLEAR/SEALS mechanism structurally cannot fire —
driven end-to-end through RunFrame with an outdoor cameraCellId,
asserting SKY present, LFLUSH/CLEAR/SEALS absent, counter stays 0.
MUTATION (verified, then reverted): added a stray
`sink.OnInteriorFloodDrawTurn([], 1);` call to WalkFrame's outdoor
branch:
Assert.DoesNotContain() Failure: Item found in collection
↓ (pos 1)
Collection: ["SKY", "LFLUSH", "SEALS"]
Found: "LFLUSH"
- OnInteriorFloodDrawTurn_OvZeroAfterAPriorArmedCounter_LeavesTheLatch
CompletelyUntouched: interior, ov==0 immediately after an EARLIER
ov>0 frame armed the counter — proves the counter is left EXACTLY
as an earlier frame left it (not merely "not cleared this frame"),
since S3 §8.1 R3 gates the ENTIRE outside_view.view_count>0 block,
including the read-then-zero decision itself, on ov>0.
MUTATION (verified, then reverted): moved
`int armed = PortalsDrawnCount; PortalsDrawnCount = 0;` out of the
`if (outsideViewCount > 0)` gate in
WalkFrameDriver.OnInteriorFloodDrawTurn (unconditional
read-then-zero every call):
Assert.Equal() Failure: Values differ
Expected: 1
Actual: 0
(every OTHER WalkFrameDriverTests fact stayed green under this same
mutation — this new test is the only one that catches it).
- MultipleLookIns_WithinOneFrameAndAcrossFrames_NeverTouchTheRootLatch
(T5): extends the single-look-in fact to TWO look-ins in one frame
then a THIRD in a later frame. MUTATION (verified, then reverted):
a `_mutationLookInCalls` counter in HandleDrawCellsTurn's
LookInStatic branch that resets PortalsDrawnCount on the SECOND
look-in call:
Assert.Equal() Failure: Values differ
Expected: 1
Actual: 0
— while LookInDrawCells_NeitherArmsNorConsumesThePortalsDrawnCounter
(one look-in only) stayed green under the identical mutation,
confirming this test's incremental value over the existing single-
look-in fact.
Gates: dotnet build (App.Tests and App) 0 warnings/0 errors; hermetic
lane 6832/6832 passed; InstalledDat lane against
C:/Users/erikn/Documents/Asheron's Call — exactly the four known
failures (TowerAscentReplayTests.TowerAscent_StaircaseStaysConeVisible_
EveryStep, LayoutImporterMediaBearingChildSweepTests.
MainGameUiAndChatInput_MediaBearingChildrenNowBuildAsRealWidgets and
LayoutImporterInvisibleSweepTests.EveryAuthoredInvisibleWidget_
StartsHiddenAcrossAllLayouts — both #383 — and
WalkTraceConformanceTests.Oh_doorway_still_first_frame_diff #458),
243 passed / 1 skipped / 4 failed / 248 total, no new failures; shader
tests (VulkanShaderDescriptorContractTests/VulkanShaderManifestTests/
RenderPackSpirvValidatorTests/SkyVertexLayoutTests) 35/35; register
tests (Divergence|Register filter) 52/52.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| architecture | ||
| audit | ||
| plans | ||
| reference | ||
| render-packs | ||
| reports | ||
| research | ||
| reviews | ||
| superpowers | ||
| bugs.md | ||
| ci-and-releases.md | ||
| ISSUES.md | ||
| launch-options.md | ||
| README.md | ||
| release-gate.md | ||
acdream documentation map
This page is the entry point for project documentation. It distinguishes current sources of truth from implementation history so an old plan or issue banner cannot silently override the current program state.
Current snapshot — 2026-07-27
- Milestone state: M3, “Cast a spell,” landed 2026-07-21. M4, “Live in the world,” is active.
- M4 gameplay program: resume the pre-M4 world-interaction completion program. Favorite-spell overflow, status Use/Assess, and the complete assessment surface are user-accepted. Equipped-child picking and vendor browse/transactions remain Slices 4–6.
- Structural/runtime state: all eight
GameWindowdecomposition slices, Modern Runtime Slices A–J, and the connected visual/lifecycle gates are complete.GameWindowis a 1,622-line composition/callback shell.AcDream.Runtime.GameRuntimeowns canonical session, entity/object, gameplay, movement, physics, projectile, environment, and portal state; graphical and no-window hosts borrow the same owner graph. - Headless state: Slice K is complete.
AcDream.Headlessis a presentation-free Windows/Linux host with deterministic commands/events, shared immutable content, multi-session isolation, reconnect, resource telemetry, and 1/5/10/30-session gates. The final two-account native-Linux soak completed ten minutes, logged out through ACE, and converged every ownership ledger. - Linux graphical state: Slice L0 and the L1 implementation checkpoint are
complete at
66f114b2and11501d52. Native Windows passes the active modern-GL/audio/window smoke. WSLg X11/Wayland correctly reject their missingGL_ARB_bindless_texture. Physical-Linux validation and L2–L6 are explicitly deferred; resume at the supported AMD/NVIDIA L1 gate. - Completed gameplay gates: R6 locomotion/collision/projectile/teleport/ radar, two-client portal-out/materialization, indoor prepared collision, loot ordering, local/remote ground drops, and selection-marker lifetime.
- Separate visual verification: issue
#225, the shared-alpha lifestone/particle result; its connected resource-lifetime and performance routes pass. - Carried behaviour debt: issue
#153(far teleport onto an unstreamed edge),#116(narrowed slide response),#235(capped/RDP jump cadence), and the active temporary-stopgap rows in the divergence register. - Divergence audit: 189 active rows — IA 18, AD 38, AP 91, TS 38, and UN 4 — plus retained struck/retired historical rows such as TS-37.
- Latest automated baseline: the Release build succeeds with the 17
test-project warnings tracked by issue
#228; 8,826 tests pass and five are intentionally skipped. App passes 3,763 / 3 skips. The L1 Windows supported smoke and WSLg X11/Wayland negative-capability reports all end with zero window/GL/input/audio ownership.
Sources of truth
Read these in this order when deciding what to do next:
plans/2026-05-12-milestones.md— the active playable outcome, freeze boundaries, and visual gates.plans/2026-04-11-roadmap.md— strategic phase ledger: shipped, active, deferred, and future work.ISSUES.md— tactical defects and small follow-ups. The status inside an issue is authoritative; physical order is not.architecture/retail-divergence-register.md— every known place runtime behavior can differ from retail.architecture/acdream-architecture.mdandarchitecture/code-structure.md— ownership, dependency, update-thread, and extraction rules.architecture/worldbuilder-inventory.md— rendering/DAT code already owned in-tree versus mechanisms still ours to port.
If these disagree, milestones control the current outcome, the roadmap controls work ordering, the issue status controls the individual defect, and the architecture documents control implementation shape. Reconcile the stale document in the same change; do not leave both claims standing.
Research and implementation records
research/named-retail/is the primary retail oracle: named pseudo-C, headers, symbols, and types from the Sept 2013 build.research/decompiled/is the older Ghidra fallback.research/contains focused pseudocode, traces, fixtures, and gate reports. A dated research note records evidence; it does not become a new roadmap.superpowers/specs/andsuperpowers/plans/are per-slice design and execution records. Completed plans remain historical.ci-and-releases.mdis the SSOT for the Gitea CI pipeline, the self-hosted runners, and how alpha releases are published. Load-sensitive tests live inLane=Timing; seerelease-gate.mdbefore adding to it.launch-options.mdis the SSOT for every environment variable and command-line argument the client reads, including what each one changes about the run beyond its obvious effect. Read the side-effects column before trusting any measurement. Enforced byLaunchOptionsDocumentationTests: a flag without a row fails the build, and so does a row whose read site was deleted.audit/contains completion and conformance audits.reference/ace-commands.mdpreserves the local ACE server's complete in-game command catalog and points to the authoritative per-command help surface.
Durable memory
../claude-memory/MEMORY.mdindexes the live subsystem memories and the render/physics digests. Read a domain digest before changing that subsystem, especially its DO-NOT-RETRY table.../memory/contains stable engineering references such as the modern rendering pipeline, two-tier streaming, and toolchain notes.
Memory accelerates recall; it does not outrank the canonical documents above. When current truth changes, update the relevant canonical document and distill only the durable lesson into memory.
Historical and deprecated documents
bugs.mdis the April 2026 bug snapshot. It is preserved for archaeology and is not an active ledger.- Dated plans and specs describe the decision at that time. Their completion wording is historical unless the current milestone/roadmap explicitly links the item as active.
- Old
R1→R8architecture sequencing is superseded. Current execution comes from the milestones and strategic roadmap.
Documentation maintenance rules
- Update milestone, roadmap, issue, divergence, architecture, and memory claims in the same commit when a shipped change affects them.
- Keep one issue ID per defect. Narrow an issue in place; do not reuse another issue's number as a shorthand.
- Mark automated, connected, and visual gates separately. An automated pass is not a visual acceptance, and an RDP throughput sample is not a local-display visual comparison.
- Preserve research history, but remove stale “current/next” claims from living documents once the state advances.