Commit graph

1085 commits

Author SHA1 Message Date
Erik
c6bc2b9980 fix(research): A6.P3 slice 1 T1 — citation corrections + LKCP re-latch note
Code-review feedback on commit 6b4be7f:
  - Section 1: strip stale [309NNN] inline annotations (off by 2-8
    lines from actual file content; the 0052c1xx address comments
    are the reliable anchor); address comments already present in the
    decomp output are now used as inline anchors instead
  - Section 2: validate_transition function header is at file line
    272547 (was: 272538, inside the preceding check_collisions
    function). Address 0050aa70 + LKCP-block range 272565-272583
    were already correct. References section updated to match.
  - Section 5: add note that SetContactPlane re-latches LKCP fields
    (no-op when LKCP is the source, but non-obvious side effect)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 08:20:24 +02:00
Erik
6b4be7f863 docs(research): A6.P3 slice 1 — retail Mechanism B oracle for CP retention
Pre-fix research note grounding the indoor CP-retention refactor in
retail's exact LKCP-restore pattern (acclient_2013_pseudo_c.txt:272565-272582)
and CEnvCell::find_env_collisions tiny shape (line 309573).

Key findings:
- find_env_collisions writes NO ContactPlane — only BSP Path 6 does (Mech A)
- validate_transition Collided/Slid/Adjusted branch calls set_contact_plane
  from LKCP when proximity guard passes (global_curr_center, not global_sphere)
- Our ValidateTransition is missing the SetContactPlane call in that branch
  (sets Contact/OnWalkable flags only) — this is the gap Task 4 closes
- Proximity sphere should be GlobalCurrCenter[0] not GlobalSphere[0]
- Exact insertion point: TransitionTypes.cs ~line 2849, inside the
  'radius + EPSILON > |angle|' proximity-guard branch

Output of this note drives the per-transition Mechanism B insertion
point selection in Task 4 + the slice-1 acceptance shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 08:09:40 +02:00
Erik
ba9655f6f7 plan(phys): A6.P3 slice 1 — indoor ContactPlane retention (Finding 2 fix)
Eight-task plan to close A6.P2 Finding 2 (ContactPlane resynthesis
blowup, ~1,470x more CP writes than retail). Strategy: strip the
synthesis path inside Transition.FindEnvCollisions indoor branch +
add per-transition Mechanism B (LKCP restore) so cross-frame CP
retention flows via the existing retail mechanisms instead of
per-frame TryFindIndoorWalkablePlane synthesis.

Plan structure:
  T1 — Research note (retail Mechanism B oracle) — mandatory before code.
  T2 — Add ContactPlaneWriteCount probe (test instrumentation).
  T3 — Write failing IndoorContactPlaneRetentionTests regression.
  T4 — Add Mechanism B (LKCP restore) per-transition.
  T5 — Strip indoor walkable synthesis from FindEnvCollisions.
  T6 — Re-capture scen3 + verify cp-write ratio drops to ≤200.
  T7 — Re-capture scen1 + scen5 for full slice 1 sign-off.
  T8 — Bookkeeping (findings doc, roadmap, CLAUDE.md).

Out of scope (deferred to slice 2 or A6.P4):
  - Mechanism C (frames_stationary_fall flat-CP synthesis); add only
    if slice 1 visual verification shows first-frame fall-through.
  - Finding 3 (cell-resolver sling-out); independent fix surface.
  - TryFindIndoorWalkablePlane definition deletion (A6.P4).
  - Issue #95 (visibility blowup; outside A6 scope).

