Commit graph

253 commits

Author SHA1 Message Date
Erik
2757dda63a docs(pipeline): MP0 implementation plan - frame profiler + baseline capture
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>
2026-07-05 19:03:59 +02:00
Erik
113a59887a docs(pipeline): Modern Pipeline (MP) side-track design spec
Umbrella design for the user-commissioned performance side track:
MP0 honest frame profiler -> MP1 baked asset pak (acdream-bake CLI,
mmap zero-copy reader) -> MP2 retail distance-degrade (hide-only cut)
-> MP3 Arch ECS render world + delta submission -> MP4 zero-alloc +
flat physics data (queued behind M1.5 #137) -> MP5 jobs (stretch,
evidence-gated).

Decisions recorded: C# not Rust (bottleneck is architectural, not
language); ECS scoped to the render world only (simulation keeps its
retail-mirroring OO structure); Arch framework per user choice; bake
as CLI now, client auto-detect later; smoothness gated before the
300+ FPS throughput target. Every phase has a numeric + visual gate;
legacy paths delete at each gate (no lingering dual pipelines).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 18:56:41 +02:00
Erik
78e163a41e feat(L.2b): outbound movement wire parity — RawMotionState default-difference, JumpPack, contact byte
Ports the three retail outbound-movement packers verbatim (decomp-derived
golden bytes, confirmed via the Ghidra bridge, cross-checked vs holtburger):

- D1 — RawMotionState::Pack (0x0051ed10): new AcDream.Core.Physics.RawMotionState
  data type (11 fields + actions, retail defaults) + RawMotionStatePacker that
  sets a flag bit only when the field DIFFERS from its default. MoveToState.Build
  now takes a RawMotionState instead of presence-based nullable params, so the
  over-sent forwardSpeed=1.0 / currentHoldKey=None / default per-axis holdkeys are
  no longer emitted. num_actions packs into bits 11-15 (not "bits 11-31").
- D3 — MoveToStatePack::Pack (0x005168f0) trailing byte =
  (standingLongjump ? 0x02 : 0) | (contact ? 0x01 : 0); explicit contact/
  standingLongjump params (standingLongjump=false honestly until the feature lands).
- D4 — JumpAction rewritten to retail JumpPack::Pack (0x00516d10): extent,
  velocity, full Position, four u16 timestamps, align. Removed the spurious
  objectGuid/spellId u32s; Position is now packed (it was absent). Body 56 bytes.
- Position::Pack (0x005a9640) / Frame::Pack (0x00535130) verified already-correct
  (cellId, origin xyz, quaternion wxyz); locked with a golden test, no change.

GameWindow callers adapted minimally: build the RawMotionState from the existing
MovementResult values (behavior preserved except the intended D1 omissions) and
pass cellId/position/rotation to the Jump send. Pre-existing MotionInterpreter
placeholder struct RawMotionState renamed LegacyRawMotionState (D6/Phase-2 scope,
pure rename) to free the name for the retail-faithful type.

D5 audit: confirmed a real divergence — retail SendMovementEvent (0x006b4680)
stamps only last_sent_position_time after an MTS while SendPositionEvent
(0x006b4770) stamps all three; acdream's NotePositionSent stamps all three on
both paths. Left unchanged (comments added at both call sites), recorded as
register TS-33, deferred to a dedicated cadence-port slice.

Tests: RawMotionStatePackTests / MoveToStateGoldenTests / JumpActionTests /
PositionPackTests + updated MoveToStateTests / AutonomousPositionTests. Full
suite green (Core.Net.Tests 372, full solution 3228 passed / 4 pre-existing skips).

Register: TS-24/TS-25 refreshed (packer now supports actions/style; runtime
emission still deferred), TS-33 added. Roadmap L.2b shipped note added.
Spec: docs/superpowers/specs/2026-06-30-movement-wire-parity-design.md (2-6)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:30:01 +02:00
Erik
33ad231d18 docs(L.2b/L.1b): movement wire-parity slice design + 2026-06-26 audits
Imports the two 2026-06-26 movement/animation retail-parity research docs
(audit D1-D12 + ACE-vs-2013 command-catalog gap) and adds the design spec
for the first implementation slice: dual command catalog (AceModern runtime
default + full-extraction Retail2013 conformance) + outbound wire parity
(RawMotionState default-difference packing D1, contact|standingLongjump
trailing byte D3, retail JumpPack layout D4). Decomp-verbatim, tests-first;
D6 motion-interpreter input-state construction explicitly deferred.

