Commit graph

284 commits

Author SHA1 Message Date
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
9408c062d2 docs: deep handoff for the multi-window UI mockup stage
Covers: what's done (UI Studio, faithful importer Fix A/B/C/4/5, Character window), the
look-vs-runtime boundary, the mockup goal + approach (host the production UiHost live),
must-fix issues (#149 inventory, #150 GameWindow fonts), patterns/lessons, reference map,
and first concrete steps. For the next agent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:17:27 +02:00
Erik
26d5c101e4 docs: add 2026-06-26 character window retail reference screenshot notes
Transcribed from user's retail screenshots during the character-window
polish session; documents name-white, large-gold-level, XP captions,
Infinity! cost, and the selected-row bar sprite — used as the acceptance
target for Fix A/B/C.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 10:32:47 +02:00
Erik
0d87e4dc31 docs(studio): faithful character-window element spec (decomp-grounded)
Element map for LayoutDesc 0x2100002E (the tabbed Attributes/Skills/Titles window) +
the importer-mount reality + the StringTable verdict + the follow-up list. Grounds the
CharacterStatController work in 0e644b5 and the remaining refinements.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 19:48:38 +02:00
Erik
2fe7b4a057 docs(studio): dump-panel render sweep — all 26 windows verified
Rendered all 26 dump windows through the studio's --screenshot mode + a
System.Drawing contact-sheet montage. The generic dump LayoutSource renders
every panel with no crash; fidelity tracks static-chrome (in the dump) vs
runtime content (slots/stats/appraise, not in the dump). Static-rich panels
(vitals/toolbar/character/map/radar/effects/chat…) render great; runtime-only
windows (examine) are correctly blank; inventory shows backdrop+frame but its
nested sub-window chrome is sparse (next investigation). screenshots gitignored.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:50:39 +02:00
Erik
e3de7f0dab feat(studio): dump LayoutSource — preview any retail window from the UI dump
Adds Task 4b: a second load path for the UI Studio that reads the committed
retail UI layout dump (docs/research/2026-06-25-retail-ui-layout-dump.json)
and renders any of the 26 retail windows as a static sprite hierarchy.

New files:
- src/AcDream.App/Studio/UiDumpModel.cs — POCOs + System.Text.Json parse of
  the dump (UiDump, DumpPanel, DumpNode, DumpRect, DumpStateSet, DumpImage,
  DumpState + UiDumpModel static helpers: Parse, ListSlugs, PickImageId).
- src/AcDream.App/Studio/DumpLayout.cs — DumpLayout.Load(path, slug, resolve,
  out err): parses the dump, finds the panel by slug, builds a UiElement tree.
  Internal DumpSpriteElement draws its sprite via DrawSprite (not reusing
  UiDatElement — avoids the ElementInfo/StateMedia dat-import dependency for
  this static mockup). DumpGroupElement is a transparent container for Group
  nodes. Rect basis is ABSOLUTE in the dump (verified: inventory root at
  absolute x=500 and its children also start at x≈500 — child offset from
  parent is 0–50px, not 500px); DumpLayout subtracts parent rect to produce
  parent-relative Left/Top for each child.
- tests/AcDream.App.Tests/Studio/DumpLayoutTests.cs — 5 tests covering:
  inventory load with 0x100001D5 check + >= 40 nodes, unknown slug → null+err,
  root at origin, children are parent-relative, all 26 slugs smoke-load.

Modified files:
- StudioOptions: adds DumpSlug + DumpFile fields; --dump <slug> and
  --dump-file <path> args; ResolveDumpFile() walks up to the solution root
  to find the default dump JSON (mirrors ConformanceDats.SolutionRoot()).
  --dump suppresses the default vitals layout so the two modes are exclusive.
- StudioWindow.OnLoad: when DumpSlug is set, loads via DumpLayout (no
  FixtureProvider, no controllers — static structure only); else falls
  through to the existing LayoutSource + FixtureProvider path.

Results: DumpLayoutTests 5/5 passed; full AcDream.App.Tests 609 passed, 2
skipped (same as before); dotnet build green, 0 warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 16:25:23 +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
dc1e927080 fix(physics): Task 3 follow-up — obstruction_ethereal consume for Cylinder+Sphere shapes
Retail oracle greps confirmed:
- CSphere::intersects_sphere @ 0x00537ae4 (pc:321692): the ethereal branch
  is `void __thiscall` — all paths return void (no COLLIDED). The function
  performs a proximity check only; no blocking result is produced.
- CCylSphere::intersects_sphere @ 0x0053b4a0 (pc:324573): same void-return
  pattern — ethereal branch calls collides_with_sphere (check only, no slide),
  all returns are void = passable.

Change: added `if (sp.ObstructionEthereal) return TransitionState.OK` at the
top of SphereCollision and CylinderCollision in TransitionTypes.cs, mirroring
the void-return semantics of both retail functions. The existing per-object
clear at pc:276989 (line 2837) still fires after the early OK return.

Before this fix: an ethereal-alone NPC/ghost with a Cylinder or Sphere shadow
shape would BLOCK the player (regression introduced when Task 3 made ETHEREAL-
alone fall through ShouldSkip instead of instant-skipping). After: all three
shape types — BSP (via BSPQuery Path 1), Sphere, and Cylinder — correctly pass
through when obstruction_ethereal is set.

Tests: added 4 tests to ObstructionEtherealTests.cs verifying:
- Ethereal Cylinder → passable (sweep passes through, no CollisionNormalValid)
- Ethereal Sphere → passable (same)
- Non-ethereal Cylinder → still blocks (regression guard)
- Non-ethereal Sphere → still blocks (regression guard)
Full Core suite: 1584 pass, 0 fail, 2 skip (pre-existing dat skips).

Pseudocode doc updated with confirmed cyl/sphere ethereal contracts and the
complete set/clear/consume flow summary.

Retail refs:
- CSphere::intersects_sphere @ 0x00537ae4 / acclient_2013_pseudo_c.txt:321692
- CCylSphere::intersects_sphere @ 0x0053b4a0 / acclient_2013_pseudo_c.txt:324573

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 19:30:59 +02:00
Erik
3361a8d776 fix(physics): #137 Task 3 — port obstruction_ethereal verbatim; retire AD-7 shim
Retail CPhysicsObj::FindObjCollisions (0x0050f050) only instant-skips when
BOTH ETHEREAL_PS (0x4) AND IGNORE_COLLISIONS_PS (0x10) are set (pc:276782).
ETHEREAL-alone sets sphere_path.obstruction_ethereal=1 (pc:276806) and
continues to the shape dispatch. BSPTREE::find_collisions (0x0053a496) routes
Path 1 (sphere_intersects_solid) when the flag is set (pc:323742): the open
door has no solid leaf at the doorway, so the test returns OK → player passes
through. CEnvCell::find_env_collisions (0x0052c144) clears the flag first so
ENV walls are never weakened (pc:309580, "D5 clear").

Changes:
- CollisionExemption.ShouldSkip: require BOTH bits for Gate-1 early-out
  (previously ETHEREAL alone returned true — the AD-7 shim). Divergence
  register row AD-7 deleted.
- SpherePath: add ObstructionEthereal field (mirrors retail
  SPHEREPATH.obstruction_ethereal).
- FindObjCollisionsInternal loop: set sp.ObstructionEthereal=(target&0x4)!=0
  before shape dispatch; clear it after (per-object clear pc:276989).
  Also clear at the null-BSP continue site to keep flag clean.
- FindEnvCollisions: clear sp.ObstructionEthereal=false at top (D5 clear
  pc:309580) — ENV cell walls are always solid.
- BSPQuery.FindCollisions Path 1: change `obj.Ethereal` (ObjectInfo.Ethereal,
  always false — dead code) to `path.ObstructionEthereal`. Gate now correctly
  mirrors retail pc:323742: PLACEMENT_INSERT || obstruction_ethereal.

Consume site change (BSPQuery.cs before/after):
  BEFORE: if (path.InsertType == InsertType.Placement || obj.Ethereal)
  AFTER:  if (path.InsertType == InsertType.Placement || path.ObstructionEthereal)
Mirrors retail pc:323742 exactly. obj.Ethereal was dead code (ObjectInfo.Ethereal
is never set true anywhere); the correct flag is SpherePath.ObstructionEthereal.

Tests: 1580 pass / 0 fail / 2 skip (was 1576/0/2 + 4 new in ObstructionEtherealTests.
CellarUp, CornerFlood, DoorCollision, HouseExitWalk all green — no wall regressions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 19:20:02 +02:00
Erik
78e5758185 feat(physics): Task 2 — true sphere collision primitive (CSphere::intersects_sphere)
Setup.Spheres were previously coerced to short cylinders (CylHeight=2*r),
which is geometrically wrong: a cylinder has flat caps; a sphere does not.
This ported CSphere::intersects_sphere (0x00537A80) so sphere-typed shadow
entries are tested as spheres — 3-D distance, no height clamping.

Changes:
- ShadowObjectRegistry.cs: added ShadowCollisionType.Sphere (enum value 2).
  The BuildFloodSpheres anyCyl dedup at :232 is unaffected: only Cylinder
  sets anyCyl=true; Sphere shapes fall through to the BSP-fallback path
  (anyCyl=false → included), which is correct.
- ShadowShapeBuilder.cs: FromSetup now emits ShadowCollisionType.Sphere
  (CylHeight=0) for Setup.Spheres instead of a short Cylinder.
- CollisionPrimitives.cs: added SweptSphereHitsSphere — quadratic swept
  solve ported from ACE Sphere.cs::FindTimeOfCollision, which is a C# port
  of retail's CSphere::intersects_sphere @ 0x00537A80. Sign convention
  confirmed against the decomp: retail negates the root to produce a
  forward t ∈ (0,1].
- TransitionTypes.cs: added Sphere narrow-phase branch between BSP and
  Cylinder in FindObjCollisionsInCell; uses 3-D distance for overlap
  (not XY-only). Added SphereCollision() method implementing the 3-D
  wall-slide response. Updated diagnostic logging at :2734 to cover Sphere.
- Updated ShadowShapeBuilderTests for new Sphere type assertion.
- New SphereIntersectsSphereConformanceTests: 9 geometrically-anchored
  cases (head-on, tangent, perpendicular-miss, lateral-near-miss,
  sweep-away, beyond-step, degenerate-zero-sweep, already-overlapping,
  vertical-sweep).

Retail oracle: CSphere::intersects_sphere @ 0x00537A80 (named-retail);
ACE Sphere.cs::FindTimeOfCollision (C# port, cross-confirmed).
Build: 0 errors, 10 warnings (pre-existing).
Tests: 1576 pass / 0 fail / 2 skip (1578 total).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 19:08:53 +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
bc4f71b932 docs(research): handoff — collision-object-detection vs retail deep-dive ("1 fix for all collision")
Next-session brief: build retail's collision-inclusion model (PhysicsState/
ETHEREAL/HAS_PHYSICS_BSP predicate + per-cell shadow-list registration + the
building-shell/terrain/EnvCell channels), map acdream's per-channel ad-hoc
filters against it, enumerate deviations, and design ONE unified retail-faithful
mechanism to replace them. Motivated by this session's #146/#147 — both were the
same shape (a per-channel filter diverging from retail). Brainstorm-gated,
report-first, no guess-patches. Includes the apparatus inventory + a paste-ready
prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 16:34:13 +02:00
Erik
57e79dc679 docs: handoff — two teleport-OUT issues (no-collision-after-death, char-missing)
Both observed 2026-06-24 after the FPS work; both are the known #135/#138 placed-
but-unstreamed streaming gap (NOT FPS-work regressions — those commits are render-
only). Handoff maps the symptoms to ISSUES, flags the delicate-area lessons (no
guess-patches, the reverted hold, fix-the-foundation, capture-first), and points
at the physics digest + streaming refs + capture apparatus.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 07:57:16 +02:00
Erik
02578ddb74 docs(perf): dense-town FPS — final outcome (75->165) + ISSUES record
Report: OUTCOME section (the two shipped fixes, the glFinish-artifact correction,
the deliberately-unpursued scenery-CPU/terrain-GPU headroom). ISSUES: recently-
closed entry with SHAs + pointers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 00:13:32 +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
1473e4dbf9 docs: handoff — dense-town FPS deep-dive (push past 75fps)
Cells batching shipped (29->75fps, 2.6x); remaining ~12ms is diffuse (particles
~3, punch/seal ~3, ~5.5 unattributed) + frame spikes. Next session: deep-dive
with RenderDoc to attribute + push higher. Distance-degrade theory is dead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 18:23:53 +02:00
Erik
d0dd3b17ad docs: handoff — dense-town FPS = missing distance-degrade (port next)
Session shipped the _datLock-contention fix (lockwait 88ms->0, kept). Remaining
FPS pain (sustained ~30 in Fort Tethana, view-direction-dependent) root-caused to
the absence of distance-based LOD/degrade: we draw every frustum-visible object at
full detail. Next: port retail UpdateViewerDistance/get_degrade. Full mechanism,
file:line map, apparatus state, and DO-NOT-RETRY lessons captured.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 10:05:21 +02:00
Erik
c88bc5c8eb docs(D.2b): Slice 2 handoff — paperdoll 3D doll viewport + the Slots toggle
Carries the corrected paperdoll model forward (figure = live doll, NOT
silhouettes; the Slots button toggles doll-view vs armor-slot-view — REPLACE,
not overlay). Scopes Slice 2 = (A) the toggle (read ListenToElementMessage
idMessage==1) + (B) the UiViewport Type 0xD doll via a Core->App
IUiViewportRenderer seam reusing EntitySpawnAdapter. Includes the decomp
anchors, camera/light immediates to decode, open questions, and the
new-session prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 07:56:54 +02:00
Erik
68971480a7 docs(D.2b): reconcile paperdoll docs to the corrected model (Slice 1 shipped)
- Divergence AP-66: Slice-1 empty-slot frame vs the Slice-2 doll-backed
  transparent look (retired when the doll viewport lands).
- Paperdoll handoff: prominent top note correcting the WRONG "transparent /
  per-slot silhouettes" framing — the figure IS the live 3D doll; the
  Slots toggle + doll = Slice 2. Points to the project memory's Slice 1
  entry for the full DO-NOT-RETRY.

(The detailed shipped-log + corrected model live in the auto-loaded
claude-memory/project_d2b_retail_ui.md, updated this session.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 00:09:52 +02:00
Erik
702058f7d4 docs(D.2b): paperdoll (Sub-phase C) handoff + new-session prompt
Detailed handoff for the next session. Key correction from the deep-dive:
retail empty equip slots are TRANSPARENT (the doll shows through), NOT
silhouettes — the current blue border is wrong. Decomposes into Slice 1
(equip slots: bind to CurrentlyEquippedLocation + drag-to-wield via the
GetAndWieldItem 0x001A wire gap) and Slice 2 (the heavy 3D doll UiViewport
Type 0xD + Core->App IUiViewportRenderer seam). Maps the reuse surface
(UiItemSlot, drag spine, optimistic-move, EntitySpawnAdapter) + open
questions + a paste-ready new-session prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:03:44 +02:00
Erik
ad27d1395a docs(D.2b): container-switching handoff + flag stale M1.5 banner
Banks the next clean inventory win (container-switching: Use 0x0036 -> ViewContents
0x0196 full-replace + the selected-container indicator that retires AP-56) as a
handoff for a fresh session, per the late-session-handoff lesson. Wire layer
already exists; design sketch + open questions captured.

Also flags the CLAUDE.md "Current state" banner as stale — it still tracks M1.5
(indoor world, 2026-06-14) while the active stream has been the D.2b retail-UI
track for weeks. Left the milestone reframing for a fresh reconciliation against
the milestones doc rather than a tired guess.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 13:17:55 +02:00
Erik
f8cda7e86d docs(teleport): foundation-investigation handoff for a fresh-look session
Captures the full teleport-flow journey (Slice 1 kept, Slice 2 hold built +
reverted), the root-cause findings (the IsLandblockLoaded key bug + the real
foundation problem: destination doesn't stream fast/complete during teleport,
likely _datLock starvation from the CreateObject flood), the open foundation
issues (#138 slow/incomplete streaming, lost-collision-after-teleport, FPS
leak Work-C, the PortalSpace freeze-vs-run-through question), and the clean
baseline (dd2eb8b). Written as evidence + open questions, not conclusions, so
a fresh session can re-examine the whole approach.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 10:00:23 +02:00
Erik
e6c8b65f77 docs(D.2b): inventory finish Stage 1 — handoff + divergence rows + ISSUES
Handoff doc for the fresh session (scroll/frame/resize/102-slots shipped +
visually confirmed; next = wrong empty-slot art, then main-pack icon, then
Sub-phase C paperdoll). Divergence AP-52 (side-bag 7 fallback), AP-53 (main-pack
102 fallback), AP-54 (resize approximation). ISSUES: closed the contents-grid
overflow; filed the wrong empty-slot-background issue; Stage 1 SHIPPED entry.
(Memory digest project_d2b_retail_ui.md + MEMORY.md + the don't-kill-clients
feedback updated out-of-tree.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 21:31:24 +02:00
Erik
adaec1845f docs: handoff — fold in retail teleport-anim (TAS) flow + acdream gap analysis for the teleport-flow feature
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 18:24:08 +02:00
Erik
59b4868408 docs: handoff — teleport issues cluster (#145 residual + retail teleport flow + FPS leak)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 18:11:37 +02:00
Erik
1230c07af3 docs(handoff): D.2b B-Controller shipped + visually confirmed; B-Wire next
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 18:08:05 +02:00
Erik
67f98e8e72 docs(#145): verified root cause + decision to port cell-relative physics frame (Option B) + handoff
Workflow wf_87607d15-c43 (4 research streams + synthesis + 3 adversarial verifiers, HIGH confidence) confirmed the far-town runaway is a cell-membership label cascade from a discarded TryGetTerrainOrigin bool (CellTransit.cs:736 -> (0,0) origin for unstreamed neighbors), not a free-fall; 17410 is a wire artifact. User chose the architectural fix: port retail's cell-relative Position + retire _liveCenter from physics. Handoff doc carries the verified mechanism, the retail port table (decomp addresses), acdream divergence sites, apparatus (desync-capture.jsonl + probes + harness template), and the brainstorming-gate requirement.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 09:47:01 +02:00
Erik
b07825cd24 docs(research): #138 handoff — RESOLVED banner + correct the re-hydrate source (not ClientObjectTable)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 08:09:36 +02:00
Erik
4e23a7b9bc docs(handoff): D.2b A+B-Grid shipped, #145 fixed, B-Controller next
Session handoff: window manager (F12) + inventory sub-window mount
(inheritance-children) + UiItemList grid all shipped; #145 ZLevel z-order
fixed (backdrop behind panels). B-Controller next (populate grids + burden
meter from ClientObjectTable). Key discoveries, B-Controller readiness, and
the new-session prompt captured.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 07:52:40 +02:00
Erik
b9445f53fe docs: handoff for #138 (entity re-delivery after teleport) — next session
Full orientation for a fresh session to fix #138: confirmed root (server
objects unloaded on teleport-IN, not restored on return; ACE re-broadcast
unreliable), DO-NOT-RETRY table (cache + render-cull eliminated), the
ClientObjectTable re-hydrate fix direction with file:line pointers, the
launch/probe/account setup, and the gotchas (re-broadcast latency, stale
sessions, don't-kill-clients, entity.Id != ServerGuid).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 07:29:00 +02:00
Erik
a391b86a2e docs(handoff): window manager → inventory window → paperdoll (next D.2b arc)
Frames the next core-panels work for a fresh session, after B.1 (drag spine) +
B.2 (toolbar reorder/remove + wire) + toolbar collapse merged this session.
Three sub-phases in order: (A) minimal window manager (open/close + I-key toggle;
UiHost has no open/close API today, windows are always-on); (B) inventory window
(Stream C, synthesis §4 — UiItemList grid + sub-window mount + wire gaps +
InventoryController; the drag SOURCE that closes B.2's drag-from-inventory via a
SourceKind==Inventory branch in ToolbarController.HandleDropRelease); (C) paperdoll
UiViewport (Type 0xD) + Core→App IUiViewportRenderer seam, heaviest, last. Spell
bar deferred. Reuse the shipped spine/ShortcutStore/wire. Paste-able new-session
prompt + MEMORY index line inside.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 20:38:41 +02:00
Erik
f6a576af8e docs: file #145 (portals work once/session) + session handoff (next: #145 then #144)
Indoor lighting DONE this session (#142/#143 closed). Per user: do NOT merge to
main; start fresh next session. NEXT-session order set in the handoff:
1. #145 — portals only work once per session (run in/out/re-enter repeatedly).
   Machinery: 0xF751 PlayerTeleport -> PortalSpace -> TeleportArrivalController ->
   streaming collapse/expand. Likely overlaps #138. Instrument a 2nd teleport first.
2. #144 — dungeon interiors still too dim vs retail (cdb side-by-side first).

Handoff: docs/research/2026-06-20-indoor-lighting-done-next-portal-reuse-handoff.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 18:31:47 +02:00
Erik
0d8b827721 fix(lighting): indoor interiors now lit — landblock-key bug + viewer light + weenie fixtures
The whole "indoor interiors read dark/flat vs retail" saga was ONE root-cause
bug: EnvCellRenderer.GetCellLightSet derived the landblock key as
`cellId & 0xFFFF0000` (0xXXYY0000), but landblocks are keyed by the streaming
id 0xXXYYFFFF. The lookup missed for EVERY cell, so SelectForObject never ran
and every EnvCell wall received ZERO point lights — torches, lanterns, the
viewer light, all of it. Confirmed by a [cell-light] probe: inBounds=False
selected=0 across 1M+ cell draws; after the fix inBounds=True selected=3-4.
User-confirmed the interiors now look like retail.

Three faithful additions that were blocked by the key bug (and only show now):
- Viewer light (LightManager.UpdateViewerLight): retail's SmartBox::set_viewer
  (0x00452c40) adds a white fill light at the player every frame via
  add_dynamic_light — the dominant interior fill (no sun indoors). acdream had
  NO dynamic lights at all. Params from the cdb capture: intensity 2.25,
  falloff 10, white, offset (0,0,2). Indoor-only via the AP-43 gate.
- Weenie fixture lights (OnLiveEntitySpawnedLocked): server-spawned lanterns/
  braziers carry Setup.Lights but the dat-static registration never saw
  CreateObject entities. Register on spawn; unregister on despawn
  (UnregisterOwner made unconditional). Register row AP-44.
- IndoorObjectReceivesTorches now excludes the 0xFFFF landblock marker (it is
  not an EnvCell) — fixes WbDrawDispatcherIndoorFlagTests.LandblockId_OutdoorFlag0
  (a #142 verification miss).

Divergence register: AP-44 (weenie light spawn-position, no movement tracking),
AP-47 (acdream's 128-light/camera-independent cap keeps interiors always-lit vs
retail's 40-nearest-to-player budget that pops in on approach — intentional,
user-preferred).

Investigation: docs/research/2026-06-20-indoor-torch-lantern-lighting-investigation.md

Core 1505 / App 476 green. Visual gate: user-confirmed "looks like retail now."

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 15:51:55 +02:00
Erik
f7f3e0887b docs(lighting): indoor lighting regime handoff — file #142 (windowed-interior regime) + #143 (portal dynamic light)
Clean handoff for the next M1.5 "indoor world feels right" session, picking up
the two indoor-lighting gaps the user spotted at the #140 visual gate.

#142 (PRIMARY): windowed-building interiors + look-ins read "like outdoors".
Root cause grounded: retail's lighting regime is per-DRAW-STAGE (PView::DrawCells
draws ALL EnvCells in the useSunlightSet(0) interior stage — torch-lit, no sun,
regardless of SeenOutside), while acdream's is a per-FRAME global keyed on the
player's cell (playerInsideCell). So acdream's windowed interiors (SeenOutside)
+ look-ins stay in the outdoor regime. This is the AP-43 residual surfaced.
Fix direction: make sun+ambient per-draw like AP-43's torches (design fork laid
out for a brainstorm). Resolves AP-43.

#143 (SECONDARY): portal swirl casts no light. acdream registers only static
Setup.Lights; the portal is a retail DYNAMIC light (add_dynamic_light ->
minimize_envcell_lighting). Fix: register a dynamic LightSource for portals.

Handoff doc carries the verified retail decomp (useSunlightSet/PView::DrawCells
stages), current acdream line refs, the three gaps, the fix fork, validation
plan, and DO-NOT-RETRY. Neither issue is a regression from #140.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 12:17:59 +02:00
Erik
31d7ffd253 merge: bring main (A7 lighting Fix A–D + UN-7 + #140 Fix D) into the D.5 branch
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>
2026-06-20 12:01:20 +02:00
Erik
c83fd02642 merge: bring main (UN-7, #140 filing, D.2b UI rows) into A7 Fix D round-2 branch
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>
2026-06-20 09:29:53 +02:00
Erik
b7d655bce7 fix(lighting): A7 Fix D round 2 — outdoor objects get NO torches (retail useSunlight gate) (#140)
The Holtburg meeting-hall facade washed out warm/bright vs retail. The round-1
checkpoint blamed torch REACH (acdream Falloff 6×1.3=7.8m vs a supposed retail
Falloff 4). That theory is WRONG, and this commit fixes the real cause.

Empirical (HoltburgTorchFalloffProbeTests, headless dat dump via the production
LightInfoLoader): the orange entrance torch (setup 0x020005D8) is raw dat
Falloff 6 and acdream reads it FAITHFULLY — there is no Falloff-4 torch anywhere
in Holtburg. Both clients read the same dat float, so reach was never inflated.

Decomp (read verbatim + corroborated by an independent adversarial workflow):
retail's per-object torch binder minimize_object_lighting (0x0054d480) is gated
in RenderDeviceD3D::DrawMeshInternal (0x0059f398) by `if (Render::useSunlight == 0)`.
The outdoor landscape stage runs useSunlightSet(1) (PView::DrawCells 0x005a485a,
before LScape::draw), so the building EXTERIOR shell — drawn via
DrawBlock→DrawSortCell→DrawBuilding→CPhysicsPart::Draw→DrawMeshInternal — is lit
by SUN + ambient ONLY; torches are SKIPPED. The static bake
(SetStaticLightingVertexColors 0x0059cfe0) is EnvCell-only. So retail NEVER
torch-lights outdoor objects. This exactly explains the isolation test (object
point lights OFF → building matches retail).

Fix: WbDrawDispatcher.ComputeEntityLightSet gates per-object torch selection on
the object being INDOOR (ParentCellId is an EnvCell, (id&0xFFFF)>=0x0100) via the
pure predicate IndoorObjectReceivesTorches. Outdoor objects (building shells with
null ParentCellId, outdoor scenery, outdoor creatures) keep the all-(-1) light
set ⇒ sun + ambient only = retail. The indoor "no sun" half is already handled by
the global sun-kill when the player is inside a cell (UpdateSunFromSky). No
dungeon regression: EnvCell statics get ParentCellId set (keep torches).

Divergence register: AP-37 (residual: acdream keys sun/torch on the object's own
cell + a per-frame player-inside sun-kill, vs retail's per-draw-stage useSunlight;
only matters for through-doorway look-ins). The round-1 CHECKPOINT got a RESOLVED
banner correcting the reach theory.

Tests: WbDrawDispatcherTorchGateTests (7), HoltburgTorchFalloffProbeTests (dat
dump). App 280/1skip, Core 1486/2skip green. Held at the visual gate — not merged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 23:56:49 +02:00
Erik
1e6fbff9bc docs(lighting): A7 Fix D round-2 CHECKPOINT — real cause is object torch REACH (#140)
Same-instant cdb proved acdream ambient (0.447) == retail (0.4465) and time/sun match,
so the building/character over-brightness is NOT the bake/wrap/EnvCell/clamp (D-1..D-4,
all correct but off-target) — those light the wrong surfaces. The Holtburg building
exterior is a mode-0 OBJECT (IsBuildingShell, not an EnvCell). Isolation (object point
lights gated OFF) made it match retail => cause is the torch REACH being too long
(acdream range 7.8 = Falloff 6x1.3 vs retail 5.2 = Falloff 4x1.3), flooding the small
facade. OPEN: confirm same-torch Falloff acdream-vs-retail before tightening the reach.
Diagnostic shader hack reverted (tree clean); D-1..D-4 kept. Branch not merged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 23:22:50 +02:00
Erik
d400bc6105 docs: handoff — finish the action bar (selected-object meter + shortcut drag) + start the inventory/paperdoll window
Next D.2b-UI work after D.5.4. 3 streams (spell bar deferred): selected-object
meter, shortcut drag/add/reorder/remove, inventory+paperdoll window. Current-code
anchors + dependency graph + build order + brainstorm questions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 21:29:04 +02:00
Erik
c407104ab9 docs(lighting): A7 Fix D investigation RESOLVED + implementation spec (#140)
Resolve the Fix D contradiction with decomp (workflow wf_f660eb88 + adversarial
verify) + 4 live cdb captures. The D3D-FF model was the WRONG oracle: retail has
TWO light systems — STATIC torches BAKE into wall vertices (calc_point_light,
triple-clamped: range gate + per-channel min(scale*color,color) + per-vertex
[0,1] from black), DYNAMIC lights go D3D hardware. The captured intensity=100 is
the purple PORTAL (magenta, dynamic), not a wall torch. Ground truth: 38 static
warm torches (orange (1,0.588,0.314)/cream, intensity=100, falloff 3-5) + 2 dynamic.

acdream over-brightness = two confirmed bugs: D-1 mesh_modern.vert folds
ambient+sun+torches into one UNCLAMPED accumulator (single frag clamp) -> warm
blowout; D-2 EnvCellRenderer never binds SSBO 4/5 so the cell shell reads a leaked
light set. Spec: D-1 in-shader clamp-split (clamp the torch sum on its own before
ambient/sun); D-2 bind the shell's own per-cell light set (mirror WbDrawDispatcher);
LightBake.cs is the C# conformance oracle. Adds the 4 reusable cdb capture scripts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 17:08:27 +02:00
Erik
4795a6c7f3 merge: A7 lighting Fix C (sun-vector brightness) + handoff into main
Brings Fix C (57c1135, sun-vector magnitude / ~32% over-bright) + the A7 lighting
handoff doc onto main. Auto-merged clean against the D.2b line. Merged tree builds
green; 18/18 sky tests pass. Fix A/B already on main (37911ed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 15:35:25 +02:00
Erik
f384d036a3 docs: A7 lighting handoff — Fix A/B/C shipped, Fix D (object torch over-bright) open
Session handoff: live-cdb grounding shipped Fix A (point-light shape), Fix B
(per-object selection), Fix C (sun-vector magnitude / ~32% over-bright). Fix D
(outdoor objects too bright near torches) is fully grounded but BLOCKED on one
capture (the building's render path) — the D3D-FF math says it'd make objects
brighter, so not ported. Full cdb cheat-sheet + the contradiction + the next
capture in the doc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 15:35:00 +02:00
Erik
5b568d000a docs(D.5): sub-phase ledger + item-model cold-start prompt
Roadmap: refresh the D.5.2 entry to its final shipped state (per-pixel gradient
surface overload 0x004415b0; AP-43/AP-44 retired by visual verification; range
419c3ac..fb288ad). Add an explicit D.5 sub-phase ledger: D.5.4 client object/item
data model (foundation, NEXT) -> D.5.3 selected-object + spell shortcuts -> window
manager -> D.5.5+ core panels. Handoff doc gains a paste-ready new-session prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 10:41:08 +02:00
Erik
9e0d2568cc docs: handoff for the client object/item data model (next phase after D.5.2)
Frames the root cause of the live-Coldeve 4/6-missing-hotbar-icons (acdream's
enrich-existing-only item model drops CreateObjects without a pre-seeded stub) and
the retail ClientObjMaintSystem model to port. CRUX to settle first: unify the
WorldEntity + ItemRepository tracks, or keep separate with shared ingestion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 10:33:31 +02:00
Erik
419c3ac40c docs(D.5.2): stateful item-icon spec + RESOLVED research
Research basis (clean Ghidra decompile via MCP + live-dat probe + ACE oracle)
overturns two handoff hypotheses:
  - Appraise carries NO icon/UiEffects data (Icon/IconOverlay/IconUnderlay +
    PropertyInt.UiEffects all lack [AssessmentProperty]); every icon input is
    CreateObject-only. The "wire appraise -> enrichment" item is a no-op.
  - The effect overlay (enum 0x10000005) is a ReplaceColor tint SOURCE, not a
    blit layer (RenderIcons 0x0058d180 + ReplaceColor 0x00441530); effect tiles
    are 32x32 fully-opaque colored squares.

Design (user-approved): capture UiEffects (weenieFlags 0x80, currently discarded)
-> ItemInstance.Effects; faithful 2-stage IconComposer recolor (white pixels ->
effect hue); live PublicUpdatePropertyInt(0x02CE) wire-up so the icon updates as
state changes ("item with mana vs out of mana"). Drops the appraise no-op.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 18:12:45 +02:00
Erik
6770381fc3 docs(D.5.2): stateful icon-system handoff + roadmap (D.5.1 shipped, D.5.2/D.5.3 next)
Extensive handoff for the next session to build the full stateful item-icon system (the 5-layer IconData::RenderIcons composite + effect layer 0x10000005 + overlay ReplaceColor tint + appraise-driven enrichment/re-composition). D.5.1 toolbar flipped to SHIPPED; D.5.2 (icon system) + D.5.3 (toolbar interactivity / selected-object display) registered as next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:24:03 +02:00
Erik
a5c5126e8d docs(D.5): action bar / inventory / paperdoll research drop
Five report-only deep-dives + synthesis for the next D.2b UI panels, built on the shipped widget toolkit. Confirms LayoutDesc ids (toolbar 0x21000016, inventory 0x21000023, backpack 0x21000022, paperdoll 0x21000024, 3ditems 0x21000021), the shared item-slot/item-list spine (UIElement_UIItem 0x10000032 / UIElement_ItemList 0x10000031), the 5-layer icon composite (IconData::RenderIcons @407524), the cross-panel wire catalog with acdream parse-status, and the dependency-ordered build plan.

Produced via a multi-agent research workflow; the spine agent died on a transient API error and was re-run as a focused follow-up with its decomp anchors verified against source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 21:04:57 +02:00
Erik
50758d4795 docs(D.2b): chat-window re-drive session handoff (Plan 2 chat piece)
Captures: current hand-authored chat window (UiNineSlicePanel + UiChatView,
read-only, debug font), the importer toolkit to reuse, the retail gmMainChatUI
oracles, the open design questions (scope / behavioral widgets / dat font), and
the first research step (find the chat LayoutDesc id). Resume via brainstorming.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:07:05 +02:00
Erik
8aa643f3e0 fix(D.2b): correct edge-anchor mapping (RightEdge==1=stretch) + enable vitals horizontal resize
ToAnchors was inverted vs retail UIElement::UpdateForParentSizeChange @0x00462640:
stretch is RightEdge==1 (not ==2/==4), LeftEdge==2 = track-right. Verified against
all 19 vitals fixture pieces. Enables Resizable/ResizeX on the importer vitals root
(the prior 'dat is fixed-size' conclusion was wrong). At-rest render unchanged
(anchors only fire on resize). Added a 160->200 resize conformance test.
Also fixed DatWidgetFactoryTests.RectAndAnchors_SetFromElementInfo which encoded
the old inverted model (Right=2 expecting Right anchor; corrected to Right=1).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 17:05:04 +02:00