Acceptance: scen3 cp-write ≤ 200 (vs current 86,748); scen1/5 ratio
≤ 10x; visual verification at Holtburg inn 2nd floor passes;
1147+8 baseline maintained.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:27:38 +02:00
Erik
90fbdc02df docs(roadmap+milestones): mark A6.P1/A6.P2 shipped; update M1.5 demo
A6.P1 (cdb probe spike) + A6.P2 (analysis report) both SHIPPED this
session. Updated:

  docs/plans/2026-04-11-roadmap.md — M1.5 phase block now shows A6.P1
  + A6.P2 SHIPPED with commit refs; A6.P3 entry expanded with the
  Finding-2-first sequencing recommendation from A6.P2; A6.P4 entry
  notes the original "Holtburg Sewer end-to-end" acceptance walk is
  unreachable (sewer doesn't exist).

  docs/plans/2026-05-12-milestones.md — M1.5 demo scenario split into
  building/cellar half (achievable post-A6.P3) + dungeon half (blocked
  on issue #95 visibility blowup; promote to post-M1.5 if #95 isn't
  fixed in scope). Issue list updated: added #95 + indoor sling-out
  (new from scen4); marked stairs/2nd-floor/cellar as characterized by
  A6.P2 Finding 2 family.

  CLAUDE.md — Currently-working-toward block now points at A6.P3 as
  the active phase. A6.P1 + A6.P2 ship noted with the findings doc
  pointer. Demo-scenario note updated to reflect the sewer + #95
  reality. Issues-in-scope updated.

Also includes a 1-line trailing-prompt addition to scen3 + scen4
retail.log files (cdb wrote one more `0:000>` after the kill that
landed after the original capture commits).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:17:21 +02:00
Erik
184933d796 docs(research): A6.P2 — capture findings report (5 of 9 scenarios)
Replaces the A6.P1 stub with the analysis pass over 5 paired captures
(scen1-5). Scen6-9 (sewer-specific) cancelled because the Holtburg Sewer
doesn't exist on this ACE server and any substitute dungeon hits issue
#95 (portal-graph visibility blowup) on entry.

Four findings ready for A6.P3 sequencing:

  Finding 1 — Dispatcher entry frequency mismatch (4x to 281x fewer in
              acdream). Likely secondary effect; may close as side-effect
              of Finding 2 fix.

  Finding 2 — ContactPlane resynthesis blowup. 250x to INFINITE more CP
              writes in acdream. Strongest single signal; scen3 shows
              retail wrote CP zero times during a flat 2nd-floor walk
              while acdream wrote 86,748 field updates. Primary M1.5
              root cause. HIGH severity.

  Finding 3 — Indoor cell-resolver sling-out (scen4). Resolver flings
              +Acdream across landblock boundary; CheckBuildingTransit
              fires 5,495 times during the sling while indoor BSP is
              barely queried. Same family as the M1.5 cell-tracking
              ping-pong hypothesis. HIGH severity.

  Finding 4 — Portal-graph visibility blowup (scen5 incidental). Filed
              as issue #95; not strictly A6 scope but documented here so
              A6.P3 sequencing knows about it.

Tables 1+2 (per-site push-back delta + path-frequency diff) deferred to
A6.P1.5: the v4 cdb probe captures function entry only, not exit values.
Adding paired exit BPs is ~1 hour of cdb scripting work but not needed
unless A6.P3 fixes fail to close the symptoms.

Table 3 (CP lifecycle) fully populated — geometric mean CP-write ratio
across 4 finite scenarios is ~1,470x; median ~2,200x.
Table 4 (sub-step state mutations) partially populated with proxy
metrics (per-tag firing rates).

M1.5 symptom coverage matrix: every in-scope physics symptom maps to
at least one finding. Acceptance per spec §4.7 met.

A6.P3 sequencing recommendation: Finding 2 first (highest-confidence
single-cause; may close Finding 1 as side effect), re-run captures, then
Finding 3. Issue #95 handled separately outside A6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:13:07 +02:00
Erik
5be784eee3 file: issue #95 — dungeon portal-graph visibility blowup
Filed during A6.P1 scen5 capture (Town Network as substitute for the
nonexistent Holtburg Sewer). After portal teleport, visibleCells per
cell explodes from ~4 to 135-145, with cells from multiple disconnected
landblocks loaded simultaneously — direct cause of the user-observed
"see through walls / other dungeons rendering" failure across all
portal-accessed dungeons.

This is what "dungeons are broken" means as a coherent failure mode.
Scen6-9 (sewer corridor/chamber/stair) as originally scripted couldn't
have produced clean physics-only captures because the dungeon would
have been visually unusable from the moment of portal entry. The A6.P1
scenario script was written before the visibility bug was characterized.

Evidence is in scen5's acdream.log (already committed at 35d5c58).
Scen6-9 are not captured — the visibility bug blocks the scenarios.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:08:48 +02:00
Erik
35d5c58c7b capture(research): A6.P1 scen5 — town network portal entry paired traces
Substituted "Holtburg Sewer" portal with Town Network Portal — no
sewer entry exists in this world (user-verified). Town Network is
also an outdoor->indoor portal transition with the same physics
signature.

Both clients walked to the portal, entered, walked 2 m inside.
Retail: clean traversal. Acdream: also clean (no failure mode).

Retail (decoded, 23,890 raw / 9,769 BP lines):
  BP1 transitional_insert: 13,863
  BP4 find_collisions:      9,552
  BP5 adjust_sphere:           97
  BP6 check_walkable:          55
  BP7 set_contact_plane:       65  (moderate, portal threshold + indoor)
  BP2 step_up:                  1

Acdream (31,914 lines, no failure):
  [cp-write]:        20,956  (vs retail BP7 = 65 — ~322x ratio)
  [cell-cache]:       9,642  (Holtburg landblock streaming)
  [check-bldg]:         740
  [push-back-disp]:      34  (flat-ground walking)
  [push-back]:            1
  [cell-transit]:        12  (CLEAN traversal, no thrashing)

cell-transit event chain — captures the portal entry signature:
  0x00000000 -> 0xA9B30030  (login teleport)
  0xA9B30030 -> 0xA9B40029 -> 0xA9B40021 -> 0xA9B40019 ->
  0xA9B40011 -> 0xA9B40012 -> 0xA9B4000A -> 0xA9B4000B ->
  0xA9B40003  (walked across Holtburg, all reason=resolver)
  0xA9B40003 -> 0x00070143 reason=teleport  (PORTAL ENTRY)

scen5 is the "control" — both clients reached their target, no
visible failure. The CP-write blowup persists as the only A6.P2
divergence. Useful baseline for separating "indoor physics broken
during walking" (scen2, scen3, scen4) from "indoor physics okay
when portal-delivered" (scen5).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:59:00 +02:00
Erik
46c6e08ee5 capture(research): A6.P1 scen4 — cottage cellar paired traces
Asymmetric pair (scenario-level, not protocol-failure):
- Retail: user walked UP out of the cellar (ascent of 2 cellar
  steps + exit through doorway) — captures ascent + indoor-to-
  outdoor transition.
- Acdream: user teleported INTO the cellar, walked a few meters,
  the resolver flung +Acdream OUTSIDE the cottage entirely
  (landblock prefix changed A9B4 -> A9B3 mid-walk) — captures
  a real indoor physics failure that's not a stair issue per se.

Both traces are valuable to A6.P2 even though they don't match
walk-for-walk.

Retail (decoded, 22,536 raw / 12,875 decoded BP lines):
  BP1 transitional_insert:  9,402
  BP4 find_collisions:     12,596  (ended in mem-access error
                                    @ hit#12596 - cdb hit a null
                                    transition arg, dropped to
                                    interactive prompt; worth a
                                    note for A6.P2 retail edge)
  BP5 adjust_sphere:          136
  BP6 check_walkable:         128
  BP2 step_up:                 13  (2-step cellar = 13 vs scen2
                                    4-step inn = 188; non-linear)
  BP7 set_contact_plane:        3  (Finding 2 holds)

Acdream (42,001 lines, ended with sling-out):
  [cp-write]:        35,624
  [check-bldg]:       5,495  (CheckBuildingTransit fired
                              constantly trying to re-resolve
                              which building +Acdream was in)
  [cell-cache]:         540
  [push-back-disp]:      82  (very few dispatcher hits)
  [push-back]:            1  (almost no sphere-adjustment)
  [indoor-bsp]:           2  (indoor BSP barely queried!)
  [cell-transit]:         3  (3 transit events captured the sling:
                              0xA9B40148 -> 0xA9B40029 -> 0xA9B30030
                              all reason=resolver)

Sling-out signature: indoor BSP never engaged (only 2 indoor-bsp
hits), but the cell resolver fired 3 transit events crossing a
landblock boundary, with check-bldg thrashing in between. This is
distinct from scen2's stair-attempt pattern (which hammered the
BSP); scen4 shows the resolver pushing the character out of indoor
space entirely without triggering the indoor BSP collision path.

A6.P2 fix surface: investigate why ResolveCellId / CheckBuildingTransit
push a player from indoor cell 0xA9B40148 to outdoor cell 0xA9B30030
through routine walking. Likely the same family as the M1.5 hypothesis:
indoor cell membership isn't sticky (the ping-pong bug from the
2026-05-20 A4 handoff in a different guise).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:50:09 +02:00
Erik
4b5aebc61f capture(research): A6.P1 scen3 — inn 2nd floor paired traces
User reached the 2nd floor in acdream via ACE @teleport (stair-physics
unblocked separately by scen2). Retail walked normally to 2nd floor.
Both clients performed the same walk: forward 3 m, sidestep 1 m,
walk back. Flat-floor scenario, no stairs, no transitions.

Retail (decoded, 21,337 lines):
  BP1 transitional_insert: 10,217 hits
  BP4 find_collisions:     10,636 hits
  BP5 adjust_sphere:          113 hits
  BP6 check_walkable:         113 hits  threshold=0.6642
  BP2 step_up:                  0 hits  (no stairs)
  BP3 set_collide:              0 hits  (no walls)
  BP7 set_contact_plane:        0 hits  (KEY: zero CP updates)

Acdream (93,558 lines):
  [cp-write]:        86,748  (vs retail BP7 = 0 — INFINITE ratio)
  [push-back-disp]:   2,752
  [push-back]:          320
  [push-back-cell]:     550
  [other-cells]:        550
  [indoor-bsp]:       1,061
  [indoor-walkable]:    707

KEY FINDING for A6.P2: scen3 is the strongest CP-write blowup
evidence yet. On a flat 2nd-floor walk where retail's
set_contact_plane fires ZERO times across the entire scenario,
acdream rewrites the contact plane 86,748 times. This is the
exact pattern Finding 2 hypothesized (M1.5 design spec §1.2):
acdream resynthesizes CP every frame instead of retaining it
through the documented retention mechanisms (LKCP-restore,
Path-6 land write, post-OK step-down probe).