Spec: docs/superpowers/specs/2026-06-30-movement-wire-parity-design.md
Phases: L.2b (movement wire/contact authority) + L.1b (command router).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:34:33 +02:00
Erik
ca94b479bf Merge claude/hopeful-maxwell-214a12 — D.2b UI Studio + faithful importer + Character window
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
2026-06-26 12:33:51 +02:00
Erik
f55650fdcd docs(studio): implementation plan — acdream UI Studio (8 staged tasks)
Bite-sized, gated build order: (1) RenderBootstrap [new code, no GameWindow
edit → zero game-regression risk], (2) StudioWindow + LayoutSource + ui-studio
subcommand, (3) ImGui inspector (canvas FBO + tree + props + click-inspect),
(4) FixtureProvider sample data, (5) live 3-D doll, (6) markup + hot-reload,
(7) editable props + write-back, (8) doll-camera sliders. Pure-logic units
(LayoutSource/FixtureProvider/MarkupWriteBack) unit-tested; GL/window tasks
visual-gated. Spec: docs/superpowers/specs/2026-06-25-ui-studio-previewer-design.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:13:27 +02:00
Erik
d607b55e61 docs(studio): design spec — acdream UI Studio (live previewer + inspector)
A standalone dev tool that renders any acdream UI panel through the
PRODUCTION renderer (UiHost/LayoutImporter/dat-sprite path + the WB mesh
pipeline for the 3-D doll), with an ImGui click-to-inspect inspector,
sample-data fixtures, markup hot-reload + write-back, and render-config
sliders. Collapses the edit→build→login→F12→eyeball loop into edit→glance.

Full v1 scope (user pre-approved): both sources (dat LayoutDesc id +
markup file), full fixtures, editable inspector + doll-camera sliders,
live doll. Seven isolated units; the highest-risk step (extracting a
shared RenderBootstrap from GameWindow.OnLoad) is sequenced first behind
a "game still renders" gate, with a studio-local-duplicate fallback. The
drag-drop designer is deferred to phase 2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:05:31 +02:00
Erik
01594b4cfd merge: integrate main (D.2b paperdoll/inventory UI line) into the physics/collision branch
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
2026-06-25 12:57:46 +02:00
Erik
4f26067755 docs(physics): implementation plan — unified verbatim-collision (5 slices)
Task-by-task plan for the unified collision-inclusion fix: D1 DAT-only shape
authority + delete mesh-AABB, D3 sphere primitive, D2+D5 obstruction_ethereal
port, D8 RemoveCellsForLandblock, D4+W1 entry-restrictions + dispatch wiring.
Each task: grep-named -> pseudocode -> port -> conformance-test, build+test
green, commit; visual gates batched to the end.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 18:51:01 +02:00
Erik
2d9e0cf424 docs(physics): collision-inclusion audit + unified verbatim-collision design
19-agent verified audit of how retail decides which objects collide vs
acdream's per-channel filters. Confirms the user's "1 fix for all collision"
intuition: acdream already has retail's two-layer shape (per-cell shadow
registration + query-time exemption); the divergences are now narrow and
enumerable, not a scattered filter mess.

Audit (docs/research/2026-06-24-collision-inclusion-audit.md): 6 confirmed
deviations (D1 mesh-AABB phantom HIGH, D2 ETHEREAL-alone, D3 no sphere
primitive, D4 entry-restrictions, D5 obstruction_ethereal absent, D8 cell-
transform stale cache) + 2 refuted by the adversarial pass (D6 placement-
insert present in BSPQuery; D7 terrain pass-through is the #135/#138
streaming-gap, retail does it too). Loader verified faithful: CacheGfxObj
reads PhysicsBSP gated on HasPhysics; the mesh-AABB is a pure additive
non-faithful layer.

