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>
Faithful port of retail UIElement_UIItem::UpdateCapacityDisplay (0x004e16e0):
each container cell (side bags + main pack) shows a vertical UIElement_Meter
(element 0x10000347, back 0x06004D22 / fill 0x06004D23) filled to
GetNumContainedItems / ItemsCapacity, clamped [0,1]; hidden for non-containers
(CapacityFill=-1). Drawn procedurally on UiItemSlot like the triangle/square
overlays (back full + front clipped bottom-up). Right-anchored flush to the cell
edge (visual gate: the dat X=26 sat ~5px off the right edge). Visually confirmed
2026-06-22. Divergence AP-59; polish deferred to ISSUES #146 (exact rect/anchor,
fill direction vs m_eDirection 0x6f, closed-bag lazy-load).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
acdream accumulated every CreateObject from every town visited and never pruned by
distance/time (only on server DeleteObject / respawn de-dup), so the entity tables +
the O(N^2) TickAnimations scan grew with each hop and sank FPS (confirmed in Release).
Faithful port of holtburger liveness.rs (ACE_DESTRUCTION_TIMEOUT_SECS=25,
CONSERVATIVE_VISIBILITY_DISTANCE_M=384): a world entity is evicted only after being
>384m AND outside the 3x3 landblock neighborhood for 25s continuous (arm-on-leave /
clear-on-return). Logic in a pure, unit-tested EntityVisibilityCuller; GameWindow
wires a 1Hz tick that snapshots the world entities + player and tears each evicted
guid down through the existing pruner. Player + held/equipped/contained items are
excluded (player by guid; inventory items never carry a world position so they never
enter the culled map). A re-created object starts fresh (deadline cleared on remove).
Skipped during a teleport hold (frozen player position). AD-32 registered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
After a teleport ACE floods a town's CreateObjects; WorldSession.Tick drained the
ENTIRE inbound queue every frame, each spawn hydrating mesh+textures under _datLock
on the render thread — monopolizing the update loop for ~a minute and starving the
streaming apply, so only the destination landblock loaded and neighbors trickled in
(visible at high render-FPS because update/render are separate Silk.NET callbacks).
Bound the per-frame drain to a ~4ms wall-clock budget once InWorld (handshake uses
the blocking PumpOnce path, never Tick). A time budget self-adapts to the highly
variable per-datagram cost; the tail stays queued (unbounded channel, FIFO) and
drains over the next few frames. Acks are queued per packet BEFORE the heavy handler
(WorldSession.cs:680), so deferring the tail only delays the tail's acks a few frames
— within ACE's tolerance (holtburger defers acks on a flush cadence; verified in
references/holtburger session/send.rs). Budget decision extracted as a pure testable
static (4 unit tests).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The prior commit pinned 0x060011F4 from a research dat-dump of
GetDIDByEnum(0x10000004,7) — it rendered as a GREEN TILE (green slot, no
pack) at the visual gate. Dat-exported the candidates (AcDream.Cli
dump-sprite-sheet / export-ui-sprite): 0x0600127E is the 32x32 brown
backpack (user-hinted, PNG-confirmed). Swap the pinned literal; the
test + AP-51 register row updated to the visually-verified id. Container
type-underlay (green) + backpack base still composited via _iconIds.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The m_topContainer cell (0x100001C9) rendered blank (tex=0). Retail's
IconData::RenderIcons (0x0058d1ee) has an IsThePlayer() branch that draws a
CONSTANT backpack — m_idIcon = GetDIDByEnum(0x10000004, 7) = 0x060011F4,
m_itemType = TYPE_CONTAINER — NOT the player's body icon (the original AP-51
"equipped-pack weenie icon" premise was wrong). Compose that base over the
Container type-underlay via the existing _iconIds delegate. Verified vs decomp
(407546-407549) + IconComposer.GetIcon (base=arg2, type drives underlay) + a
live dat dump (map 0x25000008 index 7 = 0x060011F4). Test locks type+literal.
AP-51 reworded to the residual hardcoded-vs-runtime-resolve nuance (cf. AP-55).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment-only. (1) the InventoryController summary said "Read-only: no
container switching" — now live. (2) note the _openContainer 0-vs-playerGuid
sentinel equivalence so the dual main-pack path isn't mistaken for dead code.
No behavior change. Code-quality review verdict: APPROVED (ship to gate).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
T6 visual gate found three issues; probe pinned the roots.
- StreamingController.DrainAndApply: the priority hunt + outbox drain were gated
behind 'if (budget <= 0) return;' AFTER the deferred-buffer drain. During a
dungeon-exit expand (~600 completions) the buffer is always >= budget, so the
hunt never ran and the destination never priority-applied (APPLY stalled ~5s;
dest built +265ms but applied +6s). Restructured: priority hunt runs FIRST and
unconditionally. Indoor (single-LB collapse) was unaffected, which is why only
outdoor exits broke.
- Teleport timeout was a 600-FRAME count; the empty-world exit hold renders at
~1000fps so it fired in ~0.6s and force-placed into the skybox before the
expand finished. Now wall-clock (10s) — worldReady wins the race.
- Arrival facing: synced _playerController.Yaw to the server orientation via the
exact inverse of YawToAcQuaternion (ExtractYawFromQuaternion has a 270deg offset
vs our yaw). Previously only the render mesh got the rotation → camera/movement
faced the stale pre-teleport direction.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Wires the dormant TeleportAnimSequencer as the transit driver: on PlayerTeleport
the player holds in PortalSpace behind a full-screen fade (FadeOverlay) until the
destination terrain is resident (TeleportWorldReady, gated on the priority-applied
landblock), then materializes (Place), and after the world fades back in regains
control + acks the server (FireLoginComplete). No movement resolves against the
empty world, so the outbound cell frame can't corrupt. Outdoor changes from
place-immediately back to hold-until-resident (now fast, not a band-aid).
- FadeOverlay: fullscreen NDC black quad, alpha = ShowTunnel ? 1 : FadeAlpha.
- Retires TeleportArrivalController + its 2 tests (TAS subsumes the driver role).
- Divergence register: AD-2 updated to the new mechanism; AD-31 (fade vs swirl).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Acceptance apparatus for the teleport-residency fix. ACDREAM_PROBE_TELEPORT=1
gates 5 log points with cross-thread TickCount64 timestamps + the _datLock
waited/held measurement. Stripped (or promoted) at verification (plan T6).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BuildCellSetAndPickContaining discarded the bool from TryGetTerrainOrigin — when
the current landblock's terrain hadn't been applied yet (priority-apply in flight
after a teleport or dungeon exit), blockOrigin was silently set to (0,0,0). The
AdjustToOutside/GetOutsideLcoord math treated world-frame sphere coordinates as
block-local and marched the cell one landblock per tick in the direction of movement
until lbX or lbY underflowed to 0x00. ACE rejected every subsequent move as a
failed transition.
Fix: honor the bool return. When terrain is unregistered for an OUTDOOR seed
(low < 0x0100), return currentCellId verbatim — "no block-local frame →
preserve". This mirrors the NO-LANDBLOCK verbatim contract in PhysicsEngine.Resolve
and is correct: the cell stays last-known-correct until terrain registers.
Indoor seeds are explicitly excluded (blockOrigin is never consumed by the indoor
pick path; outdoorPickAllowed=false for indoor seeds).
Reproduce + verify via CellMarchLandblockPreservationTests (two new FAILING-before
tests: WestEdge and SouthEdge with empty cache, no anchor → lbX/lbY preserved).
TeleportFarTownRunawayTests updated: no-anchor path now also preserves (pre-fix it
marched south to 0x59; post-fix returns currentCell unchanged).
CellTransitFindCellSetTests, Issue112MembershipTests, PhysicsEngineTests: added
RegisterTerrain for the streaming-center block (in production it is always resident
before outdoor resolves run; tests that used blockOrigin=(0,0,0) as an implicit
fallback now register the block explicitly). All 1567 tests pass.
Divergence AD-30 added to retail-divergence-register.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Adds a high-16-bit-prefix landblock residency check used as the teleport
worldReady gate — true once the destination landblock's terrain+cells
have been registered via AddLandblock, regardless of whether the caller
passes a canonical (0xFFFF), cell-resolved, or bare landblock id.
Two TDD tests confirm: false before registration, true after, and
that a cell-resolved id on the same landblock returns true.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace RemoveAt(0)-in-a-loop drain idiom with RemoveRange(0, i) in both
Step-1 deferred drain and the post-found deferred drain inside DrainAndApply,
making each an O(N) single shift instead of O(N²) on the render-thread hot path.
Add PriorityNeverArrives_noThrow_noLoss_noDoubleApply test: sets a priority id
that never appears in the outbox, ticks several times, asserts no throw, no
loss of the non-priority completions, and no double-apply (applied count ==
completions enqueued). Comment above the priority-hunt explains the failure
mode: completions relocate to _deferredApply while the hunt is active and drain
at per-frame budget until the caller clears PriorityLandblockId.
Restyle test 2 to use named constructor arguments and break the compressed
lambda onto readable lines (matches test 1 style).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds PriorityLandblockId (uint, default 0) + _deferredApply buffer.
DrainAndApply now: (1) applies up to budget from the deferred buffer,
(2) when PriorityLandblockId != 0, hunts the worker outbox in chunks
applying the priority LB immediately on match and buffering any
non-priority items drained past it for later frames,
(3) falls back to normal drain when no priority is set or not found.
Extracts ApplyResult(result) + ResultLandblockId(result) helpers so
both the priority and normal paths share identical side-effects.
No existing behaviour changes on the non-priority path.
21 streaming tests pass (19 existing + 2 new priority-apply tests).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
User confirmed the container column reads correct after the inheritance-resolved
fix (22d9231). Status: inventory empty-slot art FIXED + VISUALLY CONFIRMED.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Visual gate (2026-06-22) showed the side-bag/main-pack empty cells drawing yellow
triangles. Root cause: the frame-first heuristic grabbed the 36x36 container
prototype's DirectState child 0x06005D9C — which is the open/SELECTED-container
triangle indicator, NOT a background frame — and stamped it onto every empty cell.
Fix: drop frame-first; FindIconEmpty resolves the inner m_elem_Icon ItemSlot_Empty
THROUGH BaseElement inheritance (0x1000033F -> 0x10000340 -> base 0x1000033E ->
0x1000033B -> 0x06000F6E), so containers get the dark slot background matching the
inventory. Pin test now asserts exact ids (0x06004D20 contents / 0x06000F6E
containers) — the old structural-only assertion let the wrong-but-valid 0x06005D9C
pass (Opus review Minor 2, now vindicated). The triangle + green/yellow selection
square is deferred to container-switching (AP-56 reworded). Full suite green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Inventory contents grid / side-bag / main-pack empty cells now dat-resolve their
art via ItemListCellTemplate (0x1000000e -> 0x21000037). Paperdoll equip
silhouettes + main-pack icon remain open (Sub-phase C / AP-51).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Inventory contents grid / side-bag / main-pack cells now render the retail
pack-slot empty art (dat cell template via ItemListCellTemplate) instead of the
generic toolbar square. Divergence rows AP-55 (toolbar still hardcoded) + AP-56
(flat single-sprite cell vs retail''s layered prototype).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ports UIElement_ItemList::InternalCreateItem (0x004e3570) attribute lookup to a
testable static helper. The 0x1000000e property is an EnumBaseProperty (not
DataIdBaseProperty as the spec anticipated) — discovered by runtime reflection and
confirmed against the live dat. Pinned sprite ids from real-dat test:
contents (0x100001C6): 0x06004D20
side-bag (0x100001CA): 0x06005D9C
main-pack (0x100001C9): 0x06005D9C
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>
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>
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>
User-tested: the Slice 2 'hold outdoor until landblock loaded' gate made
EVERY outdoor teleport a ~10 s freeze, because the destination landblock
does NOT load fast during the hold (lbs=0 the whole time — the #138
streaming gap + _datLock starvation from the CreateObject flood). The hold
was band-aiding a broken/slow foundation rather than fixing it, and it never
actually prevented the #145 edge cascade anyway (it force-snapped onto
NO-LANDBLOCK after the timeout regardless).
Reverts ad8c24e..c880973 to the pre-Slice-2 state (00ef47e): outdoor places
immediately again (fast teleports). The genuine bug found along the way —
IsLandblockLoaded queried the wrong key form (& 0xFFFF0000 vs the stored
| 0xFFFF) — is preserved in the history (c880973) and will be re-applied when
we re-introduce a proper hold ON A FIXED FOUNDATION.
Decision (user, 2026-06-21): fix the foundation FIRST — fast/complete
streaming during teleport (#138), the post-teleport lost-collision bug, and
the FPS leak (Work item C) — then revisit the teleport-flow animation. Slice 1
(the pure TeleportAnimSequencer) stays in (dormant, unwired, harmless).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The contents grid now pads empty slot frames up to the main-pack capacity
(player ItemsCapacity, default 102 per retail "up to 102 items"), so the pack
reads like retail's fixed 102-slot grid you scroll through — not just the loose
items. Mirrors the side-bag column padding. Three existing grid tests updated
for the now-padded count; new test covers the 102-pad.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drag the bottom edge to expand the inventory and see more of the pack
(ResizeX=false + ResizableEdges=Bottom blocks horizontal resize). The contents
grid + its gm3DItemsUI sub-window + scrollbar + the backdrop stretch vertically
(Left|Top|Bottom); the paperdoll + side-bag column stay pinned at the top
(Left|Top). The grid's ViewHeight grows → more rows + the scrollbar thumb ratio
(view/content) reflects how much is left to scroll. Min = dat default (expand
only for now); max = 560. Visually confirmed: scroll + resize work.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wrap the gmInventoryUI content in the universal 8-piece beveled chrome (same
UiNineSlicePanel as vitals/chat/toolbar), so the inventory has a proper window
frame like every other window. The frame is the draggable window + the F12-
toggled registered window; the dat content sits inside it offset by the border.
Reused as the base for the vertical-resize step next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>