scen3 pair confirms CP-write blowup isn't stair-specific — it
fires equally for ordinary flat-floor walking inside any indoor
cell. A6.P3 fix surface: same as Finding 2 — stop resynthesizing
CP per frame.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:39:46 +02:00
Erik
297d1c54e8 capture(research): A6.P1 scen2 — replace acdream pair with stair-attempt
Original acdream capture (a9a427f) was a doorway-walk because acdream's
indoor stair physics doesn't work. For A6.P2 to characterize the
divergence we need the FAILURE captured, not a substitute walk.

User re-attempted the inn stairs in acdream (whatever it produces:
bumping, sliding, stuck). Failure signature is dramatic vs door-walk:

  Tag             | door-walk | stair-attempt | ratio
  ----------------+-----------+---------------+------
  push-back-disp  | 1,141     | 4,156         | 3.6x
  push-back-cell  | 87        | 1,478         | 17x
  other-cells     | 87        | 1,478         | 17x
  indoor-bsp      | 343       | 1,286         | 3.7x
  indoor-walkable | 227       | 859           | 3.8x
  cp-write        | 70,244    | 33,969        | 0.5x (!)

The 17x explosion on push-back-cell / other-cells says acdream's
CheckOtherCells loop fires constantly when physics can't resolve a
stair-step — the indoor BSP query fails, then the multi-cell
fallback fails, then the next tick repeats. The cp-write DROP
(half the door-walk volume) is the inverse signal: when no ground
plane resolves, no CP gets written. Both are A6.P2 fix-surface
indicators.

Now scen2 pair = retail successfully climbs (BP2 step_up=188) vs
acdream tries and fails (push-back-cell explosion).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:26:20 +02:00
Erik
a9a427fff9 capture(research): A6.P1 scen2 — inn stairs paired traces
Walked +Acdream up 4 steps of Holtburg inn, stopped on landing,
shuffled briefly to push past 50K cdb threshold. Acdream walk
exceeded the minimal scenario script (user explored further) —
scen2 dataset is a superset of the protocol; A6.P2 analysis can
window to the comparable section.

Retail (decoded, 110,104 lines):
  BP1 transitional_insert: 60,289 hits  (~5x scen1)
  BP2 step_up:                188 hits  (vs scen1 = 1 — stair signal)
  BP4 find_collisions:     47,783 hits  (~8x scen1)
  BP5 adjust_sphere:          780 hits  (~65x scen1)
  BP6 check_walkable:         677 hits  threshold=0.6642 confirmed
  BP7 set_contact_plane:      136 hits  (~7x scen1)

Acdream (73,937 lines):
  [cp-write]:        70,244 — CP-write blowup persists on stairs
  [push-back-disp]:   1,141
  [push-back]:          101
  [push-back-cell]:      87
  [indoor-bsp]:         343
  [indoor-walkable]:    227

Note: cdb's qd-in-BP-action threshold doesn't actually self-detach
(documented CLAUDE.md cdb watchout) — user closed retail manually.
Three remaining workflow steps work cleanly (decode + acdream pair
+ graceful close).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:21:05 +02:00
Erik
2f2b63f8bd docs(handoff): A6.P1 partial-ship — infra DONE, captures 1/9
Pickup-prompt + lessons doc for the A6.P1 capture work. Documents:

- The 16 commits shipping today (infrastructure Tasks 1-14 + cdb
  script v1→v4 iteration + scen1 capture + decoder).
- WHY cdb iterated 4 times: v1 wrong offsets, v2 PowerShell UTF-16,
  v3 cdb %f unreliable with dwo()/@@c++, v4 hex output works.
- Scen1 findings already strong: dispatcher entry frequency mismatch
  (acdream 20× fewer than retail) + ContactPlane write blowup
  (~100-1000× more frequent in acdream) — directly confirms the
  spec's M1.5 hypothesis about per-frame CP resynthesis.
- Per-scenario protocol validated by scen1.
- Pasteable session-start prompt for picking up scenarios 2-9.
- Known issues (kill-cdb-kills-retail, .printf %f unreliable, etc).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:05:17 +02:00
Erik
194ed3ef21 feat(cdb): A6.P1 — decode_retail_hex.py hex→float decoder
Python tool that decodes the retail.log hex-bits float fields produced
by a6-probe.cdb v4 into IEEE 754 single-precision values. Required
because cdb's .printf %f doesn't reliably format floats from dwo()
reads — v4 works around this by emitting 32-bit hex, this script
reinterprets via struct.unpack('<f', struct.pack('<I', value)).

Verified against scen1 retail.log:
  BP6 threshold_h=0x3F2A0751 → threshold=0.6642 (= FloorZ exactly)
  BP5 hit#1 Nz_h=0x3F800000 → Nz=1.0 (ground normal)
  9,517 float fields decoded across 9,331 lines.

Output written next to input as .decoded.log. Format matches
acdream-side [push-back] probe (4-decimal floats), so A6.P2
analysis can compare line-for-line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:03:03 +02:00
Erik
8ca718a56d capture(research): A6.P1 scen1 — acdream.log paired with retail.log
Acdream-side capture for the Holtburg inn doorway walk, paired with
the v4 retail capture committed at 180b4a5. 84,130 lines total.

Probe line distribution (~30 sec session, ~2 sec actual walk):
  [push-back] (adjust_sphere): 8 hits   — vs retail BP5 12 hits
  [push-back-disp] (dispatch): 295      — vs retail BP4 5818 (!)
  [push-back-cell] (other_cells): 5     — vs retail's check_other_cells
  [indoor-bsp]: 26
  [cell-transit]: 30 (cell ID changes)
  [cp-write]: 73,304 (per-field writes) — vs retail BP7 18 fn calls (!)
  [cell-cache]: 540

Two major divergences already visible from this single scenario:

1. DISPATCH FREQUENCY: retail's BSPTREE::find_collisions fires 20×
   more than acdream's BSPQuery.FindCollisions. Could reflect either
   different physics tick rate, different sub-step cadence, or
   different call paths into the dispatcher.

2. CONTACTPLANE LIFECYCLE: acdream writes CP fields 73,304 times
   in 30 seconds (~2,400/sec). Retail calls set_contact_plane 18
   times (~0.6/sec). Even with a 6× field-write multiplier per
   set_contact_plane call, that's ~100 actual CP updates in retail
   vs ~12K in acdream — 100-1000× more frequent in acdream. This
   directly confirms the spec's hypothesis that FindEnvCollisions
   indoor branch is rewriting CP every frame (sub-step?) instead
   of retaining it across frames. Same family as the
   TryFindIndoorWalkablePlane workaround.

