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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
Phase O extracts the WB pieces we actually use (mesh pipeline, texture
decode, GL state, scenery, terrain blending, EnvCell/portal decode —
roughly 3-5K LOC) into src/AcDream.Core/Rendering/Wb/, swaps their
dat dependency from DefaultDatReaderWriter to our DatCollection, and
drops the WorldBuilder.Shared + Chorizite.OpenGLSDLBackend project
references. WB stays in references/ as a read reference, not as a
project dependency. MIT attribution in NOTICE.md.
Tagline: ONE thing touches the DATs.
Discipline: verbatim copy first, no "improvements" while extracting.
Refactors land in follow-up phases. Out of scope: re-porting from
retail decomp; perf optimization; API cleanup.
User direction 2026-05-21: pre-empts M1.5. M1.5 paused at its
2026-05-20 baseline; A6/A7 don't touch dat infrastructure so no
rework needed when it resumes.
Files:
- docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md (new, full spec)
- docs/plans/2026-04-11-roadmap.md (Phase O block inserted before M1.5; M1.5 marked PAUSED)
- CLAUDE.md (Currently-working-toward line updated; M1.5 block marked paused)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>