Five bite-sized tasks: FrameStatsBuffer (TDD), FrameProfEnabled flag +
GpuFrameTimer TimeElapsed ring, FrameProfiler facade (TDD on the report
formatter), GameWindow wiring (one boundary call + three stage scopes)
with DebugPanel mirror, and the user-driven baseline capture that gates
MP1. Records two spec deviations: GPU per-stage timestamps deferred
(frame is CPU-bound at ~0.5ms GPU), and the toggle lives in
RenderingDiagnostics per the diagnostic-owner rule, not RuntimeOptions.
Encodes the discovered GL constraint: whole-frame TimeElapsed is
mutually exclusive with ACDREAM_WB_DIAG per-pass queries.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
UI Studio (preview panels through the production renderer), importer dat-fidelity (Fix A/B/C/4/5:
the importer carries dat font/justification/color; boundary look=importer / state=runtime), and the
Character window Attributes tab (reads as retail). 3062 tests green.
Handoff: docs/research/2026-06-26-mockup-stage-handoff.md.
# Conflicts:
# docs/ISSUES.md
# docs/architecture/retail-divergence-register.md
Brings the 134 D.2b UI commits onto the physics/collision development line so
main can fast-forward. Today's #149 (BSP-less static collision) + #150 (open
doors fully passable) + the full collision/streaming/dense-town-FPS arc meet
the paperdoll/inventory work.
# Conflicts:
# docs/ISSUES.md
# docs/architecture/retail-divergence-register.md
5-task TDD plan: PhysicsDatBundle on LoadedLandblock; worker pre-reads the
apply's six Get<T> sites into it; ApplyLoadedTerrainLocked reads from the
bundle; drop the apply's lock(_datLock); verify lockwait->0; strip probes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code-quality review (needs-changes) on Task 3: WieldItemOptimistic wrote
item.WielderId directly, but RollbackMove (via MoveItem) never cleared it
→ a wield-rollback left a pack item with a stale WielderId=player.
Root-cause fix (vs the reviewer's snapshot-WielderId suggestion): acdream's
existing WieldObject 0x0023 confirm models a wielded item as ContainerId=
wielder + equip=mask and does NOT touch WielderId. So the optimistic path
must match — drop the WielderId write entirely. Optimistic state now equals
the confirmed state, rollback fully restores through MoveItem alone, and the
stale-state class is structurally eliminated. The paperdoll's
(WielderId==p || ContainerId==p) filter still matches optimistic wields via
ContainerId (login-equipped items match via WielderId from their CreateObject).
Also: + the wield+move outstanding-count combo test (spec §8), MoveItem doc
note that it doesn't manage WielderId, and the test pins WielderId==0 post-
optimistic-wield to document the model. Core 74/74 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5-task TDD plan for the OPEN empty-slot-art issue: ItemListCellTemplate resolver
(ports UIElement_ItemList::InternalCreateItem's 0x1000000e -> 0x21000037 lookup),
UiItemList.CellEmptySprite, InventoryController + GameWindow wiring, divergence
rows AP-55/AP-56, ISSUES close + visual gate. Each task is test-first with exact
code; Task 1 pins the exact retail sprites from the live dat.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6 tasks: (1) UiItemList clip+scroll via the shared UiScrollable + whole-row
clip; (2) mouse-wheel; (3) InventoryController binds the gutter scrollbar
0x100001C7 like ChatWindowController; (4) side-bag column 36px pitch + empty-
slot padding to capacity; (5) backdrop coverage — screenshot-gated, primary
fix is the clip; (6) verify + bookkeeping. TDD on the pure/controller logic
(internals test-visible); backdrop is the visual gate. Grounded in the dat
dumps + the existing scroll/scrollbar machinery.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bite-sized TDD plan for the retail teleport flow. Slice 1: pure
TeleportAnimSequencer (7-state TAS) + golden-timing tests. Slice 2: the
#145 readiness-gate fix (IsLandblockLoaded + Decide outdoorReady axis +
apparatus probe) — ships independently of the visuals. Slice 3:
TeleportFlowController (delegate-injected, unit-tested) + TeleportFadeOverlay
+ portal wiring (PlaceTeleportArrival split: place vs InWorld so the input
lock persists the whole animation). Slice 4: one yaw-freeze + portal sounds
via the EnumIDMap chain. Slice 5: de-dup login readiness onto Decide, route
login/death through the controller, logout (Shift+Esc) + 0xF653 + disconnect,
remove dead _teleportArrival plumbing. Slice 6 (literal 3D swirl) is a
follow-up plan gated on a cdb asset trace.
Drafted via two research + drafting workflows; slices 3-5 redrafted as one
cohesive unit against a pinned controller API after the first parallel pass
produced cross-slice inconsistencies (missing controller task, triplicated
yaw-freeze, a fabricated PlayerMovementController.Update signature). All
load-bearing signatures personally verified against the tree.
Plan: docs/superpowers/plans/2026-06-21-retail-teleport-flow.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The new value type collided with DatReaderWriter.Types.Frame (used in
physics-adjacent code like ShadowShapeBuilder), which the structural fix
(per-file using-aliases across 6 files) would have re-incurred in every
later physics slice. Renamed the TYPE to CellFrame; the Position.Frame
MEMBER keeps retail's name. Restored the 5 alias-only files to their
pre-Slice-1 state; synced spec + plan. Core 1522 passed / 0 failed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4-task TDD plan: UiItemList grid mode (CellOffset/Columns/cell pitch),
the LayoutImporter.Resolve sub-window mount (ShouldMountBaseChildren +
attach base subtree), GameWindow swap of the placeholder for the real
Import(0x21000023), then full-suite regression guard + visual.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4-task TDD plan for Sub-phase A: UiRoot named-window registry
(Show/Hide/Toggle/BringToFront) + raise-on-click + F12 wiring of the
existing ToggleInventoryPanel action to a throwaway placeholder inventory
window that Sub-phase B replaces. Divergence handled by extending IA-12.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bite-sized TDD plan, three slices: (1) Core ShortcutStore + the AddShortcut
0x019C/RemoveShortcut 0x019D wire (rename BuildAddShortcut to the real
Index/ObjectId/SpellId/Layer fields; SendAddShortcut/SendRemoveShortcut); (2)
spine extensions (OnDragLift hook; ghost snapshotted at BeginDrag at full
opacity; FinishDrag delivers a drop only on a real hit); (3) ToolbarController
as the live handler (store-driven Populate w/ lazy-load; green-cross FA overlay;
OnDragLift removes + HandleDropRelease places + bumps displaced→source; wire
actions injected from GameWindow). Amends AP-47, deletes TS-33. Spec + plan
preapproved; executing subagent-driven next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Integrates main's 19 commits (A7 outdoor/indoor torch lighting Fix A/B/C/D,
GlobalLightPacker, shader updates, UN-7) under the D.5 toolbar/item-model stack
(D.5.1/D.5.2/D.5.4/D.5.3a). Auto-merged cleanly except docs/ISSUES.md.
Conflict resolved: both lineages used #140 for different issues. Kept main's
#140 = "A7 Fix D" (resolved); renumbered the toolbar/selected-object issue to
#141 (note added; this branch's commits/spec still reference #140 — immutable).
The register auto-merged (AP-46 cites file:line, not #140; UN-7 keeps #140=Fix D).
Build + full suite green on the merged tree (2,713 passed / 4 skipped).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolves the divergence-register conflict: kept the accurate per-VERTEX AP-35
(Fix A shipped per-vertex; main's row was the stale pre-Fix-A per-pixel text),
kept main's UI rows AP-37..AP-42, and renumbered this branch's torch-gate row
AP-37 -> AP-43 (AP-37 was taken by main's LayoutDesc row). AP count 41 -> 42.
Retargeted the AP-37 references in WbDrawDispatcher + the CHECKPOINT to AP-43.
Marked ISSUES #140 RESOLVED (b7d655b) with the corrected root cause.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brainstormed design for the action bar's bottom strip: name + Health meter
on selection (mana deferred #140). Decisions: SelectionChanged via property
setter; send QueryHealth(0x01BF) on select. Grounded in retail
gmToolbarUI::HandleSelectionChanged (acclient_2013_pseudo_c.txt:198635) —
clear-then-populate, overlay state 0x1000000b, health gate
IsPlayer||pet||attackable. Render-bug fix is BuildMeter-only (single-image
back+fill meter; UiMeter already renders it).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task-by-task TDD plan: (1) extract GlobalLightPacker (Core, pure) + test + refactor
WbDrawDispatcher; (2) lock the bake contract via LightBake conformance test on the
captured golden torches; (3) D-1 clamp the point-light sum on its own in
mesh_modern.vert; (4) D-2 EnvCellRenderer binds its own per-cell light set (SSBO 4+5)
via SelectForObject over cell bounds; (5) correct register AP-35 + reconcile Fix B.
Concrete code + exact insertion points; visual verification is the acceptance gate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code-review follow-up from Task 2: align StackSizeMax with the other quantity
fields (int?, ACE PropertyInt convention) in Tasks 3/4/5; drop the (int) cast.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bite-sized TDD plan for the stateful item-icon system. Corrects spec 5.8:
the live 0x02CE event binds in GameWindow (next to VitalUpdated), not
GameEventWiring (which only handles the 0xF7B0 GameEvent dispatcher).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The earlier 'not resizable / fixed-size' note was wrong (inverted edge-flag
reading). Resize shipped: dat edge-anchors reflow per UIElement::UpdateForParentSizeChange.
Noted the two number-render fixes (submission-order + glyph pixel-snap).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Roadmap: update D.2b LayoutDesc importer entry to record that the default
flip shipped 2026-06-15 (bf77a23) — importer is the default at
ACDREAM_RETAIL_UI=1; vitals.xml + ACDREAM_RETAIL_UI_IMPORTER flag retired;
window movable, resize deferred to Plan 2 (WindowManager).
Plan: update "After Plan 1" to mark the flip DONE, clean up the Plan 2
description now that vitals.xml is gone.
Register:
- AP-37 "Why" cell: replace "Gated opt-in (ACDREAM_RETAIL_UI_IMPORTER)"
with "Now the default vitals path (the hand-authored markup vitals was
retired)" — the flag is gone.
- IA-15: add row (was missing from this branch) — D.2b retail UI design
stance, updated to note that the vitals window is now rendered by the
LayoutDesc importer (dat chrome elements), not UiNineSlicePanel;
UiNineSlicePanel/RetailChromeSprites now back only chat window + plugin
panels. IA count header 14 → 15.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
main was 65 commits ahead of this branch's fork point. Only conflict was the
divergence register: both sides appended an 'AP-32' row. Resolved by keeping
main's AP-32..AP-36 (cell-shell lift, look-in cells, alpha deferral, dungeon
streaming, point lights) and renumbering the importer's row to AP-37; AP header
count -> 37. GameWindow.cs auto-merged cleanly. Verified: AcDream.App builds
0/0; AcDream.App.Tests 354 passed / 1 skipped / 0 failed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9-task TDD plan against the re-grounded spec, building on the existing
AcDream.App/UI scaffold: RuntimeOptions toggles, textured-sprite path in
TextRenderer (+ frag uUseTexture=2, + TextureCache size overload), Step-0 chrome
prove-out, UiNineSlicePanel + UiMeter widgets, wire UiHost + live Vitals
(render-only) retiring TS-30, controls.ini loader, MarkupDocument (XML ->
UiElement tree), and the IUiRegistry plugin surface. Exact code per step; pure
parsers TDD'd in AcDream.App.Tests, GL/visual bits user-verified.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
TDD plan for the gated G.3a core: a pure TeleportArrivalController state machine
(hold-until-hydration + force-snap on impossible/timeout) + its GameWindow wiring
(replace the unconditional arrival snap with recenter + deferred BeginArrival;
per-frame Tick; readiness predicate reusing the #107 login triplet) + the EnvCell
physics/visibility hydration decouple + the visual acceptance gate. G.3b/c/d get
their own plans after the gate.
Also syncs the spec: the readiness predicate reuses SampleTerrainZ + IsSpawnCellReady
+ IsSpawnClaimUnhydratable (the validated #107 login gate) rather than a new
IsLandblockApplied query — strictly more faithful, less new surface.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
R-A2b (485e44d) killed the 0171<->0173 churn (maxPop 16->1, measured). Visible flap residual is sec 4 (edge-on openings render-side + corner camera-seal). Camera-damping tried+failed+reverted. The white-walls scare was a RED HERRING: heavy per-frame probes (ACDREAM_PROBE_FLAP) starve the thread-unsafe dat-reader so texture-decode loses the race -> white; a clean launch (no probes) fixes it. The dat-reader thread-safety bug is the real underlying issue (filed). Repo clean at HEAD.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reading retail InitCell (:432896) side test during writing-plans showed retail's flood is acyclic (the back portal fails the side test, so 0171<->0173 can't cycle). Our flood traverses the back portal -> the cycle -> the churn. Option B (user-chosen): cull the back portal like retail, keep the forward-portal void rescue, remove the dead cap. Phase 1 pins WHY the back portal is traversed (B1 eyeInsideOpening bypass vs B2 CameraOnInteriorSide convention) before the fix; spec REVISION updated A->B.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The indoor doorway flap is the portal flood's re-enqueue churn (0171<->0173 mutual re-contribution; drifted near-duplicate regions AddRegion won't dedup -> grew -> re-enqueue, capped at MaxReprocessPerCell=16 -> eye-sensitive flood depth -> grey flash). Confirmed live: launch-churn-confirm.log shows maxPop=16 on 44% of frames during a doorway walk-through. The 2026-06-08 'maxPop=1, churn refuted' verdict was a camera-turn-at-rest capture (wrong reproduction); its DO-NOT is overturned.
Fix (Option A, user-approved): contributions already covered by the neighbour's accumulated view don't grow it (no re-enqueue); only the uncovered remainder propagates -- retail's 'redundant -> empty before copy_view' (copy_view confirmed to just append). Remove MaxReprocessPerCell; keep re-processing of genuinely-new slices. Scope: PortalVisibilityBuilder only. Revives 2026-06-08 spec+plan (banners redirected).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the outdoor root's single unified reverse-portal flood (whose root-level
portal-side test oscillated as the chase eye grazed a doorway — the measured
flood 2<->6) with retail's per-building floods.
- OutdoorCellNode.Build(uint): portal-less land root; floods only itself ->
full-screen OutsideView -> terrain (PortalVisibilityBuilder IsOutdoorNode seed).
- PortalVisibilityBuilder.ConstructViewBuilding: per-building flood seeded at a
building's own finite entrance (retail ConstructView(CBldPortal) 0x5a59a0 via
DrawPortal 0x5a5ab0 / portal_draw_portals_only 0x53d870). Entrance-bounded ->
consistent ~2-cell depth (measured retail cell_draw_num, handoff OPTION-A 3.4).
- RetailPViewRenderer.DrawInside: when the root is the outdoor node, group nearby
cells by BuildingId and merge each per-building flood into the frame before
assembly; existing shells/object-list draw path unchanged. 48 m seed cutoff.
- GameWindow: pass flat NearbyBuildingCells only on outdoor-node frames.
Tests: +3 PortalVisibilityRobustnessTests (per-building touches ~2 cells, membership
stable under the measured 36 um eye jitter). UnifiedFloodTests retired (its subject,
the unified flood from the outdoor node, is removed); surviving full-screen-OutsideView
coverage moved to OutdoorCellNodeTests. App Rendering 207/207, Core movement 14/14.
Conformance-verified sound; the grazing-doorway flap is the visual acceptance test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace ReferenceEquals(clipRoot, _outdoorNode) object-identity checks with the
documented LoadedCell.IsOutdoorNode flag (4 sites) so they survive R-A2 changing
the outdoor root's portals. Behavior-preserving (build + targeted suites green:
App PortalVisibilityBuilderTests 24/24, Core PlayerMovementControllerTests 14/14).
Right-sized from the planned 'collapse to one root': reading the live dispatch,
the viewerRoot ?? outdoorRoot split is already correct (viewerRoot feeds
cameraInsideCell/lighting via the older CellVisibility BFS; clipRoot is the render
root), and the 2026-06-07 cutover flip already made in-world frames single-path
DrawInside. The real flap fix is R-A2 (per-building floods). Dead exterior
DrawPortal look-in deletion deferred to R-A3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>