Per-call shape comparison (BP5 hit#1):
  Retail: plane=(0,0,1) d=-0.0, sphere=(0.0046,10.31,-0.27) r=0.48,
          mvmt=(0,-0,-0.75), winterp=1.0
  Acdream: plane=(0,0,1) d=-0.0, sphere=(-0.43,11.02,0.46) r=0.48,
           mvmt=Z-down, winterp 1.0→0.96 (small adjust applied)
Identical operation SHAPE (ground plane + vertical step-down probe
+ same radius). XY positions differ because walks were independent.

Scenario 1 complete. Remaining 8 scenarios deferred per user
direction. Python hex→float decoder + A6.P1 handoff doc to follow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:02:08 +02:00
Erik
180b4a5010 capture(research): A6.P1 scen1 — retail.log with real hex-bits floats
v4 cdb probe captured paired field data for the Holtburg inn
doorway walk. 13,552 BP hits in ~2 sec of walking. Distribution:
  - BP1 transitional_insert: 7,686 (sub-step loop)
  - BP4 find_collisions:      5,818 (per cell per sub-step)
  - BP5 adjust_sphere_to_plane: 12 (the over-correction suspect)
  - BP6 check_walkable:        12
  - BP7 set_contact_plane:     18

Smoking-gun verification:
  BP6 threshold_h=0x3F2A0751 ≈ 0.664 = PhysicsGlobals.FloorZ
  BP5 plane normal = (0,0,1), movement = (0,-0,-0.75) — classic
       step-down probe against the ground polygon
  BP5 sphere radius = 0x3EF5C28F ≈ 0.480 m — player foot sphere

All hex-bits floats decode cleanly via Python struct.unpack('<f').
Decoder script TBD as part of the handoff.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 19:59:33 +02:00
Erik
2d841cb615 fix(cdb): A6.P1 — a6-probe.cdb v4 hex-bits floats
v3 with @@c++(*(float*)..) STILL produced 0.000000 across the board.
Conclusion: cdb's .printf %f is unreliable for our use case — possibly
doesn't handle the float-to-double promotion in varargs the way C
printf does, or has a deeper limitation we don't have time to debug.

Pivoting to: print all floats as 32-bit hex bits via %08X, reinterpret
in the Python analysis pipeline via struct.unpack('<f', bytes.fromhex(...))
to recover IEEE 754 single-precision values.

This bypasses cdb's float formatting entirely. Integer reads (which
work — substeps, insertType, collide flag, isWater) stay as %d.

The smoking gun: BP6's check_walkable threshold should be 0.0871556997
(cos 85°) per the decomp call site at acclient_2013_pseudo_c.txt:273202.
v4's BP6 should output threshold_h=0x3DB283D7. If it does, the
infrastructure is sound and we can proceed to all 9 scenarios.

v3 capture preserved as retail-v3-cpp-zero-floats.log audit trail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 19:55:48 +02:00
Erik
1b6d49ea57 fix(cdb): A6.P1 — a6-probe.cdb v3 with C++ float reads
v2 dry-run produced correct hit counts but all %f field values
printed as 0.000000 — including BP6 threshold which the decomp says
must be 0.0871556997f (cos 85°). Root cause: cdb's MASM evaluator
returns dwo(addr) as a 32-bit integer; .printf %f expects a 64-bit
double; passing the integer to %f produces formatted-zero garbage.

Fix: switch all float-reading expressions to @@c++(*(float*)addr).
The C++ evaluator dereferences memory as a float pointer, returning
a proper float that .printf %f formats correctly. Integer reads (%d)
still use MASM dwo() — that works.

For double-indirect (pointer args), the form is
  @@c++(*(float*)(*(unsigned int*)(@esp+N)+offset))
which reads the pointer at [esp+N], adds the offset, and treats the
result as a float pointer.

v2 capture preserved as retail-v2-zero-floats.log audit trail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 19:50:11 +02:00
Erik
7b9b26f647 fix(cdb): A6.P1 — a6-probe.cdb v2 with PDB-verified offsets
Replaces v1's broken-offset BP actions with PDB-authoritative field
reads. All offsets extracted from `dt acclient!TYPENAME` against the
loaded PDB (output preserved at tools/cdb/a6-types-dump.txt).

Key offsets:
  Plane.N at +0x00, .d at +0x0c
  CSphere.center at +0x00, .radius at +0x0c
  CPolygon.plane at +0x20
  SPHEREPATH.collide +0x104, .walkable_allowance +0x1b8, .walk_interp +0x1bc
  CTransition.sphere_path +0x020 (so e.g. CTransition+0x174 = insert_type)

Per-BP arg-read fixes (all use __thiscall: ecx=this, args at [esp+N]):
  BP1: substeps from [esp+4], insertType from this+0x174
  BP2: walkable_allowance from this+0x1d8, normal.z from *(arg+8)
  BP3: normal.x/y/z from *arg
  BP4: collide+insertType via *(arg2+0x124/0x174), walkAllow from arg3
  BP5 (the over-correction suspect): full plane + sphere + walk_interp +
       movement vector. 12 fields, all double-indirect for pointer args.
  BP6 SYMBOL FIXED: CTransition::check_walkable (v1 had
       validate_walkable which doesn't exist; check_walkable confirmed
       in symbols.json and at decomp line 272811).
  BP7: plane + isWater from *arg.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 19:43:50 +02:00
Erik
d0c8c54d96 fix(cdb): A6.P1 — v1 dry-run lessons + v2 prep tooling
Dry-run of scenario 1 (retail-v1-broken-offsets.log preserved as
audit trail) surfaced three issues with the v1 cdb script:

1. STACK-ARG OFFSETS WRONG: BP actions used arbitrary registers
   (@edx, @edi) to read function args, but __thiscall puts non-this
   args on the stack ([esp+N] after the return address). All 12 BP5
   "adjust_sphere" hits printed Nx=0.0 Ny=0.0 ... — fields not read.
   Fixed by writing a type dumper (a6-types-dump.cdb + runner) that
   uses cdb's `dt` command against the loaded PDB to get authoritative
   struct offsets. v2 probe script (to be written next) will use
   double-indirect reads (dwo(poi(@esp+N)+offset)) with correct
   offsets from the dump.

2. TEE-OBJECT UTF-16 ENCODING: PowerShell's default Tee-Object writes
   UTF-16 LE with BOM, making logs unparseable by grep without
   conversion. Runner now uses Out-File -Encoding ASCII. Sacrifices
   live console echo; use `Get-Content -Tail 50 -Wait` in a separate
   shell if live monitoring is needed.

3. BP6 SYMBOL NOT FOUND: `acclient!CTransition::validate_walkable`
   doesn't exist in the PDB. Decomp at line 272811 has
   `CTransition::check_walkable` — likely the actual name. To be
   verified + fixed in v2.

The BP hit-count distribution from v1 is still meaningful diagnostic
data (14,318 transitional_insert + 16,558 find_collisions + 40
set_contact_plane + 12 adjust_sphere + 1 step_up + 1 set_collide in
a 2-second walk through the inn doorway). Preserved as a baseline
sanity-check the v2 distribution can be diffed against.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 19:38:31 +02:00
Erik
22e341faf6 docs(research): A6.P1 — refresh PDB verification to MATCH
User swapped in the correct Sept 2013 EoR build acclient.exe.
GUID {9e847e2f-777c-4bd9-886c-22256bb87f32}, linker UTC
2013-09-06T00:17:56 — exact match for refs/acclient.pdb.
T15 captures are unblocked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:51:34 +02:00
Erik
260c60f8f5 docs(research): A6.P1 — capture directory structure + findings stub
Creates the 9 per-scenario capture directories (gitkeep stubs) and
the findings doc stub at docs/research/2026-05-21-a6-cdb-capture-findings.md.
A6.P1 fills the capture log slots (Task 15, user-driven); A6.P2
fills the analysis tables and findings section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:46:56 +02:00
Erik
0e21f22fc5 docs(research): A6.P1 — record retail-PDB match verification (MISMATCH)
Audit trail for the A6.P1 capture session: the retail binary at
C:\Turbine\Asheron's Call\acclient.exe is the 2015-06-12 build
(GUID {08e25c14-e2a1-46d5-b056-92b2e43a7234}), not the Sept 2013
EoR build that pairs with refs/acclient.pdb
(expected GUID {9e847e2f-777c-4bd9-886c-22256bb87f32}).

