Cluster A's investigation pinned #86 (picker) as structural and closed
it (Phase B). #84 and #85 both pinned on missing indoor cell tracking;
Phase D promoted CellId via AABB containment which un-stuck the
spawn-in-building case (closes#84 partially) but proved too tight for
threshold/doorway cells to keep CellId indoor during normal walking.
The proper fix is retail's portal-based cell traversal; filed as a
new ISSUES.md issue (see body) for the follow-up phase. Phase E
diagnostic infrastructure ([cell-cache] + extended [indoor-bsp]) stays
in place as scaffolding for that work.
ISSUES.md: #86 → Recently closed. #84 status updated to PARTIAL with
resolution paragraph. #85 status update note added. New issue #87 filed
for portal-based indoor cell tracking.
Roadmap: Cluster A added to Recently shipped with partial-ship note.
Forward entry added for the portal-traversal follow-up under Phase G.
CLAUDE.md: current-phase paragraph updated to reflect Cluster A partial
ship. Next phase deferred to Claude's choice in a future session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lifts 13 startup-time environment variables out of GameWindow.cs into a
single typed AcDream.App.RuntimeOptions record read once in Program.cs.
Behavior-preservation only — no live behavior change, no visual change.
Verified end-to-end against ACE on 127.0.0.1:9000: full M1 demo loop
(walk Holtburg, click door, click NPC, portal entry) plus DEVTOOLS
ImGui panels load cleanly.
Why: GameWindow.cs is 10,304 LOC and scattered Environment.GetEnvironmentVariable
calls were one of the structural smells called out in the new
"Code Structure Rules" doc. Typed options is the safest cut to make
first because the substitution is mechanical and parsing semantics
get pinned by unit tests.
What lands:
- CLAUDE.md: removed stale R1→R8 execution-phases line, replaced with
pointers to the milestones doc + strategic roadmap (the actual
source of truth). Tightened the "check ALL FOUR references"
section to describe WB as the production rendering base, not
just a reference. New "Code Structure Rules" section (6 rules)
captures the discipline we're committing to.
- docs/architecture/acdream-architecture.md: removed dangling link
to the deleted memory/project_ui_architecture.md.
- docs/architecture/code-structure.md (NEW, 376 LOC): rationale for
the 6 rules + 6-step extraction sequence
(RuntimeOptions → LiveSessionController → LiveEntityRuntime →
SelectionInteractionController → RenderFrameOrchestrator →
GameEntity aggregation). This PR is Step 1.
- src/AcDream.App/RuntimeOptions.cs (NEW, 100 LOC): typed record
with FromEnvironment(string) factory and Parse(datDir, env)
overload for testability. Covers ACDREAM_LIVE, _TEST_HOST/PORT/
USER/PASS, _DEVTOOLS, _DUMP_MOVE_TRUTH, _NO_AUDIO,
_ENABLE_SKY_PES, _HIDE_PART, _RETAIL_CLOSE_DEGRADES,
_DUMP_SCENERY_Z, _STREAM_RADIUS.
- src/AcDream.App/Program.cs: builds RuntimeOptions once, passes
to GameWindow.
- src/AcDream.App/Rendering/GameWindow.cs: ctor takes RuntimeOptions;
7 startup-cached env-var fields become expression-bodied
properties or direct _options.X reads; TryStartLiveSession,
audio init, legacy stream-radius branch all route through
_options.
- tests/AcDream.App.Tests/ (NEW project, 10 unit tests + csproj):
pins parser semantics — default-off bools, the literal "0"
gate for RETAIL_CLOSE_DEGRADES, the >=0 guard for
STREAM_RADIUS, null-vs-empty for user/pass, exact-"1" check
for diagnostic flags. Registered in AcDream.slnx.
Out of scope (per code-structure.md §4):
- Per-call-site ACDREAM_DUMP_* / _REMOTE_VEL_DIAG diagnostic reads
sprinkled through GameWindow (~40 sites). Rule 5 in CLAUDE.md
commits us to migrating these opportunistically as larger
extractions land, not in a bulk pass.
- AcDream.Core's project-reference to Chorizite.OpenGLSDLBackend.
Only the stateless .Lib namespace is used; tightening the project
reference is documented as future work in code-structure.md §2.
Build: green.
Tests: AcDream.App.Tests 10/10 ✓, Core.Net.Tests 294/294 ✓,
UI.Abstractions.Tests 419/419 ✓,
AcDream.Core.Tests 1073/1081 (8 pre-existing failures verified
against pre-refactor baseline by stash-and-rerun).
Visual verification: full M1 demo loop against ACE +Acdream login
including DEVTOOLS panel host load.
Next: Step 2 — extract LiveSessionController per code-structure.md §4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
M1 "Walkable + clickable world" landed 2026-05-16 with Phase B.6
(d640ed7). All four demo targets work end-to-end retail-faithfully:
walk Holtburg, open inn door, click NPC, pick up item.
Freeze list applied: L.2 (collision), B.4 (interaction outbound),
B.5 (pickup) — these phases are off-limits until M7 polish unless
something is actively broken.
CLAUDE.md "currently working toward" advanced to M2 — "Kill a drudge."
Phases to ship: F.2 (Inventory panel), F.3 (Combat math + damage),
F.5a (dev panels Attributes/Skills/Equipped/Inventory), L.1c
(combat animation wiring), L.1b (command router prereq).
Also removes the "record a demo video" requirement from milestone
discipline (CLAUDE.md rule #3 + milestones doc operating rule #3) —
user finds video recording pointless. Milestones are now textual
events: writeup + freeze flip + CLAUDE.md "currently working toward"
update. Saved as feedback memory.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes#63, #69, #74, #75. Replaces the chain of Commit-B workarounds
that compensated for ACE's MoveToChain getting cancelled by a leaked
user-MoveToState packet during inbound auto-walk. The fix is
architectural — auto-walk drives the body directly from the
server-supplied path data, no player-input synthesis, no spurious
wire-packet transitions, no grace-period band-aid.
Architectural change (closes#75):
PlayerMovementController.ApplyAutoWalkOverlay → DriveServerAutoWalk.
- Steps Yaw toward target at retail-faithful turn rates.
- Computes desired forward velocity from path runRate.
- Calls _motion.DoMotion(WalkForward, speed) directly for the
motion-interpreter state (drives animation cycle).
- Sets _body.set_local_velocity directly when grounded.
- Returns true to gate the user-input motion + velocity section
in Update so user-input flow doesn't overwrite auto-walk
velocity or motion state.
Mirrors retail's MovementManager::PerformMovement case 6 (decomp
0x00524440) which never touches the user-input pipeline during
server-controlled auto-walk.
Wire-layer guard at GameWindow.cs:6419 retained as a SEMANTIC
statement (`if (result.MotionStateChanged && !IsServerAutoWalking)`):
user-MoveToState packets are for user-driven motion intent. During
server-controlled auto-walk, the motion-state transitions caused by
the animation override (RunForward / WalkForward / TurnLeft /
TurnRight cycles) must not leak as user-cancellation packets. This
is NOT the deleted 500ms grace-period band-aid; it's the wire-layer
expressing the user-vs-server motion split.
Animation plumbed for auto-walk phases (closes#69):
- Moving forward → WalkForward (speed=1.0) / RunForward (speed=runRate)
- Turn-first phase → TurnLeft / TurnRight (sign of yawStep)
- Aligned-but-pre-step / arrival → no override (idle)
Driven via _autoWalkMovingForwardThisFrame + _autoWalkTurnDirectionThisFrame
fields set in DriveServerAutoWalk and read in the MovementResult
construction at the bottom of Update. UpdatePlayerAnimation picks up
the localAnimCmd as the highest-priority animation source.
Walk/run threshold = 1.0m, retail-observed. ACE's wire-default of
15.0f is too generous; ACE's own physics layer uses 1.0f at
MovementParameters.cs:50 (with the 15.0f line commented out) and
Creature.cs:312 notes "default 15 distance seems too far". The
formula matches retail's MovementParameters::get_command at decomp
0x0052aa00: running = (initialDist - distance_to_object) >=
threshold, evaluated ONCE at chain start and held for the rest of
the auto-walk (matches retail "runs all the way / walks all the way"
behaviour). Wire-supplied threshold is ignored.
Pickup gate (IsPickupableTarget) now uses BF_STUCK
(acclient.h:6435, bit 0x4) to discriminate immovable scenery from
real pickup items that share a Misc ItemType. Sign (pwd=0x14 with
BF_STUCK) → blocked; spell component (pwd=0x10, no BF_STUCK) →
allowed. ACE's PutItemInContainer (Player_Inventory.cs:831-836)
responds with WeenieError.Stuck (0x29) on stuck items so the gate
prevents wasted wire packets + a UX dead-end.
R-key dispatch by target type. UseCurrentSelection's top-level
IsUseableTarget gate was wrong (blocked USEABLE_NO=1 items that
ARE pickupable). Reordered:
1. Creature → SendUse
2. Pickupable → SendPickUp
3. Useable → SendUse
4. Otherwise → "cannot be used" toast
Each handler keeps its own gate. Matches retail's per-action
server-side validation.
AP cadence revert (closes#74). With the MoveToChain race fixed,
the per-frame "send while moving" cadence is no longer load-bearing.
Reverted to retail's two-branch ShouldSendPositionEvent gate
(acclient_2013_pseudo_c.txt:700233-700285):
Interval NOT elapsed (< 1 sec): send if cell or contact-plane changed.
Interval elapsed (>= 1 sec): send if cell or position frame changed.
Adds _lastSentContactPlane field + ApproxPlaneEqual helper +
PlayerMovementController.ContactPlane public accessor. Extended
NotePositionSent(Vector3, uint, Plane, float) — both outbound sites
(MoveToState + AP) pass _playerController.ContactPlane.
Effective rates: 0 Hz idle, ~1 Hz smooth motion, per-event on
cell/plane changes, 0 Hz airborne.
CLAUDE.md updated with no-workarounds rule (commit `da126f9` on
the worktree branch). Saved as feedback memory at
memory/feedback_no_workarounds.md.
Tests: build green; Core.Net 294/294; Core 1073/1081 (baseline,
8 pre-existing Physics failures unchanged). Visual-verified
end-to-end on 2026-05-16 for far/near Use + PickUp on NPCs,
doors, items, spell components, signs (correctly blocked), corpses,
turn-first animation, run/walk thresholds, idle quiet, smooth-
motion 1Hz.
Spec: docs/superpowers/specs/2026-05-16-phase-b6-suppress-movetostate-during-inbound-autowalk-design.md
Plan: docs/superpowers/plans/2026-05-16-phase-b6-suppress-movetostate-during-inbound-autowalk.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Retires divergences flagged in the 2026-05-16 faithfulness audit:
1. AP cadence. Replaces the 1 Hz idle / 10 Hz active flat heartbeat
with a diff-driven model gated on `Contact && OnWalkable`
(acclient_2013_pseudo_c.txt:700327 SendPositionEvent). Sends on
position or cell change while grounded on walkable, plus a 1 sec
heartbeat; suppressed entirely airborne. PlayerMovementController
exposes `NotePositionSent(pos, cellId, now)` which GameWindow stamps
after each AutonomousPosition / MoveToState send — mirrors retail's
shared `last_sent_position_time` between SendPositionEvent
(0x006b4770) and SendMovementEvent (0x006b4680). Known divergence
from retail: ours is per-frame-while-moving, retail's effective rate
is ~1 Hz during smooth motion (cell/plane checks). Filed as #74,
blocked by #63 — when #63 lands we revert to retail's narrower gate.
2. Workaround retirement. Removes TinyMargin (0.05 m inside arrival)
and the AP-flush before re-send (`SendAutonomousPositionNow`). The
diff-driven cadence makes both obsolete. Close-range turn-first
deferred Use is kept (it IS retail — ACE Player_Move.cs:66-87
mirrors retail's CreateMoveToChain pre-callback rotation), renamed
`OnAutoWalkArrivedSendDeferredAction` to clarify it's a FIRST send.
`isRetryAfterArrival` parameter dropped.
3. Far-range Use/PickUp retry. Restored — was load-bearing, not the
"redundant cleanup" the Group 2 audit thought. Issue #63 means ACE
drops the first Use as too-far without re-polling on subsequent APs;
the arrival re-send is what makes far-range Use complete. Logs
include `(queued for arrival re-send pending #63)` to make this
explicit. Removes when #63 closes.
4. Screen-rect picker. New `AcDream.Core.Selection.ScreenProjection`
helper shared by `WorldPicker` and `TargetIndicatorPanel`. The
`Setup.SelectionSphere` projects to a screen-space square (retail
anchor `SmartBox::GetObjectBoundingBox` 0x00452e20); picker
hit-tests the mouse pixel against the same rect the indicator draws,
inflated by 8 px (`TriangleSize`). Guarantees what-you-see is
what-you-click — including rect corners that were dead zones under
the old ray-sphere picker. Per-type radius (1.0/1.6/2.0 m) and
vertical-offset (0.2/0.9/1.0/1.5 m) heuristic lambdas retired;
`IsTallSceneryGuid` deleted; `EntityHeightFor` trimmed to 1.5 m × scale
defensive default. No defensive sphere synth — entities without a
baked `SelectionSphere` are skipped, matching retail's
`GfxObjUnderSelectionRay` (0x0054c740).
5. Rotation rate run multiplier (Commit A precursor). `TurnRateFor(running)`
helper applies retail's `run_turn_factor = 1.5f` (PDB-named
0x007c8914) under HoldKey.Run, matching `apply_run_to_command` at
0x00527be0 (line 305098). Effective: walking ≈ 90°/s, running ≈ 135°/s.
Keyboard A/D + ApplyAutoWalkOverlay both use it.
6. Useability gate (Commit A precursor). `IsUseableTarget` corrected to
`useability != 0` per `ItemUses::IsUseable` at 256455 — ANY non-zero
passes (USEABLE_NO=1, USEABLE_CONTAINED=8, etc.), not just the
USEABLE_REMOTE bit. Cross-checked against 4 call sites in retail
(ItemHolder::UseObject 0x00588a80, DetermineUseResult 0x402697,
UsingItem 0x367638, disable-button-state 0x198826). Added
`ProbeUseabilityFallbackEnabled` diagnostic
(`ACDREAM_PROBE_USEABILITY_FALLBACK=1`) to measure how often the
creature/BF_DOOR fallback fires for ACE-seed-DB entities with
null useability.
CLAUDE.md updated with the graceful-shutdown rule for relaunch:
Stop-Process bypasses the logout packet, leaving ACE's session marked
logged-in for ~3+ min. CloseMainWindow() sends WM_CLOSE so the
shutdown hook runs and the logout packet reaches ACE.
Tests: +3 ScreenProjectionTests + 6 WorldPickerRectOverloadTests = +9.
Core.Net 294/294 pass; Core 1073/1081 (8 pre-existing Physics failures
unchanged). Visual-verified 2026-05-16: rotation rate, useability,
screen-rect click area, double-click + R-key + F-key Use/PickUp at
short and long range — dialogue/door/pickup fire on arrival.
Filed follow-ups #70 (triangle apex/size DAT sprite), #71 (picker
Stage B polygon refine), #72 (cdb omega.z probe), #73 (retail-message
sweep pattern), #74 (per-frame AP chattier than retail — blocked by
#63). Old ray-sphere `WorldPicker.Pick(origin, direction, ...)`
overload kept for back-compat; no callers in acdream proper.
Plan: docs/superpowers/plans/2026-05-16-retail-faithfulness-fixes.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a 'MCP servers (live tooling)' section after the cdb retail debugger. Ghidra MCP (LaurieWired v1.4 HTTP) on :8081 serving patchmem.gpr provides live decomp lookups by address/name/xref without dumping acclient_2013_pseudo_c.txt into context. WireMCP (stdio, Node, tshark wrapper) enables loopback capture against 127.0.0.1:9000 for ACE wire-protocol cross-checks (0xF61C, 0xF74A, 0xF7DE parsing). Both extend the static-decomp + cdb workflow with live introspection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase B.5 (ground-item pickup, close-range path) shipped and
visual-verified 2026-05-14 at Holtburg. M1 demo target 4/4 ("pick up
an item") met.
New ship-handoff doc captures the 5-commit history including the
post-visual-test PickupEvent (0xF74A) wire-handler fix that closes
the local-despawn gap.
Roadmap and CLAUDE.md updated to reflect the ship + the new follow-up
issues:
- #63 (MEDIUM) — server-initiated MoveToObject auto-walk not
honored; blocks double-click pickup + out-of-range F. Filed as
candidate Phase B.6. holtburger has the reference implementation.
- #64 (LOW) — local-player pickup animation does not render
(retail observers see it correctly). Likely a self-echo filter
dropping UpdateMotion(Pickup) on the local player.
Carry-overs from B.4c (#61 link-cycle flash, #62 PARTSDIAG null-guard)
unchanged.
L.2g slice 1 is CODE-COMPLETE: parser + registry mutator + WorldSession
dispatcher + GameWindow subscriber (4 commits: 2459f28, d538915,
536a608, 108e386). Build clean, 6 new tests pass, baseline-stable
across the full suite. Per-commit + final integration code reviews
all approved.
Visual verification deferred: while running the Holtburg-doorway test,
Phase B.4's outbound Use handler turned out to be unwired. The wire
builders (InteractRequests.BuildUse), classes (SelectionState,
WorldPicker), input-action enums, and keybindings all exist — but
GameWindow.OnInputAction has no case for SelectDblLeft, so the click
silently does nothing. The inbound L.2g chain we just landed can't
fire until something sends an outbound Use.
This commit captures the handoff + reframes next-session work:
* docs/research/2026-05-12-l2g-slice1-shipped-handoff.md (NEW)
Full evidence: 4 shipped commits, end-to-end code flow, B.4
discovery explanation, 4 minor + 1 Important review notes
(the Important one is a test-coverage gap that the B.4b visual
test will settle automatically), reproducibility recipe,
next-session pick.
* CLAUDE.md
"Currently in Phase L.2" paragraph: L.2g slice 1 code shipped;
visual test deferred to B.4b. Next-phase-candidates list:
L.2g slice 1 (now done) replaced with the B.4b candidate
pointing at the slice scope.
* docs/plans/2026-04-29-movement-collision-conformance.md
L.2g section gains a "Current shipped slice (2026-05-12):" table
listing the 4 commits.
* docs/plans/2026-05-12-milestones.md
M1 phase-list updated: L.2g slice 1 (code) shipped; B.4 renamed
"B.4 / B.4b" with the gap-discovery note + B.4b shape.
* docs/ISSUES.md
New issue #57 (HIGH) for the B.4 interaction-handler gap.
Promoted to Phase B.4b; will close as
DONE (promoted to Phase B.4b) when B.4b's design spec lands.
* Memory file project_interaction_pipeline.md (in personal
memory dir, not in this commit) updated to reflect reality.
Next session: Phase B.4b (~30-50 LOC, 1-2 subagent dispatches,
~30 min). Subscribe SelectDblLeft -> WorldPicker.Pick ->
InteractRequests.BuildUse -> _liveSession.SendGameMessage. Same
Holtburg-doorway visual test verifies both L.2g slice 1 and B.4b
in one pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
L.2d slice 1.5 ship identified the Holtburg doorway blocker as a closed
Door entity (Setup 0x020019FF) whose PhysicsState.Ethereal bit flips
when the player Uses the door. The L.2d shape-fidelity work doesn't
cover this — the door's collision shape is correct; what's missing is
honoring the *runtime* state change.
L.2g is the new sub-phase that handles it. Scope is narrow:
* Parse inbound GameMessageSetState (0xF74B).
* Plumb the new PhysicsState value into ShadowObjectRegistry's
cached per-entity state so the existing CollisionExemption.IsExempt
already-in-place short-circuit sees up-to-date bits.
* Verify the Holtburg inn-door scenario: walk in blocked, Use door,
walk through, auto-close blocks again after 30s.
* Confirm UpdateMotion (NonCombat, On/Off) drives non-creature
entities (door swing animation).
Why a new L.2 sub-letter (and not B.4 or Door-special-case): the wire
mechanism (SetState flipping Ethereal) is also how ACE handles activated
traps, opened chests, spell projectiles becoming ethereal. Generic
infrastructure with doors as the verification scenario; lane is the
informal sixth "dynamic state."
Roadmap state:
* L.2 plan-of-record adds the L.2g section after L.2f.
* Milestones doc M1 phase list extended `a-f` -> `a-g`.
* CLAUDE.md status pointer + "next phase candidates" list updated to
name L.2g slice 1 implementation as the natural next step.
Risk: low. Wire-byte width has a hex-dump fallback path in slice 1
(holtburger says 12 bytes, ACE writes 16, capture settles it). ETHEREAL
plumbing already exists; we feed it new data. No resolver changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User request: stop being asked "which should we work on next?" Claude
should drive the work-order autonomously per the milestone discipline +
roadmap, never present a menu of options, never pause for confirmation
between phases.
Two additive edits:
1. New "Work-order autonomy — the meta-rule" paragraph in the Milestone
discipline section, placed above the four motivation rules. Names
it the meta-rule that makes the others actually work. The user
retains the right to redirect; default is "Claude drives, user
reviews." If two next steps are genuinely equivalent, state the
choice in one sentence and start — don't ask.
2. Strengthened the "Continue to the next planned sub-step" bullet in
"Things you should just do without asking" under How to operate.
Adds explicit "You pick what comes next per the Milestone discipline
section — never present the user a menu like 'should we do X or Y?'
or ask 'what next?'. Just choose and announce the choice in one
sentence."
Saved as durable feedback memory in feedback_work_order_autonomy.md
(outside repo, in Claude's auto-memory).
Triggered by the 2026-05-12 session where the user reported feeling
lost / jumping between things; the diagnosis was decision fatigue from
constant work-order picks. Milestones doc (ecb0f2d) gave the structure;
this commit makes Claude responsible for executing it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduce a morale + scope-management layer above the roadmap. Seven
milestones (M0-M7) from "Connect & explore" to "v1.0", each defined by a
concrete playable scenario and ~6-10 weeks of work. Each milestone hit
triggers a recorded demo video + a freeze list of phases that go
off-limits until M7's polish pass.
M0 ("Connect & explore") is declared done retroactively — ~25 shipped
phases through 2026-05-12 (terrain, network, audio, particles, chat,
input, streaming, WB rendering migration, sky/lighting, particle system,
combat/spell/item data layers) are frozen. Currently working toward M1
("Walkable + clickable world"): L.2 collision + B.4 interaction, ~4-6
weeks.
CLAUDE.md gains a "Milestone discipline" section above the existing
"Roadmap discipline" — sets the two-altitude orientation (milestones
above, phases below), names M1 as the current target, and codifies the
four motivation rules: (1) one active milestone at a time, (2) frozen
phases off-limits, (3) recorded demo video per landing, (4) state both
altitudes at session start.
Addresses the "everything is half-built" feeling caused by per-phase
vertical-slice ships never adding up to a milestone-level "done" event.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three new bullets, all evidence-driven from /insights:
"Only stop and wait for the user when":
- Request is an investigation/audit/analysis/review/report-only — no edits
or diagnostic drops until the user approves a fix. Use the /investigate
skill (local at .claude/skills/investigate/SKILL.md, gitignored).
- A referenced commit/path/branch/doc doesn't exist where the user said
it would — ask one short question; don't go hunting across branches.
Prevents the N.5-handoff-style 30-min wrong-branch exploration.
"What NOT to do":
- Don't replace working retail-faithful logic with a modern redesign
without explicit approval. Cites the 267 min remote-entity rewrite +
sky-fog shader speculation both reverted in full. Retail-faithful
first; "cleaner" second.
The /investigate skill itself stays local (option 3 — not committed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Documents the L.2a-slice-1/2/3 ship for the next session and updates
the project's source-of-truth docs to reflect current state.
New files:
- docs/research/2026-05-12-l2a-shipped-l2d-handoff.md — full cold-start
handoff: what shipped, three findings (L.2e cell-id format gap, L.2c
wall-slide working, L.2d sub-direction = CBuildingObj port), branch
state, diagnostic surface inventory, files changed, open concerns,
cold-start checklist, reproduction recipe.
- docs/research/2026-05-12-l2d-next-session-prompt.md — terse copy-paste
prompt for the next Claude Code session.
Modified:
- docs/plans/2026-04-29-movement-collision-conformance.md — added "Current
sub-direction" note under L.2d capturing the evidence-driven call:
building-mesh fidelity issue, not door-state-toggle.
- CLAUDE.md — updated "Currently between phases" → "Currently in Phase
L.2"; added L.2a shipped paragraph; added ACDREAM_PROBE_RESOLVE /
ACDREAM_PROBE_CELL to Diagnostic env vars; prepended L.2d brainstorm
to next-phase candidates list.
No code changes in this commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Roadmap:
- Status header now reads "Between phases" with C.1.5b in the recent-wins
bullet list.
- New SHIPPED row in the table (C.1.5b) summarising both slices, the
reality discovery about EnvCell statics, and the 4 visual-verified sites.
- The "IN FLIGHT — C.1.5b" sub-bullet under Phase C flipped to SHIPPED.
ISSUES.md:
- #56 removed from Active.
- #56 added to Recently closed with full commit chain (1e3c33b spec+plan,
f3bc15e SetupPartTransforms helper, 11521f4 ParticleHookSink part-transform,
5ca5827 activator refactor, 8735c39 GpuWorldState fire-sites), resolution
notes, and the visual-verification record.
CLAUDE.md:
- "Currently in flight" pointer replaced by a "Currently between phases"
marker + a C.1.5b shipped paragraph that's parallel to the existing
C.1.5a entry.
- "After C.1.5b" → "Next phase candidates".
Visual verification 2026-05-12: portal swirl matches retail extent + lateral
spread (no ground-burial); Holtburg Inn fireplace flames; cottage chimney
smoke; spell-cast particles on +Acdream — all match retail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EntityClassificationCache keyed by (entityId, landblockHint) tuple lands
per spec docs/superpowers/specs/2026-05-10-issue-53-tier1-cache-design.md
+ plan docs/superpowers/plans/2026-05-10-issue-53-tier1-cache.md.
Perf result (horizon-safe preset + High quality, AMD Radeon RX 9070 XT
@ 1440p): entity dispatcher cpu_us median ~1200 us, p95 ~1500 us. Down
from ~3500m / ~4000p95 pre-Tier-1. ~66% / ~63% reduction. Well under
the A.5 spec budget (median <= 2.0 ms, p95 <= 2.5 ms). No BUDGET_OVER
flag across 30s+ standstill captures.
Visual gate cleared after 4 bug-fix iterations:
- 71d0edc: namespace stab Ids globally (cross-LB id collision)
- 95ebbf3: key cache by (entityId, landblockHint) tuple (defensive)
- c55acdc: skip cache populate when classification is incomplete
- f928e66: incomplete-entity flag must persist across same-entity tuples
User-confirmed visually via +Acdream test character: NPCs animate,
multi-part static buildings render fully (no airborne geometry, no
Z-fighting, no missing parts, no wrong textures), Nullified Statue of
a Drudge on top of the Foundry renders all parts, trees outside
Holtburg render with branches present.
Closes the post-A.5 polish phase. Issues #52, #54, #53 all closed.
Tests: 1711 passing, 8 pre-existing physics/input failures unchanged.
N.5b sentinel: 112/112 throughout.
Memory: ~/.claude/projects/.../memory/project_tier1_cache.md +
feedback_cache_per_tuple_pattern.md capture the audit-gap and per-tuple-
vs-per-entity recurring trap for future cache work.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move ISSUE #54 to Recently closed referencing commit `bf31e59`. Drop
#54 from CLAUDE.md "Currently in flight" — only #53 (Tier 1 retry)
remains open in the post-A.5 polish phase.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move ISSUE #52 from Active to Recently closed with full root-cause writeup
referencing commit `e40159f`. Strip lifestone reference from CLAUDE.md
"Currently in flight"; remaining post-A.5 polish scope is #53 (Tier 1
retry) + #54 (JobKind plumbing).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Document Phase N.5b shipping (terrain on the modern rendering path via
Path C — `TerrainModernRenderer` mirrors WB's `TerrainRenderManager`
pattern but consumes acdream's `LandblockMesh.Build` so retail's
`FSplitNESW` formula stays in lockstep with physics + visual mesh).
Changes:
- `docs/plans/2026-04-11-roadmap.md` — add N.5b row to the Shipped
table; promote N.5b's "Phases ahead" entry to ✓ SHIPPED with the
Path C resolution + perf reality check; refresh N.6 scope to note
Terrain has joined the modern path (legacy `Texture2D` retirement
scope narrows to Sky + Debug); update top-of-doc Status line.
- `docs/ISSUES.md` — close issue #51 (WB terrain-split formula
divergence). Move from OPEN to "Recently closed" with the Path C
resolution: never adopted WB's formula; modern dispatcher uses
retail's via `LandblockMesh.Build`. References `da56063` (the
black-terrain fix that landed within the N.5b ship chain).
- `CLAUDE.md` — add `TerrainModernRenderer.cs` to the WB integration
cribs list with the GL_INVALID_OPERATION caveat (use uvec2 +
`sampler2DArray(handle)` constructor, NOT direct
`uniform sampler2DArray` + `glProgramUniformHandleARB`). Update
the "Currently in flight" preamble: N.6 builds on N.5 + N.5b;
add an N.5b shipped paragraph linking the perf baseline doc.
- `docs/plans/2026-05-09-phase-n5b-perf-baseline.md` — new doc
capturing the radius=5 Holtburg perf measurement (modern 6.4-7.0
µs median vs legacy 1.5 µs — modern is ~4× SLOWER on CPU at
radius=5). Documents the spec acceptance criterion #5 amendment,
the architectural wins that DO hold (zero glBindTexture/frame,
constant-cost dispatch as A.5 raises radius, per-LB frustum cull),
and the three high-value gotchas surfaced during implementation.
User-memory updates (outside repo, not in this commit):
- `memory/project_phase_n5b_state.md` — full N.5b state file with
the three gotchas captured.
- `memory/MEMORY.md` — index entry pointing at the state file.
Build: dotnet build green. No code changes in this commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reframe the selection-blink follow-up so it doesn't suggest near-term
work. Was listed in N.5 ship record as "Phase B.4 follow-up adds the
field" — now phrased as open backlog with the hook reserved in
mesh_modern.vert's InstanceData comment for whoever eventually picks
it up.
The shader hook itself is unchanged — change is purely doc wording in
the plan SHIP record + CLAUDE.md WB integration cribs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final cross-cutting review of N.5 found that Task 15's deletion of
mesh_instanced.vert/.frag left InstancedMeshRenderer orphaned —
ACDREAM_USE_WB_FOUNDATION=0 silently rendered terrain+sky only with
no entities. The SHIP commit's "[x] ACDREAM_USE_WB_FOUNDATION=0 still
works" claim was inaccurate.
Resolution: formal retirement of the legacy renderer path within N.5
instead of deferring to N.6.
Deleted:
- src/AcDream.App/Rendering/InstancedMeshRenderer.cs
- src/AcDream.App/Rendering/StaticMeshRenderer.cs
- src/AcDream.App/Rendering/Wb/WbFoundationFlag.cs
GameWindow simplified — capability detection is unconditional, missing
bindless throws NotSupportedException with a clear message at startup.
WbDrawDispatcher + mesh_modern shader load are mandatory after init.
No escape hatch.
GpuWorldState simplified — WbFoundationFlag.IsEnabled guards on
AddLandblock/RemoveLandblock removed; adapter calls are unconditional
when the adapter is non-null.
PendingSpawnIntegrationTests updated — WbFoundationFlag.ForTestsOnly_ForceEnable
static ctor removed (flag is gone; adapter calls are unconditional).
The ApplyLoadedTerrain physics-data loop was also simplified: the
EnsureUploaded sub-loop that fed InstancedMeshRenderer is gone;
_pendingCellMeshes is now explicitly cleared to prevent unbounded
accumulation (the worker thread still populates it, but WB handles
EnvCell geometry through its own pipeline).
Spec §2 Decision 5 + §10 Out-of-Scope updated. Plan ship-amendment
section added. Roadmap updated (N.5 ships with retirement; N.6 scope
narrowed to perf-only). CLAUDE.md "WB integration cribs" updated.
Perf baseline doc updated. WbDrawDispatcher class summary docstring
corrected to describe the as-shipped SSBO + multi-draw-indirect path.
ISSUES.md #51 updated (terrain not in N.5 scope; deferred to N.7).
Bindless support is now a hard requirement. Modern desktop GPUs
universally expose GL_ARB_bindless_texture + GL_ARB_shader_draw_parameters;
if a user hits the NotSupportedException, that's a real bug report
worth investigating, not a silent fallback.
Build: 0 errors, 0 warnings. Tests: 71/71 (Wb+MatrixComposition+TextureCacheBindless filter).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Moves N.5 from in-flight to Shipped (2026-05-08). N.6 (retire
InstancedMeshRenderer + perf polish) becomes the in-flight phase.
CLAUDE.md in-flight pointer updated to match.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase N.4 (Rendering Pipeline Foundation) ships. WbFoundationFlag
flips to default-on (== "1" → != "0"). WB's ObjectMeshManager is
now acdream's production mesh pipeline; WbDrawDispatcher is the
production draw path. Legacy InstancedMeshRenderer is retained as
ACDREAM_USE_WB_FOUNDATION=0 escape hatch until N.6 retires it.
Visual verification at Holtburg passed:
- Scenery (trees / rocks / fences / buildings) renders correctly
- Characters connected with full close-detail geometry (Issue #47
preserved — GfxObjDegradeResolver path intact)
- FPS substantially improved by grouped instanced draws + per-entity
AABB cull + opaque front-to-back sort + palette-hash memoization
Three high-value WB API gotchas surfaced during Task 26 visual
verification and are now documented in CLAUDE.md "WB integration
cribs" + plan Adjustments 7-9 + memory project_phase_n4_state.md:
1. ObjectMeshManager.IncrementRefCount only bumps a counter — does
NOT trigger mesh loading. Call PrepareMeshDataAsync explicitly.
2. ObjectRenderBatch.SurfaceId is unset — read batch.Key.SurfaceId.
3. Modern rendering (GL 4.3 + bindless = every modern GPU) packs
every mesh into ONE global VAO/VBO/IBO. Use
glDrawElementsInstancedBaseVertex(BaseInstance) with FirstIndex +
BaseVertex from the batch, not naive DrawElementsInstanced.
Plan doc flipped to Final state. Roadmap N.4 → Live ✓; N.5 rebranded
from "Terrain rendering" to "Modern rendering path" (bindless +
multi-draw indirect on top of N.4's foundation; terrain rendering
moves to N.5b). CLAUDE.md "Currently in flight" pointer updated to
N.5. New memory file project_phase_n4_state.md preserves the three
WB gotchas for cross-session continuity.
n4-verify*.log added to .gitignore.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
28-task plan covering 4 weeks of work organized as:
- Week 1 (Tasks 1-10): WB plumbing + atlas for static scenery + conformance
- Week 2 (Tasks 11-15): streaming integration + memory budget verification
- Week 3 (Tasks 16-21): per-instance customization + animation
- Week 4 (Tasks 22-28): full draw dispatcher + visual verification + ship
Living document — task checkboxes marked as commits land; adjustments
appended in-place rather than rewriting earlier tasks. Conformance
tests run before substitution per N.1/N.3 pattern. Behind
ACDREAM_USE_WB_FOUNDATION=1 feature flag during weeks 1-3.
CLAUDE.md updated with a "Currently in flight" pointer in the Roadmap
discipline section so future agents pick up the plan as authoritative
for rendering work.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Saves the comprehensive inventory of what WorldBuilder provides
(terrain, scenery, static objects, EnvCells, portals, sky, particles,
texture decode, mesh extraction, visibility) vs what acdream still
ports from retail decomp (network, physics, animation, movement, UI,
plugin, audio, chat).
This is the load-bearing reference for the strategic shift from
"port retail algorithms ourselves" to "rely on WorldBuilder for
rendering + dat-handling, port only what WB doesn't cover."
Updates CLAUDE.md:
- Adds top-level instruction: read the inventory FIRST before
re-porting anything in the 🟢 list
- Reframes references/WorldBuilder/ as acdream's rendering BASE,
not just a reference repo
- Updates the "Reference hierarchy by domain" table to point
rendering/dat questions at WorldBuilder, with retail decomp as
cross-check
Subsequent commits will fork WorldBuilder and replace our terrain/
scenery/object rendering with calls into the fork.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cleans up dead code revealed by L.3 M2/M3:
GameWindow.cs:
- RemoteMotion.LastServerZ field deleted (only consumed by the M2-
removed Step 5 landing fallback in TickAnimations; never read).
- RemoteMotion.TargetOrientation field deleted (audit § 1 flagged as
DEAD; only ever written, never read).
- Stale ACDREAM_INTERP_MANAGER comments removed from RemoteMotion.Interp
and OnLivePositionUpdated (the env-var no longer gates anything as
of M2).
- Doc-comments on Interp + Position rewritten to describe the M2/M3
production semantics (queue catch-up + REPLACE-style combiner).
CLAUDE.md:
- ACDREAM_INTERP_MANAGER env-var entry rewritten as a retirement note
pointing at commit 40d88b9 (M2). The path it gated is now the
default for player remotes.
Build green, dotnet test green (8 pre-existing failures unchanged on
this baseline).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
End-of-session cleanup of the 2026-05-03 remote-motion debug session.
Documentation:
- CLAUDE.md: add ⚠️ DO-NOT-ENABLE warning for ACDREAM_INTERP_MANAGER=1
in the diagnostic env-vars list. Add an "Outbound motion wire format"
section documenting acdream's WalkForward+HoldKey.Run encoding (which
ACE auto-upgrades to RunForward on relay) so future sessions don't
re-derive it.
- docs/ISSUES.md: file two new issues:
* #39 — Run↔Walk cycle transition not visible on observed
retail-driven player remotes when watched from acdream. Root cause
located: ApplyServerControlledVelocityCycle is gated by
IsPlayerGuid, excluding the exact case where ACE doesn't broadcast
a UM (shift toggle while direction key held). Fix sketch ~10
lines, separate commit. Cross-references the four-agent
investigation prompt.
* #40 — ACDREAM_INTERP_MANAGER=1 env-var path regressed. Documents
why (e94e791 conflated MoveOrTeleport with update_object), the
visible symptoms (staircase Z, position blips), and why
Commit B (039149a)'s ResolveWithTransition port was insufficient
(env-var path also clears body.Velocity → no horizontal Euler
motion → sweep input is queue catch-up only, which stair-steps).
Fix path = separate L.3 follow-up to re-integrate PositionManager
additively on top of the legacy chain.
Code:
- GameWindow.cs:6042: prepend a ⚠️ REGRESSED warning block at the top
of the env-var per-frame branch so anyone reading the code is
immediately aware not to enable it. Cross-references ISSUES.md #40.
- AnimationSequencer.cs: re-throttle [SCFAST]/[SCFULL] diagnostics to
0.5s per instance (rolled back from A.1's unthrottled experiment).
Already served its purpose; throttled is enough for steady-state
diagnostics. Restore _lastSetCycleDiagTime field.
No behavior change for any current launch (env-var unset = legacy
path unchanged). Build green; baseline test failures (8) unchanged
from prior commit, none introduced by this session.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three intertwined changes from a single investigation session driven by
attaching cdb to a live retail acclient.exe (v11.4186, Sept 2013 EoR
build) and tracing what retail actually DOES on the steep-roof wedge
scenario the user reported in acdream.
═══════════════════════════════════════════════════════════
1. L.5 — physics-tick MinQuantum gate (PlayerMovementController)
═══════════════════════════════════════════════════════════
Retail's CPhysicsObj::update_object subdivides per-frame dt into 1/30 s
sized integration steps and SKIPS entirely when accumulated dt is below
MinQuantum. Live trace evidence:
update_object = 40,960 calls
UpdatePhysicsInternal = 25,087 calls (61%)
i.e., 39% of update_object calls return early via the MinQuantum gate.
Retail's effective physics tick rate is 30Hz even at 60+ Hz render.
acdream's PlayerMovementController bypassed the existing PhysicsBody.
update_object and called UpdatePhysicsInternal(dt) directly each render
frame, which compressed bounce-energy / gravity-tangent accumulation
into half the time and amplified our steep-roof wedge dynamics.
Fix: add `_physicsAccum` accumulator. Integrate only when accumulated
dt ≥ MinQuantum (clamped to MaxQuantum to bound stale-frame jumps).
HugeQuantum drops accumulated time to discard truly stale frames
(debugger break, GC pause). Render still runs at full rate; only the
physics step is gated.
═══════════════════════════════════════════════════════════
2. Phase 3 reset retail-faithful kill_velocity (TransitionTypes)
═══════════════════════════════════════════════════════════
Retail's reset path (acclient_2013_pseudo_c.txt:273231-273239) gates
kill_velocity on `last_known_contact_plane_valid`:
if (last_known_valid == 0) {
set_collision_normal(step_up_normal); return COLLIDED;
}
kill_velocity(this);
last_known_valid = 0;
return COLLIDED;
Earlier in this session I deviated to "unconditional kill_velocity" as
a hypothesis-driven wedge fix. The live trace then showed the
deviation CAUSED a different wedge by zeroing V every frame, leaving
the body with no tangent momentum to escape (V = (0,0,0) for 169
consecutive frames while position pre/resolved frozen). The retail-
faithful gate is restored.
Note: the gate rarely fires in normal airborne play because our L.2.4
proximity guard clears last_known_valid soon after the body separates
from its remembered floor. Live retail trace also showed
kill_velocity = 0 hits over an entire play session — same behavior. So
acdream's kill_velocity is correct as ported now.
The supporting ObjectInfo.VelocityKilled flag + StopVelocity wiring +
PhysicsEngine.ResolveWithTransition consumer that actually zeros
body.Velocity when the flag is set — these were a no-op stub before
this session and are now correctly wired. Retail anchor:
OBJECTINFO::kill_velocity → CPhysicsObj::set_velocity({0,0,0}, 0) at
acclient_2013_pseudo_c.txt:274467-274475.
═══════════════════════════════════════════════════════════
3. Retail debugger toolchain (#35)
═══════════════════════════════════════════════════════════
When the question is "what does retail actually DO at runtime?" — not
"what does retail's code SAY" — the decomp at docs/research/named-retail/
is invaluable but doesn't capture state interactions across frames.
This commit ships infrastructure to attach Windows' cdb.exe to a live
retail acclient.exe with full PDB symbols and capture state at any
breakpoint.
- tools/pdb-extract/check_exe_pdb.py — reads any PE's CodeView entry
and reports MATCH / MISMATCH against refs/acclient.pdb's GUID.
Always run before attaching cdb. The matching v11.4186 build's
GUID is 9e847e2f-777c-4bd9-886c-22256bb87f32.
- tools/pdb-extract/dump_pdb_info.py — dumps a PDB's expected
build timestamp + GUID + age. Used to figure out which acclient.exe
build pairs with our PDB.
CLAUDE.md gets a Step -1 in the development workflow ("ATTACH cdb
TO RETAIL when behavior is the question, not code") and a full
"Retail debugger toolchain" section with the workflow, sample .cdb
script structure, and watchouts (PDB names use snake_case for some
classes / PascalCase for CPhysicsObj; ; is cdb's command separator;
killing cdb kills the debuggee; high-hit-rate breakpoints lag the game).
memory/project_retail_debugger.md captures the workflow + key findings
so future sessions inherit the toolchain by reading project memory.
═══════════════════════════════════════════════════════════
4. BSPQuery Path 6 slide-tangent restored (b1af56e behavior)
═══════════════════════════════════════════════════════════
After this session's retail-strict experiments showed that retail-
faithful Path 6 (SetCollide + Phase 3 reset chain) produces a
"lands on roof in falling animation, can't slide off" half-state in
acdream — because our acdream port of step_up_slide / cliff_slide is
incomplete for grounded-on-steep movement — the L.4 slide-tangent
deviation from commit b1af56e is restored as the pragmatic ship state.
The deviation: when an airborne sphere hits a polygon whose normal Z
is below FloorZ (≈ 0.6642, slope > ~49°), project the move along the
steep face to remove the into-wall displacement, set CollisionNormal +
SlidingNormal, return Slid. Body never gets ContactPlane on the steep
poly, never gets the half-state, slides off the slope under gravity's
tangent contribution.
Retail-strict requires the deeper step_up_slide / cliff_slide audit
(filed under #32). Until that lands, slide-tangent is the right
deviation — produces user-acceptable "slide off the roof" behavior.
═══════════════════════════════════════════════════════════
Test status: 833/833 green.
Refs:
acclient_2013_pseudo_c.txt:283950 (CPhysicsObj::update_object)
acclient_2013_pseudo_c.txt:273231-273239 (Phase 3 reset path)
acclient_2013_pseudo_c.txt:274467-274475 (OBJECTINFO::kill_velocity)
acclient_2013_pseudo_c.txt:323783-323821 (BSPTREE::find_collisions Path 6)
Closes#35. Updates #32 with L.4/L.5 status.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a "Communication style" section after "How to operate" that documents
how to discuss spatial / physics / animation / dat-format / protocol
topics with the user. The user has asked repeatedly for plain-language
framing of new concepts: name the idea in English first, then introduce
the term of art; give units (degrees, meters) instead of raw floats;
use analogies for spatial concepts (BSP = nested rooms, contact plane =
imaginary floor, sphere sweep = rolling a ball, dot/cross products);
walk through control flow frame-by-frame; flag terms of art the first
time they appear.
The goal is collaborative learning, not dumbed-down content.
Phase K final commit. Settings panel with click-to-rebind UX on top of
the K.1+K.2 input architecture, plus the roadmap / ISSUES / memory
updates that retire Phase K.
InputDispatcher gains BeginCapture / CancelCapture / IsCapturing /
SetBindings — modal capture suppresses normal action firing for the
next chord. Esc cancels (returns sentinel default chord); modifier-only
keys don't complete capture; non-modifier key down with current
modifier mask completes.
IPanelRenderer + ImGuiPanelRenderer + FakePanelRenderer gain
BeginMainMenuBar / EndMainMenuBar / BeginMenu / EndMenu / MenuItem
primitives.
SettingsVM owns a draft copy of KeyBindings with explicit Save /
Cancel / Reset semantics. Click-to-rebind enters dispatcher capture
mode; on chord captured, conflict-detect against draft (excluding the
action being rebound itself); surface a ConflictPrompt when the chord
collides; ResolveConflict(replace=true|false) commits or reverts.
ResetActionToDefault restores a single action to RetailDefaults();
ResetAllToDefaults rebuilds the entire draft. Save invokes the
onSave callback (which writes JSON + swaps the live dispatcher's
bindings).
SettingsPanel renders 8 retail-keymap-categorized CollapsingHeader
sections (Movement, Postures, Camera, Combat, UI panels, Chat,
Hotbar, Emotes). Per action: name + current binding(s) summary +
"Rebind"/"Reset" buttons. Conflict prompt at the top when pending.
Save / Cancel / "Reset all to retail defaults" at the top.
GameWindow registers SettingsPanel + wires F11 →
ToggleOptionsPanel → IsVisible toggle, plus a top-of-frame ImGui
MainMenuBar with View → Settings/Vitals/Chat/Debug entries (calls
ImGui directly — the abstraction methods exist for backend
portability but the host doesn't own a menu-bar surface).
Tests: +37 across InputDispatcherCaptureTests (7),
IPanelRendererMainMenuBarTests (9), SettingsVMTests (13),
SettingsPanelTests (8). Solution total 1220 green.
Roadmap (docs/plans/2026-04-11-roadmap.md) appends Phase K shipped
section after Phase J with K.1a–K.3 commit SHAs. ISSUES.md files
Phase L deferred work as #L.1–#L.8 (hotbar UI, spellbook favorites,
combat-mode dispatch, F-key panels, floating chat windows, UI layout
save/load, joystick bindings, plugin input subscription) and adds
#21–#25 to Recently closed. project_input_pipeline.md updated to
shipped state. CLAUDE.md gets an input-pipeline reference.
Closes Phase K.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps Phase I — UI consolidation + complete chat system. All 7 prior
commits (I.1 through I.7 + I.2) are now reflected in the canonical
sources of truth.
- docs/plans/2026-04-11-roadmap.md: new "Phase I — UI consolidation +
complete chat system" section between H and J. 8 sub-pieces all
marked SHIPPED 2026-04-25 with their actual commit SHAs:
I.1 b131514, I.2 56037a4, I.3 8e6e5a0, I.4 f14296c, I.5 ff5ed9e,
I.6 ca968fc, I.7 3d26c8e, I.8 (this commit).
Plus Phase H.1 entry annotated to credit I.4 + I.7 for chat input
+ combat translation. D.5 / D.6 entries cross-link to the new I
surface where relevant. Three Q&A rows added to "When will my
specific complaint be fixed?".
- docs/ISSUES.md: 7 issues filed and closed in the same session
(#14 IPanelRenderer widgets, #15 DebugPanel migration, #16
LiveCommandBus, #17 ChatPanel input, #18 holtburger inbound
parity, #19 TurbineChat, #20 CombatChatTranslator). All in
Recently closed with real commit SHAs.
- CLAUDE.md: surgical update to the UI strategy paragraph (~line 35).
ImGui now hosts ALL dev/debug UI (Vitals + Chat + Debug);
StbTrueTypeSharp DebugOverlay deleted in I.2; TextRenderer +
BitmapFont retained for the future HUD-in-world (D.6); custom
retail-look toolkit (D.2b) remains the long-term retail-look
path while ImGui is the pragmatic D.2a default.
- memory/project_chat_pipeline.md (auto-loaded; in user's claude
project memory tree): new evergreen crib documenting the
ChatLog -> ChatVM -> ChatPanel + LiveCommandBus -> WorldSession
pipeline with the slash-command set + opcode coverage.
- memory/MEMORY.md: indexed line for project_chat_pipeline.
Solution state at end of Phase I:
989 tests green (107 + 639 + 243), 0 warnings, 0 errors.
+124 tests across the phase.
Closes Phase I in roadmap.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CLAUDE.md edits (6 surgical ranges):
- Goal section: introduce named-retail/ as primary; old chunks
remain as fallback for chunk-by-chunk address-range navigation.
- Workflow renamed to "grep named -> decompile -> verify -> port"
with a new STEP 0 GREP NAMED FIRST. Decompile demoted to a
fallback (Step 1) for the rare obfuscated/packed minority that
pseudo-C lacks.
- Function-map citation updated to point at symbols.json + the
cross-port hand-curated table.
- "Do not guess" rule strengthened: PDB has the answer for almost
everything; guessing is now negligence.
- Phase completion checklist accepts named symbols + addresses.
- Reference hierarchy table gets a new top row pointing at
docs/research/named-retail/ as the primary oracle for any
AC-specific algorithm — beats every other reference.
memory/project_named_decompilation.md (new): evergreen crib-sheet
with file inventory, grep examples, hard rules. Pattern matches
project_ui_architecture.md.
memory/project_retail_research_index.md: updated preamble to point
named-retail/ as first stop; older slices remain useful for
pseudocode + C# port sketches.
memory/project_collision_port.md: rewrote the "Decompiled ground
truth" section to put named-retail/ first, chunks second. The
"DECOMPILE FIRST" mandate becomes "GREP NAMED FIRST, then DECOMPILE
FALLBACK".
docs/architecture/acdream-architecture.md: Guiding Principle text
updated to introduce named-retail as the primary decomp source.
docs/plans/2026-04-11-roadmap.md: new Phase R block — Retail
research infrastructure. R.1 (corpus, shipped a9a01d8), R.2
(pdb-extract, shipped 69d884a), R.3 (actestclient vendored,
shipped a9a01d8). All marked SHIPPED 2026-04-25.
Auto-loaded MEMORY.md index updated with a new entry pointing at
project_named_decompilation.md so post-compaction sessions inherit
the workflow change automatically.
Acceptance verified:
- grep -c "named-retail" CLAUDE.md = 9 (>= 3 required)
- grep -c "named-retail" MEMORY.md = 1
- dotnet build green (docs-only commit, but verified)
Foundation phases A + B + C all landed. Next: Phase D files
ISSUES #8/#9/#11 + closes#10 (KillerNotification orphan parser).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes Phase D.2a. Launch with ACDREAM_DEVTOOLS=1 now shows a live
ImGui "Vitals" window whose HP bar reads CombatState.GetHealthPercent
for the local player. Without the env var the branches are dead code,
no ImGui context is created, and behaviour is identical to before.
GameWindow hunks:
- fields: _imguiBootstrap / _panelHost / _vitalsVm + DevToolsEnabled
- init (OnLoad): construct bootstrap + host, register VitalsPanel
- GUID push: _vitalsVm?.SetLocalPlayerGuid(chosen.Id) at live-connect
- frame begin: _imguiBootstrap.BeginFrame(dt) after GL clear
- frame end: _panelHost.RenderAll(ctx) + _imguiBootstrap.Render() after debug overlay
- input gating: skip WASD when ImGui.GetIO().WantCaptureKeyboard
Backend pivot: Hexa.NET.ImGui → ImGui.NET + Silk.NET.OpenGL.Extensions.ImGui.
First-light integration with the Hexa backend crashed 0xC0000005 inside
Hexa.NET.ImGui.Backends.OpenGL3.ImGuiImplOpenGL3.InitNative. Root cause:
Hexa's native OpenGL3 backend resolves GL function pointers via GLFW or
SDL internally; with Silk.NET (which uses neither) the pointers are null
and the native code crashes on first use. The mitigation path was
already planned — the design doc's Risk section called a pivot to
ImGui.NET a "one-morning operation" — and that's exactly what happened.
- Packages: Hexa.NET.ImGui 2.2.9 + Hexa.NET.ImGui.Backends 1.0.18
→ ImGui.NET 1.91.6.1 + Silk.NET.OpenGL.Extensions.ImGui 2.23.0
- ImGuiBootstrapper: was static Initialize(gl)+Shutdown() wrapping
Hexa's OpenGL3 init; now an IDisposable wrapping Silk.NET's
ImGuiController instance which handles GL backend init + input
subscription in one go.
- SilkInputBridge.cs deleted (~190 LOC): ImGuiController subscribes
IKeyboard / IMouse events itself, we don't need a bespoke bridge.
- ImGuiPanelRenderer: ImGuiNET.ImGui.* calls instead of
Hexa.NET.ImGui.ImGui.*. Widget surface unchanged.
Boundary discipline is preserved — no panel imports ImGuiNET; only
ImGuiPanelRenderer does. The D.2b custom toolkit will implement the
same IPanelRenderer contract without touching panel code.
Out of scope (tracked for follow-up):
- Stam/Mana currently return float? null (VitalsVM). Absolute values
need LocalPlayerState + PlayerDescription (0x0013) parsing to be
stored rather than discarded — filed as a post-D.2a issue.
- Mouse-capture gating (WorldMouseFallThrough-style click-through
tests) — not needed until we add clickable inventory items.
Roadmap + memory + architecture doc + UI framework plan updated in the
same commit per CLAUDE.md roadmap-discipline rules. 753 tests pass
(550 Core + 192 Core.Net + 11 new UI.Abstractions), 0 build warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces `docs/ISSUES.md` as the tactical rolling list of known bugs +
small deferred features. Scope is strict: anything fitting in one-to-two
commits lives here; anything larger gets promoted to a Phase in the
roadmap. Complement, not replacement, of the strategic roadmap.
Schema per issue:
- Sequential integer ID (#1, #2, ...)
- Status (OPEN / IN-PROGRESS / DONE)
- Severity (HIGH / MEDIUM / LOW)
- Filed date, component, description, root cause, files, research,
acceptance
- DONE items move to "Recently closed" at the bottom with commit SHA
Four seeded issues from the 2026-04-24 sky-debug session:
#1 — Rain falls only to horizon, not to player (weather/particles)
#2 — Lightning visual not wired (dat-baked PES triggers; research done)
#3 — Client clock drifts from retail after ~10 min (periodic TimeSync)
#4 — Sky horizon-glow disabled (fog-mix skipped on sky meshes)
CLAUDE.md adds an "Issue tracking" section right after roadmap
discipline with the four maintenance rules: (1) scan OPEN at session
start, (2) add/close at session end, (3) reference IDs in commit
messages, (4) promote to a Phase if an issue grows. Calls out the
strategic-vs-tactical split so future sessions know when to reach for
the roadmap vs the issues file.
README.md gets one line pointing at `docs/ISSUES.md` below the existing
roadmap link for first-class discoverability.
No code changes; doc-only.
Landed the UI framework design in 2026-04-24-ui-framework.md yesterday;
this commit propagates the decisions across the documents that future
sessions touch first, so the three-layer pattern is discoverable without
re-reading the full plan.
Changes:
* NEW memory/project_ui_architecture.md — evergreen crib-sheet:
three-layer diagram, AcDream.UI.Abstractions contract, D.2a/D.2b
split, module layout, hard rules, why staged not pure-custom.
* CLAUDE.md: new paragraph describing the three-layer UI split, naming
AcDream.UI.Abstractions as the plugin-facing contract, pointing at
the full plan + memory crib.
* docs/architecture/acdream-architecture.md: new "UI Architecture"
companion-stack diagram after Layer 0-5 (doesn't renumber the main
stack), plus step 6a "UI tick" in Per-Frame Update Order.
* docs/plans/2026-04-11-roadmap.md Phase D tightened:
- D.2 split explicitly into D.2a (Hexa.NET.ImGui scaffold + abstraction
layer) and D.2b (custom retail-look backend, implements same contracts).
- D.3 AcFont / D.4 dat sprites / D.7 cursor flagged as D.2b dependencies.
- D.5 core panels / D.6 HUD flagged as abstraction-layer deliverables
— ship with D.2a, reskinned by D.2b.
- D.8 Sound marked superseded (shipped as Phase E.2).
- F.5 core panels + H.1 chat-window cross-references updated to say
they target AcDream.UI.Abstractions, unblocked by D.2a.
- Shipped-phases table untouched.
* docs/research/retail-ui/00-master-synthesis.md: scope note at top
clarifies the Keystone research is the D.2b (custom backend)
foundation, NOT where D.2a starts.
* ~/.claude/.../memory/MEMORY.md: one-line index entry pointing at the
new project_ui_architecture.md (so session auto-load surfaces it).
Zero code changes; doc-only. dotnet build stays green. All verification
greps pass (see plan file for exact checks).
Document the live-server loop: ACE at 127.0.0.1:9000, +Acdream test char,
the canonical PowerShell launch command with env vars, the 3-5 s logout
delay (exit 29 otherwise), diagnostic env vars, and the distinction
between 'own view' vs 'retail observer view' when triaging motion bugs.
This captures workflow that's been implicit across many sessions so any
Claude instance picking up the project can launch against the live server
on its first try instead of re-discovering the incantation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The single most important document in the project. Defines:
Architecture: 6-layer stack (Platform → Renderer → Network → World →
Game Objects → Plugin API). The code is modern C#; the behavior
matches the retail client exactly.
GameEntity: the unified entity class that replaces the current
scattered state (WorldEntity + AnimatedEntity + guid dicts + player
controller). Every world object is a GameEntity with PhysicsBody +
AnimationSequencer + CellTracker + MotionInterpreter + AppearanceState.
Per-frame update order: Network → Streaming → Input → Entity tick
(motion → physics → collision → cell → animation) → Render → Plugin.
Execution plan (R1-R8):
R1: GameEntity refactor (unify scattered state)
R2: Thin GameWindow (extract to proper systems)
R3: CellBSP + wall collision (indoor transitions)
R4: Complete animation state machine
R5: Lighting from decompiled AdjustPlanes
R6: Server compliance (authoritative Z, keepalive)
R7: Interaction (doors, NPCs, chat, inventory)
R8: Plugin API completion (Lua macros)
Also updates CLAUDE.md to establish the architect role and reference
the architecture doc as the single source of truth.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrote the reference repos section with:
1. Clear domain-to-oracle mapping table — every domain (terrain, rendering,
networking, movement, etc.) has a named primary oracle and secondary
reference. No ambiguity about which repo to check first.
2. NEVER GUESS policy: "read the reference FIRST, write code SECOND.
Always." Explicitly calls out the triangle-boundary Z bug as the
cautionary tale (5 failed fix attempts from guessing vs 1 fix from
checking ACME's ClientReference.cs).
3. Quick-reference file lists for ACME (6 key files) and holtburger
(6 key files) so future sessions can jump straight to the right code.
4. WorldBuilder original explicitly marked as SUPERSEDED for terrain
algorithms (ACME has conformance tests, original doesn't).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ACME Edition contains ClientReference.cs — a faithful C# port of the
decompiled retail AC client (CLandBlockStruct.cpp) with exact offsets.
This is ground truth for terrain algorithms and already solved the
triangle-boundary Z bug that resisted 5 other fix attempts.
Key resources: ClientReference.cs (oracle), TerrainConformanceTests.cs
(4M+ cell sweep), StaticObjectManager.cs (GfxObj+Setup+CreaturePalette),
EnvCellManager.cs (dungeon portal visibility).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Exhaustive analysis of two working AC clients revealing three critical
findings that reshape acdream's movement system:
1. Server-authoritative Z: neither AC2D nor holtburger computes local
terrain Z for the player. AC2D sends keys, receives position. Holtburger
dead-reckons for smoothing but the server overrides.
2. Terrain split formula mismatch: AC2D and ACViewer's render path use
0x0CCAC033-based FSplitNESW; WorldBuilder (our source) uses a different
214614067-based physics formula. Our terrain mesh triangulation doesn't
match the real AC client's, causing Z mismatches on slopes.
3. Movement deduplication: MoveToState sent once per state change, not per
frame. AutonomousPosition heartbeat every 1 second.
Also adds AC2D to CLAUDE.md reference repos section.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause of the still-purple-haze symptom AND the ACE-side
"Network Timeout" drop after ~60s. acdream was never sending
acknowledgement packets back to the server, so the server's
reliability layer saw a one-way stream and eventually dropped the
session. During the 60s window the player rendered to other clients
as the stationary purple loading haze (AC's "this client is in
portal-space transition" indicator).
Pattern ported from
references/holtburger/crates/holtburger-session/src/session/
{send.rs::send_ack, receive.rs::finalize_ordered_server_packet}.
The proper holtburger pattern is per-packet acks, NOT a periodic
heartbeat: every received server packet with sequence > 0 and no
ACK_SEQUENCE flag of its own gets a bare control packet sent back
with:
PacketHeader {
Flags = ACK_SEQUENCE (0x4000),
Sequence = current_client_sequence (= last issued, no increment),
Id = session client id,
}
Body = u32 little-endian server sequence being acked
Acks are cleartext control packets (no EncryptedChecksum) and
re-use the most recently issued client sequence rather than
consuming a new one — they aren't part of the reliable stream the
server tracks for retransmits.
Wired into ProcessDatagram so both Tick (post-InWorld) and PumpOnce
(during Connect/EnterWorld) trigger acks on every received non-ack
server packet.
Also (per user request) upgrades the CLAUDE.md description of the
holtburger reference repo from "Rust AC client crate" to "almost-
complete Rust TUI AC client — the most authoritative reference for
client-side behavior in the project, look here FIRST for anything
WorldSession or message-builder related." This was the third time
in two days I would have saved hours by checking holtburger first
instead of guessing at the protocol from ACE alone.
220 tests green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three additions / changes to CLAUDE.md after a brainstorming session
that produced a new strategic roadmap and Foundation phase spec:
1. "How to operate" rewritten to be more explicit that the agent is
the lead engineer and should stop only for visual verification.
Everything else — picking phases, jumping across commit boundaries,
shipping whole multi-step phases in one session, spawning subagents,
adding and stripping diagnostic logging — is the agent's call. The
closing line is "if you catch yourself about to ask 'should I
continue?', the answer is always yes."
2. New "Subagent policy" section. Default is Sonnet for all execution
work — implementers, researchers, spec-followers. Opus is reserved
for load-bearing quality review at phase boundaries. This codifies
what the memory files already said (feedback_subagent_models.md)
but is binding in CLAUDE.md so it applies to every new session
including ones that haven't read memory yet.
3. New "Roadmap discipline" section. Points at
docs/plans/2026-04-11-roadmap.md as the single source of truth and
docs/superpowers/specs/*.md as the per-phase detailed specs. Five
rules: re-read before starting new work, brainstorm when reality
diverges, update the shipped table when a phase lands, don't invent
phase numbers mid-session, name the phase in every commit message.
Directly addresses the "Phase 11 / Phase 9.3 mid-sentence" process
smell the agent hit in this session.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements the other half of ObjDesc: SubPalettes (palette-range
overlays) that repaint palette-indexed textures with per-entity color
schemes. Ported algorithm from ACViewer Render/TextureCache.IndexToColor
after the user pointed out I was prematurely implementing from scratch
instead of checking all the reference repos.
The Nullified Statue of a Drudge sends (setup=0x020007DD with a drudge
GfxObj animPart replacing part 1, plus 2 texChanges targeted at part 1,
plus 1 subpalette id=0x04001351 offset=0 length=0). The TextureChanges
swap fine detail surfaces; the SubPalette with length=0 ("entire palette"
per Chorizite docs) remaps the drudge's flesh-tone palette to stone.
Without this commit, the statue looked like a normal flesh drudge
because palette-indexed textures decoded with the base flesh palette.
Added:
- Core/World/PaletteOverride.cs: per-entity record carrying
BasePaletteId + a list of (SubPaletteId, Offset, Length) range
overlays. Documents the "offset/length are wire-scaled by 8"
convention and the "length=0 means whole palette" sentinel.
- WorldEntity.PaletteOverride nullable field. Per-entity (same across
all parts), in contrast to MeshRef.SurfaceOverrides which is per-part.
- TextureCache.GetOrUploadWithPaletteOverride: new entry point that
composes the effective palette at decode time. Composite cache key
is (surfaceId, origTexOverride, paletteHash) so entities with
equivalent palette setups share the GL texture.
- ComposePalette: ports ACViewer's IndexToColor overlay loop:
for each subpalette sp:
startIdx = sp.Offset * 8 // multiply back from wire
count = sp.Length == 0 ? 2048 : sp.Length * 8 // sentinel
for j in [0, count):
composed[j + startIdx] = subPal.Colors[j + startIdx]
Critical detail: copies from the SAME offset in the sub palette, not
from [0]. Both base and sub are treated as full palettes sharing an
index space.
- StaticMeshRenderer.Draw: three-way switch on (entity.PaletteOverride,
meshRef.SurfaceOverrides) picks the right TextureCache path:
- Both → palette override (it handles origTex override internally)
- Only tex override → GetOrUploadWithOrigTextureOverride
- Neither → plain GetOrUpload
- GameWindow.OnLiveEntitySpawned: builds PaletteOverride from
spawn.BasePaletteId + spawn.SubPalettes when the server sent any.
Reference note: the user asked "but I mean THIS MUST BE IN WORLDBUILDER"
which was the right push. WorldBuilder is actually a dat VIEWER and its
ClothingTableBrowserViewModel is a 10-line stub — it doesn't apply
palette overlays because it doesn't need to. The actual algorithm lives
in ACViewer (a MonoGame character viewer), which I should have checked
earlier. CLAUDE.md updated with a standing rule: always cross-reference
all four of references/ACE, ACViewer, WorldBuilder, Chorizite.ACProtocol,
plus holtburger. A single reference can be misleading; the intersection
is usually the truth.
Tests: 77 core + 83 net = 160, all green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CLAUDE.md captures the project goal (modern C# AC client with
first-class plugin support) and sets Claude's operating mode to
"lead developer" — drive phases continuously and only pause for
decisions that genuinely need the user's input. Reduces check-in
overhead on the long tail of phase work.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>