Design (docs/superpowers/specs/2026-06-24-unified-collision-inclusion-design.md):
"1 fix" = one DAT-only shape authority (delete mesh-AABB), one query
predicate, four faithful channels kept distinct (retail keeps find_env/
find_building/find_obj separate), one per-apply rebase invariant. 5
independently-gated slices. Retires register rows AP-2 + AD-7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 18:43:13 +02:00
Erik
6491798edf docs(perf): dense-town FPS attribution (CPU-bound, GPU=0.5ms) + cellobject-batch spec
Attribution report: the handoff's "~12ms GPU" was a glFinish artifact; the clean
split measures GPU=0.5ms and a ~96% CPU-bound frame whose cost scales with visible
buildings. [CPU-PHASE] ranks it: cellobjects 3.5 / landscape 2.6 / partition 2 /
dynamics 1.2 ms; floods, punch-seal, clip-allocs, shells all <0.3ms (refuted).

Spec: iteration-1 fix = batch the per-cell WbDrawDispatcher.Draw calls in
DrawCellObjectLists into one cross-cell draw (the proven cells-shell pattern),
two-loop structure to keep particle-after-statics depth ordering. Target: dense
town solidly 144+.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:41:45 +02:00
Erik
fd9354f69e docs: implementation plan — EnvCell shell batching
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 15:49:18 +02:00
Erik
fcf60d868a docs: spec — batch EnvCell shell draws (dense-town FPS root cause)
Live profiling found the dense-town (Arwic 29fps) bottleneck: EnvCellRenderer
.Render called ~94x/frame (per-cell x opaque+transparent) = 24.75ms = 75% of
the GPU frame. Render is a heavy per-frame method (state reset + SSBO upload +
MDI) invoked per-cell for far->near transparency order. Eliminated, with
evidence, every other suspect incl. the handoff's distance-degrade theory
(entities 0.22ms; resolution-independent => not fill; update 0.1ms).

Spec: batch the shell draws into one Render per pass. Opaque needs no order
(z-buffer) + lighting is per-instance (CellId-keyed SSBO) => safe to batch.
Transparent: skip opaque-only cells, preserve order for the rest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 15:46:19 +02:00
Erik
5ab5d3910e docs: datLock-contention FPS fix implementation plan
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>
2026-06-23 09:29:46 +02:00
Erik
c715e55937 docs: datLock-contention FPS fix design spec
The 30↔200 FPS swing is _datLock contention: the streaming worker holds
the global dat lock for the full per-landblock build (lockwait measured
24ms median / 88ms p95), stalling the update thread's ApplyLoadedTerrain.
Fix (A1): the worker pre-reads the apply's six Get<T> sites into a physics
dat bundle so ApplyLoadedTerrainLocked makes zero DatCollection calls and
its lock(_datLock) is removed. Approved design; implementation next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 09:22:20 +02:00
Erik
1c7f20fd08 docs(D.2b): Slice 2 implementation plan — paperdoll doll + Slots toggle
8-task plan. Tasks 1-4 isolated/TDD (partition+toggle, DollCamera,
UiViewport+Type-0xD, DollEntityBuilder) -> safe for subagents. Tasks 5-7
GL/animation/GameWindow integration -> INLINE per the CLAUDE.md
"no integrate-via-subagent without full context" rule, static-doll-first
then animate, verified at build + visual gate. Task 8 wrap-up + gate.

Concrete API refs pinned this session: WbDrawDispatcher.Draw call site
(8756), animatedIds assembly (8369), SetCycle idle (3514-3571),
WorldEntity build template (3390-3431), SceneLightingUbo/Binding,
ManagedGLFramebuffer + GLStateScope, UiButton.OnClick, inventory frame
.Visible. Flagged the second-Draw-per-frame dispatcher caveat (951-959).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 08:53:12 +02:00
Erik
4f2ad98915 docs(D.2b): Slice 2 design — paperdoll doll viewport + Slots toggle (RTT)
Brainstorm-approved design for Sub-phase C Slice 2: the 3-D doll
UiViewport (dat Type 0xD) + the "Slots" toggle, extending the shipped
PaperdollController.

Key decisions settled in the brainstorm:
- Compositing = render-to-texture (reuse ManagedGLFramebuffer + GLStateScope):
  the doll renders to an off-screen buffer in a pre-UI hook, then the
  UiViewport widget blits it as a normal sprite -> correct painter order
  for free, fully sealed 3-D pass.
- The armor/non-armor partition is the decomp-exact 9-slot set that
  ListenToElementMessage (idMessage==1, 0x100005be) flips, not an
  EquipMask heuristic.