BP-driven A6 captures cannot proceed until the matching binary is
installed. User needs acclient.exe v11.4186 (linker timestamp
2013-09-06) to match our PDB.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:45:32 +02:00
Erik
df315a9654 docs(cdb): A6.P1 — README for the cdb probe + runner
Documents prerequisites (PDB match, cdb install, retail+ACE
running), per-scenario invocation, the 9-scenario tag table, and
the parallel acdream capture command. Includes the CLAUDE.md cdb
watchouts inline so probe operators don't have to chase them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:44:42 +02:00
Erik
1c640ebefa feat(cdb): A6.P1 — PowerShell runner for a6-probe.cdb
Wrapper that attaches cdb to a live retail acclient.exe with a
scenario-tagged log path. Per-scenario invocation:
  .\tools\cdb\a6-probe-runner.ps1 -ScenarioTag "scen1_inn_doorway"
Output: docs\research\2026-05-21-a6-captures\<ScenarioTag>\retail.log

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:43:34 +02:00
Erik
7bb799b02c feat(cdb): A6.P1 — 7-BP probe script for retail BSP collision response
Sets non-blocking breakpoints on transitional_insert, step_up,
set_collide, find_collisions, adjust_sphere_to_plane,
validate_walkable, set_contact_plane. Each BP increments a counter
and emits a single printf line. Auto-detach via qd at 50K total
hits to avoid retail lag (CLAUDE.md gotcha — high BP rates trigger
ACE timeout).

Also adds !tools/cdb/*.cdb negation to .gitignore so committed
reference scripts in tools/cdb/ are tracked despite the blanket
*.cdb scratch-file rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:41:29 +02:00
Erik
e1f7efe214 docs(CLAUDE): A6.P1 — document ACDREAM_PROBE_PUSH_BACK env var
Adds the new probe to the Diagnostic env vars list with hit-rate
estimate and cross-reference to the cdb probe script.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:39:42 +02:00
Erik
dd95c10162 feat(ui): A6.P1 — add ProbePushBack mirror to DebugVM
Runtime checkbox mirror for ProbePushBackEnabled. Toggling in
DebugPanel (under ACDREAM_DEVTOOLS=1) flips all three [push-back]
emission sites live without relaunch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:38:51 +02:00
Erik
642734dcd0 feat(physics): A6.P1 — instrument CheckOtherCells with [push-back-cell]
Wires LogPushBackCellTransit into the multi-cell BSP iteration loop
just before ApplyOtherCellResult halts. Captures primary/other
cell ids + BSP result for direct comparison to retail's
CTransition::check_other_cells loop (already ported as A4).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:38:03 +02:00
Erik
66ee757926 feat(physics): A6.P1 — add LogPushBackCellTransit helper
One-line per-iteration emission helper for the CheckOtherCells
multi-cell BSP loop. Captures primary/other cell ids, BSP result,
and halted flag for direct comparison to retail's
CTransition::check_other_cells loop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:36:42 +02:00
Erik
35631d1ec0 feat(physics): A6.P1 — instrument FindCollisions with [push-back-disp]
Wires LogPushBackDispatch into the modern FindCollisions overload
at the entry block (after path/collisions/obj locals + movement
computed). Legacy overload at line ~1895 delegates to modern, so
single instrumentation site covers all dispatches.

returnState=-1 sentinel marks "entry log" — A6.P2 analysis pairs
each entry with subsequent [push-back] adjust-sphere lines and
the eventual return state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:34:55 +02:00
Erik
2d1f27d647 feat(physics): A6.P1 — add LogPushBackDispatch helper
One-line per-call emission helper for the FindCollisions dispatcher
instrumentation site. Captures path-selection state (collide flag,
insertType, objState) + walk-interp + return state for direct
comparison to retail's BSPTREE::find_collisions breakpoint.
Output uses the [push-back-disp] tag to disambiguate from
[push-back] adjust-sphere events.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:32:37 +02:00
Erik
eb8a3186e7 feat(physics): A6.P1 — instrument AdjustSphereToPlane with [push-back]
Wires the LogPushBackAdjust helper into all three return paths
of AdjustSphereToPlane (early-return on no-movement, early-return
on interp out-of-window, and the applied path). Probe is gated by
ProbePushBackEnabled so it's zero-cost when off.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:30:46 +02:00
Erik
3a173b9616 feat(physics): A6.P1 — add LogPushBackAdjust helper
One-line per-call emission helper for the AdjustSphereToPlane
instrumentation site. Direct field-for-field paired comparison to
retail's CPolygon::adjust_sphere_to_plane breakpoint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:28:20 +02:00
Erik
ad6c89de33 fix(physics): A6.P1 — drop unresolvable <see cref> to private method
BSPQuery.AdjustSphereToPlane is private; <see cref> from outside the
class can't resolve and emits CS1574. Switched to <c>...</c> code
span. Other two cross-refs (FindCollisions public, CheckOtherCells
internal-same-assembly) keep their <see cref> form.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:27:00 +02:00
Erik
ace9e62213 feat(physics): A6.P1 — add ProbePushBackEnabled toggle
New PhysicsDiagnostics flag gates the [push-back] probe shipping
in subsequent tasks. Env-var ACDREAM_PROBE_PUSH_BACK=1 + DebugVM
mirror, matching the existing probe-toggle pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:24:22 +02:00
Erik
0bdd5c7fca docs(plan): Phase A6.P1 — cdb probe spike implementation plan
15-task TDD plan covering the three pieces of A6.P1:
  Phase A — Build the [push-back] acdream probe (Tasks 1-9):
    toggle + 3 helpers + 3 emission sites in BSPQuery/Transition,
    DebugVM mirror, CLAUDE.md env-var docs.
  Phase B — Build the cdb infrastructure (Tasks 10-12):
    7-BP cdb script, PowerShell runner, README.
  Phase C — Execute 9 captures + findings stub (Tasks 13-15):
    PDB-match verify, capture dir + findings stub, scenario captures.

API surface verified against current code: ResolvedPolygon has no
Id property (probe omits poly attribution; cross-ref via time-
adjacent [push-back-cell] line). CheckOtherCells locals are
sp.CheckCellId + cellId + result (verified at TransitionTypes.cs
lines 1418-1473). SpherePath has Collide/InsertType/WalkInterp,
ObjectInfo has State (verified).

Spec: docs/superpowers/specs/2026-05-21-phase-a6-indoor-physics-fidelity-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:07:37 +02:00
Erik
f9214433c3 docs(spec): Phase A6 — Indoor physics fidelity (cdb-driven) — design
Brainstormed + approved 2026-05-21 for M1.5 milestone work. Designs
the cdb probe spike methodology (7 retail breakpoints + new
[push-back] probe) to capture retail's per-tick BSP collision
response state at 9 indoor scenarios (4 buildings + 5 dungeon sites)
and compare against acdream. Working hypothesis: BSPQuery.AdjustSphereToPlane
or its callers over-correct vs retail, producing the family of
indoor symptoms (walls walk through, ping-pong, vibration, multi-Z
falling) plus driving the existing #90 + TryFindIndoorWalkablePlane
workarounds. A6 ships in 4 slices: P1 probe spike, P2 analysis,
P3 surgical fixes, P4 workaround removal + acceptance.

Phase O (DatPath Unification) pre-empted M1.5 and shipped 2026-05-21;
A6 resumes from Phase O state. Phase O only touched rendering/dat
code; indoor physics design is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:57:48 +02:00
Erik
2256006cb7 ship(O): Phase O — DatPath Unification — SHIPPED
ONE thing touches the DATs. WB code lives in our repo:
- src/AcDream.Core/Rendering/Wb/ — pure helpers (5 files, ~782 LOC)
- src/AcDream.App/Rendering/Wb/ — GL infra + mesh pipeline (~27 files, ~7K LOC)

Project references to WorldBuilder.Shared + Chorizite.OpenGLSDLBackend
dropped from AcDream.App.csproj and AcDream.Core.csproj.
references/WorldBuilder/ remains in-tree as read-reference only.

DefaultDatReaderWriter eliminated; DatCollection is the only dat reader.
WbMeshAdapter consumes our DatCollection via DatCollectionAdapter
(O-D7 fallback adapter; ObjectMeshManager has 26 _dats.X call sites,
exceeding the 20 refactor threshold).

Visual side-by-side passed: Holtburg town, inn interior, dungeon all
render identically to pre-O.

Doc updates:
- CLAUDE.md: rewrote WB integration cribs to point at extracted code.
  Code Structure Rules rule 2 updated to remove stale seam names.
  "Currently working toward" flipped from Phase O to M1.5 resumption.
- docs/architecture/worldbuilder-inventory.md: Phase O banner added.
  Status/integration model updated to post-O ownership. Workflow
  section updated to reference our extracted tree, not WB project ref.
- docs/plans/2026-04-11-roadmap.md: Phase O moved to shipped table.
  Phase O "ahead" block collapsed to SHIPPED note. M1.5 block updated
  to ACTIVE (Phase O shipped; resuming from 2026-05-20 baseline).
- docs/plans/2026-05-12-milestones.md: M1.5 heading updated to ACTIVE;
  Phase O ship writeup prepended to the M1.5 block.

Phase O ship closes Tasks O-T1..O-T7 shipped across this session.
Specs + audit + plan: docs/superpowers/{specs,plans}/2026-05-21-phase-o-*

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:41:15 +02:00
Erik
57ee19968c fix(O-T7): actually delete SplitFormulaDivergenceTest (drop workaround)
The previous T7 commit (dc722e7) and the housekeeping commit (3e6f6ec)
together left the file in the tree with a <Compile Remove> guard in
the csproj. Per spec O-T7 and CLAUDE.md "no workarounds without
approval" the file was supposed to be git-rm'd outright.

This commit:
- git rm tests/AcDream.Core.Tests/Terrain/SplitFormulaDivergenceTest.cs
  (the one-time N.5b data-collection sweep — job done at Phase N.5b ship)
- Removes the now-unneeded <Compile Remove> guard from
  tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj

Build green; tests green (1146 + 8 pre-existing failures baseline
maintained).

Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:30:30 +02:00
Erik
3e6f6ec858 chore(O-T7): code-review housekeeping after WB extraction
Five small post-cleanup items from T7 code review:

I1: Removed dead `datDir` parameter from WbMeshAdapter ctor (parameter
    was unused after _wbDats removal; ArgumentNullException.ThrowIfNull
    was misleading). Updated call sites in GameWindow.cs and
    WbMeshAdapterTests.cs.

I2: Updated stale GameWindow.cs comment that still described
    WbMeshAdapter as opening its own dat handles. Now reflects Phase O
    state: shared DatCollection via DatCollectionAdapter.

I3: Documented thread-safety contract on RenderStateCache (render-thread
    only — required for the mutable-static GL sentinel pattern).

M1: Added comment on IDatReaderWriter's write-path methods noting they
    are preserved for verbatim compatibility but unused in acdream.

M3: Added comment on Chorizite.Core PackageReference in Core.csproj
    explaining the previously-transitive dependency.

Also excluded SplitFormulaDivergenceTest.cs from the test build via
<Compile Remove>: this N.5b one-time data-collection test referenced
WorldBuilder.Shared types directly; after Phase O-T7 dropped that
project reference it no longer compiles. The sweep data it produced
already informed the N.5b Path-C decision and the file is retained
in the tree for historical reference.

Build green; tests green (1146 + 8 pre-existing failures baseline
maintained).

Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:29:06 +02:00
Erik
dc722e70bd feat(O-T7): drop WB project references; complete extraction
End of Phase O extraction. Final cleanup:

- Dropped <ProjectReference> entries to WorldBuilder.Shared and
  Chorizite.OpenGLSDLBackend from both AcDream.App.csproj and
  AcDream.Core.csproj.
- Added Chorizite.Core NuGet PackageReference to AcDream.Core.csproj
  (needed by Core.Rendering.Wb.TextureHelpers for TextureFormat enum;
  previously transitive through the WB project ref).
- Added BCnEncoder.Net.ImageSharp (1.1.2) + SixLabors.ImageSharp (3.1.12)
  as direct PackageReferences to AcDream.App.csproj — previously transitive
  via Chorizite.OpenGLSDLBackend project; used directly by ObjectMeshManager.

Item A (BaseObjectRenderManager static fields):
- Inlined CurrentAtlas/CurrentVAO/CurrentIBO into a new RenderStateCache.cs
  static class (AcDream.App.Rendering.Wb namespace) — the 4 consumers
  (ManagedGLIndexBuffer, ManagedGLTexture, ManagedGLTextureArray, ParticleBatcher)
  all reference RenderStateCache.* instead of BaseObjectRenderManager.*.
- Dropped using Chorizite.OpenGLSDLBackend.Lib from all 4 consumers and from
  WbDrawDispatcher (which had it only as a dead import).

Item B (ActiveParticleEmitter.ObjectLandblock):
- ObjectLandblock? erased to object?; WorldBuilder.Shared.Models.ObjectId? erased
  to ulong? — both fields are stored but never read by any consumer in our codebase.
- Dropped both WB using directives from ActiveParticleEmitter.cs.

Item C (IDatReaderWriter / IDatDatabase):
- Verbatim copy of both interfaces into IDatReaderWriter.cs in
  AcDream.App.Rendering.Wb namespace — DatCollectionAdapter and ObjectMeshManager
  already live in that namespace, so no using changes needed.
- Dropped using WorldBuilder.Shared.Services from DatCollectionAdapter.cs and
  ObjectMeshManager.cs.

Additional extractions required by the reference drop:
- GeometryUtils.cs: verbatim copy of WorldBuilder.Shared.Lib.GeometryUtils
  (float-precision overloads only; Vector3d double-precision overloads omitted —
  ObjectMeshManager uses only the float versions).
- Dropped using WorldBuilder.Shared.Lib from ObjectMeshManager.cs.

WbMeshAdapter.cs cleanup (spec O-D12):
- Deleted _wbDats (DefaultDatReaderWriter) field + ctor init + Dispose call.
- Deleted the [indoor-upload] NULL_RESULT diagnostic block (lines ~205-262) —
  its Phase 2 cell-resolution investigation is complete; its _wbDats.ResolveId
  dependency goes with this commit.
- Deleted _pendingEnvCellRequests field + isPendingEnvCell tracking in Tick().
- Simplified Tick() to a clean drain loop.

Deleted SplitFormulaDivergenceTest.cs — one-time N.5b data-collection sweep;
job done.

Verified acceptance criteria:
- Zero <ProjectReference> to WorldBuilder.* / Chorizite.OpenGLSDLBackend.* in any csproj.
- Zero 'using WorldBuilder.*' / 'using Chorizite.OpenGLSDLBackend.*' in src/.
- DefaultDatReaderWriter referenced in zero places in src/ (comments only).

Build green (0 warnings, 0 errors).
Tests: 1154 total (-1 from deleted SplitFormulaDivergenceTest), 1146 pass,
8 pre-existing failures (unchanged from baseline — physics/input tests
unrelated to this change).

Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:17:33 +02:00
Erik
a9ccc5acf5 fix(O-T4): thread-safety lock in DatDatabaseWrapper + drop unused using
Code-review findings on T4:

1. Added lock(_lock) around _db.TryGet and TryGetFileBytes in
   DatDatabaseWrapper, matching WB's DefaultDatDatabase pattern.
   ObjectMeshManager.PrepareMeshDataAsync runs on the thread pool, so
   concurrent dat access through the adapter must be serialized — our
   underlying DatCollection is not documented as thread-safe.

2. Removed unused `using WorldBuilder.Shared.Models;` from WbMeshAdapter.cs
   (its only purpose was TerrainEntry, which moved to AcDream.Core in T2).

Build green; tests green (1147 passing, 8 pre-existing failures baseline).

Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:01:43 +02:00
Erik
c0326523ac fix(O-T4): address spec-review findings — InstanceData + using cleanups
Four fixes from T4 spec review:

1. Extracted InstanceData.cs (14-line struct) verbatim to
   src/AcDream.App/Rendering/Wb/InstanceData.cs (per O-D1).

2. ObjectMeshManager.cs: replaced `using Chorizite.OpenGLSDLBackend.Lib;`
   with `using AcDream.Core.Rendering.Wb;` (TextureHelpers comes from
   our T2 Core extraction; InstanceData comes from new T4 cleanup).

3. EmbeddedResourceReader.GetEmbeddedResource promoted from `internal`
   to `public` per O-D9 intent (the type promotion only changed the
   class signature in T3; this finishes the spec).

4. OpenGLGraphicsDevice.cs: removed stale T3 interim comment at
   lines 142-145 — T4 resolved the ParticleBatcher construction
   via post-ctor assignment in WbMeshAdapter.cs:78.

Build green; tests green (1147 passing, 8 pre-existing failures
baseline maintained).

Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:50:05 +02:00
Erik
d16d8cd4e5 feat(O-T4): extract ObjectMeshManager + mesh pipeline closure into AcDream.App.Rendering.Wb
Phase O Task 4: extract the WB mesh pipeline (ObjectMeshManager + 7 support files)
from references/WorldBuilder into src/AcDream.App/Rendering/Wb/ and bridge dat I/O
through our DatCollection via a thin DatCollectionAdapter.

O-D7 adapter path taken: ObjectMeshManager has 26 _dats.X call sites (threshold 20),
so a DatCollectionAdapter : IDatReaderWriter is introduced rather than refactoring
ObjectMeshManager's internal dat access directly.

Files added (verbatim copies, namespace-only changes):
- ObjectMeshManager.cs — mesh pipeline hub; IDatReaderWriter field satisfied by adapter
- GlobalMeshBuffer.cs — single global VAO/VBO/IBO manager
- EdgeLineBuilder.cs — wireframe edge geometry from CellStruct polygons
- ModernRenderData.cs — ModernBatchData + LandblockMdiCommand structs
- TextureAtlasManager.cs — texture array grouping by (Width, Height, Format)
- ParticleBatcher.cs — GPU particle batching; T4 interim uses BaseObjectRenderManager
  static fields from Chorizite.OpenGLSDLBackend.Lib (stays until T7)
- ParticleEmitterRenderer.cs — per-emitter particle lifecycle + rendering
- ActiveParticleEmitter.cs — wrapper holding renderer + part index + local offset
- DatCollectionAdapter.cs — NEW: bridges DatCollection → IDatReaderWriter; implements
  ResolveId() via DatDatabase.TypeFromId + Tree.TryGetFile in HighRes→Portal→Language→Cell
  order matching DefaultDatReaderWriter; DatDatabaseWrapper wraps DatDatabase as IDatDatabase

WbMeshAdapter.cs changes (T4 Step 6):
- _graphicsDevice switched from Chorizite.OpenGLSDLBackend.OpenGLGraphicsDevice to
  extracted AcDream.App.Rendering.Wb.OpenGLGraphicsDevice
- ParticleBatcher = new ParticleBatcher(_graphicsDevice) restored (T3 had null! placeholder)
- ObjectMeshManager now constructed with new DatCollectionAdapter(dats) instead of _wbDats
- _wbDats field + its construction + disposal + [indoor-upload] NULL_RESULT diagnostic block
  left intact — T7 cleanup removes these once WorldBuilder project ref is dropped

EmbeddedResourceReader.cs: replaced assembly manifest lookup (wrong prefix for our assembly)
with disk-based lookup mapping "Shaders.Particle.vert" → Rendering/Shaders/wb_particle.vert;
consistent with all other acdream shaders.

wb_particle.vert / wb_particle.frag: WB particle shaders copied verbatim with wb_ prefix
to distinguish from acdream's own particle.vert.

OpenGLGraphicsDevice.cs: ParticleBatcher property type updated to extracted ParticleBatcher;
setter changed from private to internal so WbMeshAdapter (same assembly) can assign post-ctor.

Build: green (0 errors, 0 warnings in AcDream.App).
Tests: 1147+8 baseline maintained (8 pre-existing failures unchanged).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 16:37:55 +02:00
Erik
4cc38805b5 feat(O-T3): extract GL infrastructure to AcDream.App
Phase O Task 3 — verbatim-copy GL infra from Chorizite.OpenGLSDLBackend
into src/AcDream.App/Rendering/Wb/ (namespace AcDream.App.Rendering.Wb).

18 files extracted (all namespace-changed; no algorithm changes):
  OpenGLGraphicsDevice, ManagedGLTexture, ManagedGLTextureArray,
  ManagedGLVertexBuffer, ManagedGLIndexBuffer, ManagedGLVertexArray,
  ManagedGLFrameBuffer, ManagedGLUniformBuffer, GLSLShader, GLHelpers,
  GLStateScope, GpuMemoryTracker, SceneData, DebugRenderSettings,
  TextureParameters, TextureFormatExtensions, BufferUsageExtensions,
  EmbeddedResourceReader.

3 internals promoted to public (O-D9):
  EmbeddedResourceReader, TextureFormatExtensions, BufferUsageExtensions.

SixLabors.ImageSharp not reachable: TextureHelpers was placed in
AcDream.Core (no GL/ImageSharp dep); only the GL types went to App.

TextureHelpers.GetCompressedLayerSize added to AcDream.Core.Rendering.Wb
(was in Chorizite.OpenGLSDLBackend.Lib.TextureHelpers; uses
Chorizite.Core.Render.Enums.TextureFormat which Core gets transitively
via the still-present WB project refs).

T3/T4 boundary interims:
  - WbMeshAdapter._graphicsDevice stays Chorizite.OpenGLSDLBackend.OpenGLGraphicsDevice
    (T4 will swap it when ObjectMeshManager is extracted).
  - OpenGLGraphicsDevice.ParticleBatcher deferred to null! (T4 extracts
    ParticleBatcher alongside ObjectMeshManager; can't pass `this` of our
    new type to the WB-original ctor before T4).
  - ManagedGLTextureArray uses our TextureHelpers via explicit alias.
  - IUniformBuffer is in Chorizite.Core.dll under Chorizite.OpenGLSDLBackend
    namespace (unusual packaging); resolved via type alias.
  - AcDream.App.csproj gets explicit Chorizite.Core 0.0.18 PackageReference
    (IUniformBuffer + other Chorizite.Core types now used directly in App).

Build green. Test baseline 1147+8 maintained (1902 passing, 8 pre-existing
MotionInterpreterTests failures unrelated to T3).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:00:31 +02:00
Erik
16bc10c99d feat(O-T2): extract pure stateless helpers to AcDream.Core.Rendering.Wb
Verbatim copy of 5 WorldBuilder files into src/AcDream.Core/Rendering/Wb/:
- TextureHelpers.cs (pixel-format decoders, Chorizite Lib)
- SceneryHelpers.cs (scenery transforms, Chorizite Lib)
- TerrainUtils.cs, TerrainEntry.cs, CellSplitDirection.cs (WB.Shared Landscape)

Namespace migrated from WorldBuilder.* / Chorizite.OpenGLSDLBackend.Lib
to AcDream.Core.Rendering.Wb per O-D11. [MemoryPackable] stripped from
TerrainEntry per O-D10 (we don't serialize the struct).

Updated 3 source files + 1 test file to import from the new namespace.

Verbatim discipline (O-D1): only namespace + MemoryPack attribute changed.
All algorithm bodies byte-identical to upstream.

Note: TextureHelpers omits IsAlphaFormat() and GetCompressedLayerSize()
because those reference Chorizite.Core.Render.Enums.TextureFormat, a type
that has no path into AcDream.Core without adding an unwanted NuGet dep.
Neither method is called from Core or the test suite; the omission is safe.

Verified on main checkout: dotnet build green (0 errors), dotnet test
green — Failed: 8, Passed: 1147, Skipped: 0, Total: 1155 (baseline maintained).
TextureDecodeConformanceTests (9/9) pass byte-for-byte after namespace swap.
AcDream.Core project alone builds green in this worktree (App-layer failures
are pre-existing, blocked by empty WB submodule, addressed in Tasks 3+4).

Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:13:26 +02:00
Erik
8c073e0c4c chore(O-T1): create Core/Rendering/Wb directory + NOTICE.md attribution
Phase O setup: extracted-WB code home + MIT attribution per O-D5.
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 14:59:56 +02:00
Erik
ff4164247a plan(O): Phase O implementation plan + spec layer-placement fix
Plan: 7 tasks decomposing spec T2..T9 with bite-sized TDD-style steps,
exact file paths, commit-message templates, and a T4 safety-check
branch (refactor in place if ObjectMeshManager._dats call sites <=20;
fall back to thin adapter otherwise).

Spec fix: §4.1 mesh-pipeline files now correctly placed under
src/AcDream.App/Rendering/Wb/ instead of Core (ObjectMeshManager uses
Silk.NET.OpenGL types from Managed* wrappers, and CLAUDE.md forbids
Core depending on GL). §4.2's layer split (TextureHelpers in Core,
rest in App) was already correct.

Plan task order: T2 (setup) -> T5 (Core helpers, lowest risk) ->
T3 (App GL infra) -> T4 (App mesh pipeline + dat-shim) -> T7 (drop
refs + cleanup) -> T8 (visual verification) -> T9 (ship). T5 moved
earlier than spec order to validate the namespace migration flow on
small-blast-radius files before the load-bearing T4.

Self-review: all 12 spec decisions (O-D1..O-D12) mapped to plan tasks;
placeholders intentional + explained (MIT license body fetched at T2
step 4; commit-message parameters filled at task close).

Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 14:49:19 +02:00
Erik
b9c111b80d docs(O): O-T1 audit shipped + Phase O spec amended
O-T1 audit (REPORT-ONLY) maps acdream's transitive closure on WorldBuilder:
33 files / ~7.7K LOC across Chorizite.OpenGLSDLBackend (28 files) and
WorldBuilder.Shared (5 files). Verdict on O-Q1 (thread-model): SAFE —
adapters run render-thread only; no worker-thread access to WB code.

Spec amendments incorporated via brainstorm:

- O-D7: Refactor ObjectMeshManager to take DatCollection directly (not
  via adapter). T4 safety check — fall back to thin adapter if call-site
  count >20.
- O-D8: Drop LandSurfaceManager, EnvCellRenderManager, PortalRenderManager,
  TerrainRenderManager from the extract list — audit confirmed not reachable
  (we have our own ports or never used them).
- O-D9: Promote 3 internal types in Chorizite to public on extraction
  (EmbeddedResourceReader, TextureFormatExtensions, BufferUsageExtensions).
- O-D10: Strip [MemoryPackable] from TerrainEntry (we don't serialize).
- O-D11: Namespace AcDream.Core.Rendering.Wb.* for extracted code.
- O-D12: Drop ResolveId + [indoor-upload] NULL_RESULT diagnostic block.

Task breakdown: T6 (EnvCell/portal) eliminated; T5 (stateless helpers)
shrinks to 0.5d; T4 (mesh + refactor) grows to 2.5d. Net effort estimate
holds at ~7.75d.

All originally-open spec questions are now closed (Q1/Q2/Q3/Q4) or
deferred to T3 with an explicit verify step (Q5: SixLabors.ImageSharp
reachability).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 14:35:38 +02:00
Erik
e702dec7a3 docs(O-T1): session handoff prompt for Phase O Task 1 (WB usage audit)
Self-contained prompt for a fresh Claude Code session. The next session
reads it once, has all the context it needs, produces the WB-usage
closure audit at docs/research/2026-05-21-phase-o-t1-wb-audit.md,
and stops before any extraction. Investigation-only (the /investigate
skill applies). User reviews the audit before T2 begins.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 14:01:06 +02:00