- Doll = a dedicated WorldEntity cloned from the local player's Setup +
  current ObjDesc (the player IS a WorldEntity -- corrects the handoff),
  re-dressed on ObjDescEvent 0xF625; reuses EntitySpawnAdapter/
  AnimatedEntityState.
- Seam IUiViewportRenderer lives in AcDream.App.UI (intra-App decoupling),
  not Core -- user-approved divergence from the handoff's "Core interface".

Recovered the corrupted light immediate ("ff&?" = 0x3f266666 ~= 0.65).
AP-66 reworded: empty-slot frame stays (slots ring the doll, no overlay).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 08:40:48 +02:00
Erik
3b8a39c49e fix(D.2b): PaperdollController review fixes — player-scope Concerns, loop clarity
Code-quality review on Task 5:
- I1: Concerns was unscoped (CurrentlyEquippedLocation != None) → an NPC's
  wielded item (which also carries that wire field) triggered spurious full
  repaints. Narrowed to (WielderId==p || ContainerId==p), matching
  InventoryController; OnObjectMoved's from/to-player backstop still catches
  unwield-into-a-side-bag. Populate's own scope already prevented wrong data;
  this kills the wasted repaints.
- I2: replaced the dual-`index++` (assign-vs-skip) with a for-i loop;
  SlotIndex = SlotMap position (= the drag payload's SourceSlot on unwield).
- M1/M2: comment that the cell's SpriteResolve + the discrete-slot accept/
  reject ring (0x060011F9/F8, not the grid insert-arrow) are factory-provided.
- Added two behavioral tests: a live player wield repaints the slot
  (ObjectMoved → Concerns → Populate); an NPC's wielded item never appears on
  the doll (player-scoping).
- Synced the stale spec §4b/§6c/§8 to the Task-3 Option-1 reality (the
  optimistic wield is ContainerId-based and does NOT write WielderId).

App suite 580 passed / 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 22:32:34 +02:00
Erik
c1a84cbe0c fix(D.2b): wield is ContainerId-based — drop the WielderId write (code review)
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>
2026-06-22 22:15:18 +02:00
Erik
cf472bd8db docs(D.2b): paperdoll Slice 1 implementation plan
Six TDD tasks: (1) de-risk probe (equip slots resolve to UiItemList) →
(2) correct EquipMask to canonical retail INVENTORY_LOC + numeric-pin
test → (3) WieldItemOptimistic + equip-aware rollback snapshot →
(4) ConfirmMove on the WieldObject 0x0023 echo → (5) PaperdollController
+ tests + divergence rows → (6) GameWindow wiring + full-suite gate.
Then the user visual gate. Full no-placeholder code; all signatures
verified against source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:50:04 +02:00
Erik
2d7ea7446d docs(D.2b): paperdoll Slice 1 (equip slots) design spec
Sub-phase C, Slice 1 — bind the ~25 paperdoll equip slots to live
equipped-item data + make them drag-drop wield/unwield targets. No 3D
doll (that's Slice 2).

Brainstorm findings baked in:
- The handoff's "build the wire gap" premise is STALE: BuildGetAndWieldItem
  + SendGetAndWieldItem already shipped in B-Wire. The whole optimistic-move
  machine + InventoryController:IItemListDragHandler already exist — Slice 1
  mirrors them. Unwield is free (the inventory grid handler already does it).
- Found a latent bug: acdream's EquipMask enum diverges from canonical AC
  (acclient.h:3193 INVENTORY_LOC) from bit 0x2000 up (phantom HandArmor/
  FootArmor). Correct it to the verbatim retail values + a numeric-pin test.
  Blast radius is safe (4 round-trip test refs).
- Empty equip slots are TRANSPARENT (EmptySprite=0), per the user — faithful,
  zero Slice-2 rework.

Real scope: correct EquipMask (Core) + WieldItemOptimistic & equip-aware
rollback snapshot (Core) + ConfirmMove on the WieldObject 0x0023 handler
(Core.Net) + PaperdollController (App) + GameWindow wiring. Element-id→mask
map verified dump ↔ deep-dive §3a ↔ acclient.h.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:38:24 +02:00
Erik
b657c82df5 docs(D.2b): inventory drag-drop implementation plan
7-task TDD plan: ClientObjectTable optimistic move + confirm/rollback ->
SendPutItemInContainer 0x0019 -> ConfirmMove(0x0022)/RollbackMove(0x00A0)
wiring -> InventoryController : IItemListDragHandler (no-op lift, accept/reject
overlay, optimistic drop + wire; green-arrow 0x060011F7 / red-circle 0x060011F8
export-confirmed) -> GameWindow wiring -> divergence AP-60/61 -> full-suite +
visual gate. Exact code per step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 19:45:47 +02:00
Erik
3b66858893 docs(D.2b): inventory drag-drop (item moving) design spec
Brainstorm output for B-Drag. Drop an inventory item: empty grid slot ->
first empty; on an item -> insert before; on a side-bag cell -> into that
container. Green insert-arrow (valid) / red circle (full). Movement is
OPTIMISTIC/instant per the user — local MoveItem on drop + repaint, server
reconciles via 0x0022 echo, rolls back via 0x00A0 (the rollback the B-Wire
note reserved). InventoryController : IItemListDragHandler; pending-move
tracking in ClientObjectTable (Core, reachable from the Core.Net handlers);
SendPutItemInContainer wraps BuildPickUp 0x0019. Retail anchors: InqDropIconInfo
0x004e26f0 / ItemList_InsertItem / HandleDropRelease / ServerSaysMoveItem.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 19:28:05 +02:00
Erik
24a0b84162 docs(D.2b): container-switching implementation plan
8-task TDD plan: ReplaceContents (Core) -> ViewContents full-replace
wiring (Core.Net) -> SendUse wrapper -> UiItemSlot triangle/square
overlays -> InventoryController _openContainer/_selectedItem +
click roles + indicators -> GameWindow callback wiring -> divergence
register -> full-suite + visual gate. Exact code per step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 14:22:39 +02:00
Erik
bfdab5b9e0 docs(D.2b): container-switching design spec
Brainstorm output for inventory container-switching. Key decision from
the brainstorm: the handoff conflated two orthogonal retail mechanisms —
the open-container TRIANGLE (0x06005D9C, UpdateOpenContainerIndicator) and
the selected-item SQUARE (0x06004D21, ItemList_SetSelectedItem). User
confirmed both are real (a bag is just an item, so it gets the square too)
and chose to ship both this phase, the square uniform across grid+bags and
visual-only (no selected-object-bar wiring yet).

Pins the ViewContents full-REPLACE (ACE writes entries OrderBy
PlacementPosition, no slot field -> ContainerSlot=index), the Use 0x0036 ->
ViewContents 0x0196 round-trip, and the 6 touched files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 14:15:42 +02:00
Erik
b869128df3 docs(teleport): TDD implementation plan — priority residency + fade cover
6 tasks: (1) StreamingController priority-apply, (2) PhysicsEngine residency
query, (3) TAS drives transit + outdoor hold-until-resident (retire the
TeleportArrivalController driver), (4) fullscreen fade overlay, (5) cell-march
lbX hardening, (6) verify + strip the tp-probe. Reuses the dormant
TeleportAnimSequencer as the transit driver; worldReady gates on the
priority-applied destination landblock.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 12:51:07 +02:00
Erik
31602c6a24 docs(teleport): spec — retail residency + fade-cover (refutes _datLock starvation)
Live tp-probe capture refuted the handoff's _datLock-starvation hypothesis:
worker BUILD is fast + uncontended (waited=0ms); the 10-14s 'long transition'
is render-thread APPLY latency, and 'dropped at wrong position' is the
per-frame resolve corrupting the outbound cell frame (lbX zeroed) while the
player sits on an empty world. Design: priority-apply the player's dest
landblock + hold-until-resident behind a retail fade cover (reuse the dormant
TeleportAnimSequencer) + cell-march landblock-id hardening. Foundation-first,
not a hold over slow streaming. Flood-timeslicing + 3D swirl deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 11:18:16 +02:00
Erik
69ad2cf12d docs(D.2b): implementation plan — inventory empty-slot art via cell-template resolution
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>
2026-06-22 10:40:51 +02:00
Erik
660bcc2fcf docs(D.2b): design — faithful inventory empty-slot art via cell-template resolution
Brainstorm + decomp investigation for the OPEN issue "Inventory + equipment
slots show the wrong empty-slot background art". Verified against the named
decomp that retail's UIElement_ItemList::InternalCreateItem (004e3570) sources
each list's empty-cell sprite from attribute 0x1000000e on the list's own
ElementDesc -> catalog LayoutDesc 0x21000037 -> the prototype's ItemSlot_Empty
(0x1000001c) media. acdream hardcodes 0x060074CF (the generic toolbar square),
bypassing the per-list cell-template inheritance entirely.

Design ports the resolver: a new ItemListCellTemplate.ResolveEmptySprite helper
(mirrors the existing GameWindow 0x21000037 digit-array read), a
UiItemList.CellEmptySprite property, and InventoryController wiring for the
contents grid / side-bag / main-pack lists. Toolbar untouched (its 0x060074CF
is correct); paperdoll silhouettes stay Sub-phase C.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 10:31:38 +02:00
Erik
7aba6d235c docs(D.2b): inventory window finish (Stage 1) implementation plan
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>
2026-06-21 20:15:48 +02:00
Erik
0fbb76b2e7 docs(D.2b): inventory window finish (Stage 1) spec
Stage 1 of the "full retail inventory" arc (Stage 2 = paperdoll, separate
spec). Grounded in the dat dumps: contents grid 0x100001C6 is 192x96 (6x3) +
scrollbar 0x100001C7 (16x96); backdrop 0x100001D0 is full-window 300x362
(so the "torn" look is the unclipped grid overflowing below the frame —
fixed by clipping); side-bag column 0x100001CA is 36x252 (7 slots).
Components: (A) UiItemList clip+scroll reusing UiScrollable + bind the gutter
scrollbar like ChatWindowController; (B) backdrop coverage — primary fix is A,
residual gated on the post-A screenshot; (C) side-bag column pads empty slots
up to capacity at 36px pitch. Paperdoll + B-Drag out of scope.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 20:07:10 +02:00
Erik
82462ff153 docs(teleport-flow): implementation plan — 22 TDD tasks across 5 slices (Work item B)
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>
2026-06-21 19:42:28 +02:00
Erik
aab9ddaf6b docs(teleport-flow): design — unified retail TeleportAnimState flow (Work item B keystone)
Brainstormed + decomp-researched the retail teleport flow, the keystone of
the #138/#145 teleport cluster. Five oracle calls locked: full retail TAS;
hold-until-landblock-loaded readiness (folds in the #145 residual / Work
item A); unify all four entry points (login/logout/death/portal); build the
literal portal swirl this pass; logout = animation + 0xF653 + disconnect
(char-select UI deferred).

Grounded in a verified 5-agent decomp pass (workflow wf_f0c07c93-7aa): the
7-state TeleportAnimState machine + golden constants (FADE=1s, MIN/MAX
CONTINUE=2/5s, FPS=40), the teleport_in_progress hold gate, per-entry start
states, sounds, input lock, exit (LoginComplete 0xA1). Load-bearing acdream
facts re-verified personally: AddLandblock atomicity, streaming-progresses-
during-hold, outdoor place-immediately.

Spec: docs/superpowers/specs/2026-06-21-retail-teleport-flow-design.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 19:03:10 +02:00
Erik
ca94d1d2f6 docs(D.2b-B): B-Wire implementation plan
16 bite-sized TDD tasks for the inventory wire pass: ClientObjectTable
UpsertProperties/UpdateStackSize (Core); PrivateUpdatePropertyInt 0x02CD,
SetStackSize 0x0197, InventoryRemoveObject 0x0024 parsers; ParseViewContents
0x0196; 0x0022 4th-field + 0x00A0 error fixes; DropItem/GetAndWieldItem/
NoLongerViewingContents builders + Send wrappers; PD player-property delivery
+ GameEvent registration; WorldSession dispatch; ObjectTableWiring apply-all-
ints + player-int route + stack/remove; GameWindow + InventoryController
wiring; divergence/ISSUES/roadmap/memory bookkeeping. Each pure parser/builder
/table-method is TDD'd; glue follows the codebase convention (dispatcher-
routed GameEvents unit-tested via the GameEventWiringTests harness, top-level
GameMessages build+run-verified). Plan derived from reading every call site.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 18:41:43 +02:00
Erik
5737951a19 docs(D.2b-B): B-Wire spec — inventory wire layer
Design/spec for B-Wire (follows B-Controller c38f098). Root cause found:
the burden binding already reads wire EncumbranceVal (PropertyInt 5) but
the value never arrives — login PD parses the player's int table then
drops it, the live 0x02CD (PrivateUpdatePropertyInt, no guid) is unparsed,
and ObjectTableWiring gates all non-UiEffects ints out.

Scope (user chose the full wire pass): player-property delivery (login PD
bundle upsert + 0x02CD parse + loosen the apply gate, retiring AP-48/AP-49),
latent-bug fixes (0x0022 4th field, 0x00A0 error), new C→S builders
(DropItem 0x001B / GetAndWieldItem 0x001A / NoLongerViewingContents 0x0195),
new S→C parsers (ViewContents 0x0196 GameEvent; SetStackSize 0x0197 +
InventoryRemoveObject 0x0024 GameMessages), and WireAll registration.
Opcodes pinned against ACE GameMessageOpcode.cs / GameActionType.cs; every
format requires grep-named → cross-ref → pseudocode → port + conformance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 18:25:58 +02:00
Erik
865aae8876 docs(#145): Slice 3 — capture cellId/anchor-consistency + indoor-staleness edge cases + sweep threading map
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 11:08:13 +02:00
Erik
9e184eb861 docs(#145): Slice 3 design — carried-anchor (body.Position - CellPosition.Origin) replaces TryGetTerrainOrigin
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 11:05:54 +02:00
Erik
7cae03951e docs(#145): plan — split Slice 2 into 2a (Core, shipped) + fold 2b seeding into Slice 3
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:57:04 +02:00
Erik
c980763322 refactor(physics #145): Slice 1 — rename Frame->CellFrame to avoid DatReaderWriter.Types.Frame collision
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>
2026-06-21 10:34:36 +02:00
Erik
ed32db70d7 docs(#145): implementation plan — cell-relative physics frame (7 slices)
Bite-sized, TDD-structured 7-slice plan for subagent-driven execution:
(1) Position/Frame types + GetBlockOffset, (2) PhysicsBody carries
Position, (3) membership via AdjustToOutside — closes the cascade,
(4) inter-tick collision state cell-relative + validate_transition
lockstep, (5) wire off Position, (6) _liveCenter render-only, (7)
teleport velocity-idle. Conformance via transform-on-read.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:23:37 +02:00
Erik
fd1f86b771 docs(#145): Option B design spec — cell-relative physics Position frame
Approved design for the cell-relative physics frame port: retire
_liveCenter from physics (render-only), carry Position{ObjCellId,
Frame{local in [0,192), quat}} as the source of truth, port
get_block_offset (verified 0x0043e630 nets to delta-landblock * 192 m),
translate inter-tick collision state per-cell like retail (0x0050a592).
7-slice parallel-frame migration; conformance via transform-on-read (no
fixture re-capture).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:18:18 +02:00
Erik
5b3295ad13 docs(D.2b-B): InventoryController implementation plan (8 tasks, TDD)
Bite-sized TDD tasks: BurdenMath encumbrance ports (T1), SumCarriedBurden
fallback (T2), UiMeter vertical fill (T3), InventoryController bind+populate
(T4) + burden+captions (T5), GameWindow wiring (T6), divergence/docs (T7),
visual gate (T8). Self-reviewed against the spec; test helpers verified
against the shipped widget APIs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 08:50:21 +02:00
Erik
0e273ff2ac docs(D.2b-B): InventoryController (B-Controller) design spec
Read-only population of the gmInventoryUI tree: bind 0x21000023 by id,
fill the 'Contents of Backpack' grid + the right-strip pack-selector from
ClientObjectTable, drive the vertical burden meter, render the Type-0
captions. Faithful selector + full faithful burden meter (per brainstorm).

Ported AC algorithms with decomp anchors: CACQualities::InqLoad (0x0058f130),
EncumbranceSystem::EncumbranceCapacity (0x004fcc00) / Load (0x004fcc40),
gmBackpackUI::SetLoadLevel (0x004a6ea0), UIElement_Meter::DrawChildren
(0x0046fbd0) / Initialize (0x0046f7b0, m_eDirection from property 0x6f).
Key finding: BuildMeter's single-image sprite assignment is already correct
(meter-own=track, child=fill); only vertical fill is new.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 08:31:21 +02:00
Erik
132bf36daa docs(D.2b-B): B-Grid implementation plan
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>
2026-06-20 22:32:37 +02:00
Erik
aa60615912 docs(D.2b-B): B-Grid design spec — inventory sub-window mount + grid mode
First step of Sub-phase B (inventory window). Dumped LayoutDesc 0x21000023:
the three nested panels (paperdoll/backpack/3D-items) are Type-0 pure-
container leaves that nest via the EXISTING BaseElement+BaseLayoutId
inheritance path (BaseLayoutId → 0x21000024/22/21), NOT game-class Types as
the research agent claimed. ElementReader.Merge drops base children, so they
import empty today. Mount = a surgical ~4-line LayoutImporter.Resolve change:
a childless, media-less inheritor attaches its base's resolved subtree
(predicate ShouldMountBaseChildren, unit-testable; inert for media-bearing
inheritors + childless style prototypes, so vitals/chat/toolbar are
unaffected). Plus UiItemList N-cell grid mode (single-cell toolbar preserved).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 22:25:50 +02:00
Erik
8457bf0006 docs(D.2b-A): window manager implementation plan
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>
2026-06-20 21:28:50 +02:00
Erik
8f30585cb0 docs(D.2b-A): window manager + F12 inventory toggle design spec
Sub-phase A of the window-manager → inventory → paperdoll arc. A named-
window registry on UiRoot (Show/Hide/Toggle/BringToFront) + raise-on-click
+ wiring the EXISTING InputAction.ToggleInventoryPanel (already F12-bound,
retail-faithful) through OnInputAction, toggling a throwaway placeholder
inventory window that Sub-phase B replaces with the real gmInventoryUI.

Cross-check correction vs the handoff: retail's inventory key is F12, not
I (I = Laugh emote per retail-default.keymap.txt:246). Reuses the existing
action, so no new keybind — rebindable via the Settings panel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 21:21:29 +02:00
Erik
e58be3f030 docs(D.2b): toolbar collapse-to-one-row design spec
User request: the toolbar frame resizes vertically between one row (row 2
hidden, minimum) and two rows (shown), SNAPPING between the two stops; default
expanded. Small toolkit feature: UiElement.MaxHeight + a ResizableEdges mask
(bottom-edge-only) + a UiCollapsibleFrame (snaps height to the nearer stop and
ties row-2 visibility to it in OnTick) + the GameWindow mount (compute the two
heights from the layout, top-anchor the content so row 1 never reflows). Retail's
real mechanism is keystone.dll (no decomp) + the dat stacks both rows always —
so this is a toolkit UX from the user's retail observation; amends IA-17.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 18:25:46 +02:00
Erik
7193bed309 docs(D.5.3/B.2): toolbar shortcut drag implementation plan
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>
2026-06-20 15:05:02 +02:00
Erik
19f1b8b614 docs(D.5.3/B.2): toolbar shortcut drag interactivity design spec
From the user's visual-gate feedback + a retail decomp trace this session.
Retail's model is remove-on-lift / place-on-drop / no-restore (confirmed:
RecvNotice_ItemListBeginDrag 0x004bd930 → RemoveShortcut 0x004bd450 at lift;
HandleDropRelease 0x004be7c0 places + bumps displaced → source; off-bar drop
leaves it removed). This unifies the user's points 3/4/5 into one mechanism.

Verified the drop sprite against client_portal.dat: 0x060011F9 = green RING
(inventory), 0x060011FA = green CROSS (toolbar) — user was right.

Scope (toolbar-internal; drag-from-inventory is Stream C): spine extensions
(lift hook on IItemListDragHandler; ghost snapshotted at BeginDrag at full
opacity so it survives the source emptying; FinishDrag delivers a drop only on
a real hit, off-bar = nothing); a mutable 18-slot ShortcutStore; ToolbarController
as the live handler (OnDragLift removes; HandleDropRelease places + bumps); the
AddShortcut 0x019C / RemoveShortcut 0x019D wire (fix the BuildAddShortcut param
names; SendAddShortcut/SendRemoveShortcut on WorldSession); green-cross overlay.
Amends AP-47 (ghost now full opacity), retires TS-33 (real wire replaces the stub).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 14:57:31 +02:00