Prove idle play remains passive and live until cancellation, then converges through one truthful status teardown. Keep probe mode string-only so numeric enum aliases cannot expand the pinned v1 contract, and record the Windows/WSL gates.
Both remaining agents were stopped for token budget and their partial work
committed as WIP (75a6724d LA1 fix round, c6019424 LA2). The handoff now
carries: full worktree paths with branches and HEADs, exactly what each
stopped agent had finished versus what it still owes, and a paste-ready
kickoff prompt naming all three resumable items plus the two owed merge
items.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Agent was stopped for token budget. Landed here: probe flag through
LiveSessionConnectOptions + the StartCore short-circuit, the mode field
with JsonRequired-to-semantic-validation move, host exit-code mapping,
and 34 passing tests including 3 new probe tests (agent last reported
green before the stop). NOT DONE: the idle-policy unit tests (next
step), full-suite verification, and the WSL run.
Build/test state UNVERIFIED at this commit. Next session: finish idle
policy tests, run Runtime+Headless Release suites Windows and WSL, then
dispatch the Opus dual-lens review.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Agent was stopped for token budget partway through the LA1 review fix
round. Landed here: F1 best-effort SessionStatusWriter, F2 App reader
tolerance (paths/mode), F5 argument-parsing hardening, plus new tests.
NOT DONE: F4 shared-fixture production shape (was the next step), F3
reconnect disconnected edge + recorded limitation, F6 exited
idempotency/reasons, F7 structural redaction test, F8 platform-guard
test + comment fix, optional RuntimeOptions PrintMembers redaction.
Build/test state UNVERIFIED at this commit. Next session: finish the
remaining findings, run the suites, then narrow re-review.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Self-contained handoff for a fresh session: what the campaign is and which
decisions are settled, the slice ledger with commits, the in-flight slices
and how to recover them from git, the two owed merge items (cross-assembly
contract test, Launcher.Core CI lane), the session landmines (index-sweep,
stale agent worktrees, contract-in-prompts), the binding process, and the
goal text to set.
Committed via pathspec so a live implementer agent index in this worktree
is untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Opus review of LA3 returned FIX FIRST; this addresses every finding in
scope (F1-F5, F7-F12; F6 CI-lane addition excluded per instructions):
- F1 (CRITICAL): SessionProcessSettings.Paths is now nullable and left
null by SessionConfigComposer unless a caller supplies overrides, so
the JSON key is entirely absent instead of "paths":{} — the App-side
loader's strict UnmappedMemberHandling.Disallow would otherwise reject
every gui/guiSelect session-config document at load.
- F2: added SessionConfigComposer.ComposeProbe and a nullable
SessionDescriptor.Mode field ("probe", omitted for normal play) per
the pinned contract — no character/policy/plugins/loginCommands.
- F3: LauncherProcessSupervisor.Stop now tries
ILauncherChildProcess.TryRequestGracefulStop (Linux: libc SIGINT via
LibraryImport, K4-proven graceful headless logout) before
CloseMainWindow. Windows has no reliable no-window-console equivalent
today; filed docs/ISSUES.md #397 with the CREATE_NEW_PROCESS_GROUP +
CTRL_BREAK fix direction. Stop()'s blocking-timeout contract is now
documented for LA4.
- F4: LauncherProfileStore.Save chmods the Linux temp file to 0600
immediately after creation, before any credential is serialized;
failure paths and Load() clean up a stale .tmp.
- F5: added LauncherCoreDependencyBoundaryTests asserting Launcher.Core
references exactly AcDream.Platform and no packages.
- F7: StatusEventParser.Parse no longer throws on a whitespace/null
line; StatusFileTailer.ReadNewEvents swallows the File.Exists/open
TOCTOU window (FileNotFoundException/DirectoryNotFoundException/
IOException) instead of throwing.
- F8: Start() now kills (entire process tree) and disposes a child that
started successfully but failed while being fed its stdin password,
instead of orphaning it.
- F9: SetState is monotonic — once Exited, no later transition applies
or fires StateChanged, closing a Start()-path race where a
synchronously-exiting child could be "resurrected" to Running.
- F10: CharacterIdFormat.TryParse now requires the "0x" prefix (an
unprefixed hand-typed decimal id is also valid hex and was silently
misread); a parsed id of 0 is treated as unusable and falls back to
the name selector; LauncherProfileStore.MergeRoster normalizes both
sides through TryParse/ToHexString instead of raw string equality, so
a legacy unprefixed-hex row self-heals via name match instead of
duplicating.
- F11: StatusCharacterEntry.SecondsGreyedOut is now uint, matching
CharacterRosterEntry and the host writer.
- F12: added MalformedStatusEvent, returned for a recognized `e` whose
payload doesn't match its shape, distinguished from UnknownStatusEvent
(an unrecognized `e`).
AllowUnsafeBlocks was added to AcDream.Launcher.Core.csproj — required
by the LibraryImport source generator's function-pointer marshalling
stub for F3's Linux SIGINT P/Invoke.
Verification: dotnet build AcDream.slnx -c Release green (0 errors);
dotnet test tests/AcDream.Launcher.Core.Tests -c Release green at 94/94
on native Windows and under WSL (Ubuntu, verified across multiple runs
for the timing-sensitive SIGINT/sharing-violation tests, no flakes
observed).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The running LA1 implementer had pre-staged its work-in-progress; git
commit takes the whole index regardless of what git add named, so the
docs commit swept in 37 LA1 files mid-implementation. Content is intact;
LA1 completion commit(s) follow with the remainder, and the LA1 slice
review covers the combined range. Process rule adopted: no orchestrator
commits in a worktree while an implementer agent is live in it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The LA3 Opus review process note was right: the contract both sides
implement lived only in orchestrator prompts, which is exactly the drift
mode the pin exists to prevent (and it produced the paths-key CRITICAL).
The schema, field rules, probe-mode discriminator, and status vocabulary
are now a binding plan section; amendments change this text first,
implementations second. Ledger: LA3 fix round dispatched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The spec seeded the wrong claim (restore extra strings = decompiler
artifact, no register row needed); the LA7a Opus review decoded the
PDB-paired binary and showed the two constant-string arguments are real,
making our guid-only request an adaptation — AD-97 filed on the LA7a
branch. Plan LA7 now carries the review-surfaced LA7b hazards (ACE
silent no-reply restore path, SendToLogon/SendToControl routing,
NumErrors sentinel).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Opus retail-lens review decoded the PDB-paired binary at
CPlayerSystem::RestoreCharacter@0x0055d760 and refuted the
uninitialized-edx justification: the two extra arguments are real
push imm32 of a constant PStringBase (BN mis-renders them, but they
pack to >=4 bytes each), so retail 0xF7D9 is >=16 bytes where ours
is 8. The guid-only CODE stands (ACE reads only the guid; holtburger
consensus) but it is an adaptation, not a corrected decompile — filed
as divergence register AD-97 and the doc comment now states the true
mechanism.
Also from the review: the 0xF643 conditional-parse doc now names BOTH
ACE flag-only failure branches (NameInUse + Corrupt); CharacterError
0x08 doc corrected (ACE misnames it ServerCrash2 — the port corrects
an ACE misnaming; ACE omits three values, not four); LA7b hazard notes
added (ACE silent no-reply on unknown restore guid; retail SendToLogon
vs SendToControl routing; NumErrors never rendered); two review-nit
tests (flag=0 Undef flag-only, non-Ok body with trailing bytes
ignored).
Core.Net suite: 953 passed / 0 failed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New AcDream.Launcher.Core (BCL-only, ProjectReference: AcDream.Platform
ONLY) plus tests/AcDream.Launcher.Core.Tests, both registered in
AcDream.slnx. This is the file-contract orchestrator core the Avalonia
launcher (LA4) will bind to — the game solution (Core/Runtime/App/
Headless) stays entirely out of this dependency graph, so the launcher
can never accidentally grow a game-protocol coupling.
- Profiles/: LauncherProfileStore owns launcher-profiles.json (spec §5
schema: version 1, servers[]/accounts[]/characters[]), strict
camelCase System.Text.Json (UnmappedMemberHandling.Disallow), typed
CRUD (add/edit/remove server; add/edit/remove account; edit character
settings), and MergeRoster (fold a reported roster into an account's
characters[] while preserving user-owned launchMode/plugins/
loginCommands, adding new rows with default guiSelect, and retaining
rows absent from the roster — they may be pending-delete). 0600 on
Linux via File.SetUnixFileMode after save.
- Launching/: SessionConfigComposer builds the pinned session-config
contract (Headless K1 shape + plugins/loginCommands/
loginCommandDelayMs/statusFile) from a profile character + install
record — character selector omitted entirely for guiSelect, policy
{id:"idle"} only for headless, credential always standardInput/
session. Passwords never enter this document (proven by a dedicated
test). LauncherProcessSupervisor spawns a host, feeds the password to
stdin then closes it, and exposes Starting/Running/Exited lifecycle;
Stop calls CloseMainWindow falling back to Kill after a timeout, both
reachable through an injectable ILauncherChildProcess/factory seam so
the state machine is unit-testable without real OS process timing.
- Status/: StatusEventParser decodes the v1 status.jsonl vocabulary
(started/connected/characterList/enteredWorld/pluginLoaded/
pluginFailed/disconnected/exited); an unrecognized "e" or a malformed
line degrades to a typed Unknown event rather than throwing.
StatusFileTailer incrementally reads new lines, tolerating a
not-yet-existing file and a partial trailing line (only advances its
read position past confirmed '\n' boundaries; a truncated tail is
simply re-read next poll, never parsed early).
- Integrity/: streaming SHA-256 + hex verify for later pak/download
checks (LA9/LA10).
Tests: 71 passed (profile CRUD + roster-merge matrix + strict-schema
rejection; composer golden-shape tests for gui/guiSelect/headless +
password-absence; supervisor tests against both an injected fake child
(state-machine determinism) and a real spawned `dotnet --version`
child (genuine cross-platform stdin/exit-code proof); tailer tests
incl. partial-line and not-yet-existing-file; SHA-256 tests). Verified
green on Windows (Release) and native WSL/Linux (Release) — the Linux
0600 test executes its real assertion body under WSL rather than
early-returning.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the three character-management wire messages LA7 (design spec §7,
plan §11 item 4) identified as missing before the character-select
screen (LA8) can be built: delete, restore, and the server error channel.
Message types + tests only — no WorldSession/Runtime/UI wiring, that is
LA7b.
CharacterDelete (0xF655): outbound account+SLOT-INDEX request per
Proto_UI::SendDeleteCharacter@0x00546b30 (retail packs the account as
String16L then writes the trailing u32 directly after — NOT the character
guid; CPlayerSystem::DeleteCharacter@0x0055f830 resolves that slot via
CharacterSet::GetSlot before sending). The server's ack reuses the same
opcode with an empty body (ACE GameMessageCharacterDelete.cs); a fresh
CharacterList follows separately per CharacterHandler.cs:322 — that
refresh flow is explicitly out of scope here (LA7b).
CharacterRestore (0xF7D9 request / 0xF643 response): guid-only request,
per ACE (CharacterHandler.cs:331-385, ReadUInt32 only) and holtburger
(CharacterRestoreRequestData, guid-only) independent consensus. The
decompiled call site (Proto_UI::SendAdminRestoreCharacter@0x00546cf0)
appears to pack two extra strings, but its only caller
(CPlayerSystem::RestoreCharacter@0x0055d760) passes an uninitialized
local (`class PStringBase<char>* edx;`, never assigned) as the second
argument and `this` (a CPlayerSystem*, not a string) as the third —
textbook decompiler register-corruption, not real arguments. No
divergence-register row: this follows the correct reading of a corrupted
decompile, not a deviation from retail (spec §11 item 4). The response
reuses opcode 0xF643, a genuine retail collision with
CharacterCreateResponse (ACE's own comment: "This is a duplicate...",
GameMessageOpcode.cs:42); GameMessageCharacterRestore.cs always writes a
success shape (flag=1 + guid + name + secondsGreyedOut), but retail's
CharacterRestore handler can also reply via the CharacterCreateResponse
path on failure (e.g. NameInUse) with a flag-only body and no trailing
fields — the parser mirrors that conditionality instead of assuming the
four fields are always present.
CharacterError (0xF659): u32 error code, confirmed directly from retail's
inbound dispatcher UIQueueManager::ProcessNetBlobData@0x0055b000 ->
CPlayerSystem::Handle_CharacterError@0x0055d5d0, which reads
`enum charError` straight off the wire. The Code enum is a verbatim port
of retail's own enum charError (docs/research/named-retail/acclient.h:
4038-4067, 26 members incl. CHAR_ERROR_NUM_ERRORS) rather than a subset
filtered through ACE — retail's header names four members ACE's C#
CharacterError enum omits (LoggedOn, NoPremade, AccountInUse,
CharacterIsBooted) because ACE's server never sends them, though a
genuine retail server could. The 32-bit storage-width compiler sentinel
FORCE_charError_32_BIT is deliberately excluded (not a real value).
Unknown codes never throw — RawErrorCode always preserves the wire value.
Today acdream cannot surface any character-stage server error; this is
the first parser for the family.
46 new tests (byte-exact builder assertions, ACE-serializer-shaped
parser fixtures via the existing AceWireWriter test helper, all 26
retail error codes round-tripped, unknown/truncated/wrong-opcode
handling). Full Core.Net.Tests suite: 951 passed, 0 failed, 0 skipped.
Release build green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The re-review closed all six findings and flagged one docs-only nit: the
Platform layer block described App as reaching Platform transitively when
the same commit made the reference direct, and spoke of the launcher in
the present tense. Both corrected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Opus dual-lens review of cb6502c8 passed with six findings; this lands
the fix round:
1. headless-portability.yml: AcDream.Platform src/tests join both path
triggers and the presentation-free build/test arrays — the moved XDG
tests run on ubuntu-latest again (they had fallen out of every Linux
lane).
2. acdream-architecture.md: AcDream.Platform gets its own layer block;
Runtime may-reference clause updated (the guard changed in cb6502c8,
its human-readable twin had not).
3. PlatformDependencyBoundaryTests: the BCL-only contract (zero
project/package references) is now enforced, not just observed.
4. memory/project_linux_graphical.md canonical seam renamed.
5. Plan LA0 recon corrected: the K0 Headless guard was never the guard
needing amendment (it asserts Headless own refs); Runtime own-refs
guard was — the commit did the right thing, the plan text now says so.
6. App declares its AcDream.Platform reference explicitly per its own
convention instead of riding transitivity.
Platform.Tests: 4 passed (3 moved + the new guard).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The launcher (LA3/LA4) needs the XDG/Windows path contract
(ApplicationPathSet/IApplicationPathEnvironment) without pulling in any
gameplay assembly. Move it out of AcDream.Runtime into a new BCL-only
AcDream.Platform project so the launcher-side Launcher.Core project can
reference it directly per the campaign plan (docs/plans/2026-08-14-launcher-campaign.md,
LA0). Namespace renamed AcDream.Runtime.Platform -> AcDream.Platform;
code is otherwise byte-identical (no logic changes).
AcDream.Runtime now carries a ProjectReference to AcDream.Platform and
re-exports it transitively, so App and Headless keep resolving the type
without a direct reference and K0's Headless single-ProjectReference
guard (HeadlessAssemblyReferencesOnlyTheRuntimeProject) stands unchanged.
The sibling Runtime dependency-boundary guard
(RuntimeProjectDeclaresOnlyApprovedProjectDependencies) does assert
Runtime's own project-reference set, so it needed a deliberate,
documented addition of AcDream.Platform to its expected list.
Moved tests/AcDream.Runtime.Tests/Platform/ApplicationPathSetTests.cs to
a new tests/AcDream.Platform.Tests/ project (namespace
AcDream.Platform.Tests) referencing only AcDream.Platform. Registered
both new projects in AcDream.slnx.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
User decision 2026-08-14: everything the launcher does ships Linux-tested
in this campaign (launcher UI, install/update with manual DAT picker,
headless launches with plugins + login commands, probe, per-slice Linux
test runs, Linux connected-gate section at LA11). GUI client launches
stay Windows-only until Slice L resumes later; the launcher renders GUI
modes disabled on Linux with an explicit note, and the host-agnostic
session-config contract means Slice L lights them up with no launcher
changes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plan doc with twelve slices, dependencies, review protocol (Opus
dual-lens: architectural + retail-faithful), and ledger. Three parallel
recon reports grounded the slice bodies:
- Retail select screen is gmCharacterManagementUI: flat listbox +
Enter/Delete/Restore + dialogs. NO 3D preview (that machinery is
chargen-only gmCG3DView) — the spec 3D-preview slice is deleted, the
old retail-ui/05-panels.md pedestal claim is uncited and wrong.
Restore + CharacterError join scope; delete sends account+slot.
- Chat-command core (parser/router/catalog/ChatVM) is dependency-clean
BCL+Core; extraction to Runtime is a move, not a rewrite.
- Probe reuses the NoCharacters early-exit shape (graceful teardown at
the CharacterList stage exists today); roster plumbing is new.
- Bake tool needs --progress-json + explicit --out; no whole-file SHA
exists — launcher records/verifies its own.
- UI Studio is deleted (Campaign V) — stale references corrected.
Roadmap + CLAUDE.md Current state carry the campaign pointer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Approved brainstorm outcome for the alpha launcher campaign: Approach A
file-contract orchestrator (session config in, stdin credential, JSONL
status events out), full in-UI CRUD for servers/accounts/credentials,
headless character-list probe, retail character-select screen (no
Create), plugins + login commands on both hosts, first-run DAT
locate/bake install, GitHub Releases update feed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ISSUES: #372/#374/#375/#378-#382/#385 flipped DONE (re-gate USER-PASSED
2026-08-14); #396 records the live-verified crash fix. CLAUDE.md Current
state: Campaign OP paragraph now carries the 2026-08-14 round and the
still-owed full OP3-OP6 sections + OP8 visual re-check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The user's first click on a mapping button killed the client: the live
dialog catalog's wait root 0x31 carries retail class type 0x19 (WaitDialog),
which DatWidgetFactory left unmapped, so the root built as a plain
UiDatElement and RetailWaitDialogView's ctor threw out of UiButton.OnClick
into the render loop. The unit test missed it by standing the confirmation
fixture (type 0x13, mapped) in for the wait root — the structural-false-
negative class again. Pins: DatWidgetFactoryTests theory for both dialog
root types, plus an installed-DAT UiDialogRoot/0x3D/0x3E assertion in the
env-gated keyboard probe. OpenCaptureInstructions now converts a dialog
construction failure into its contracted 0-return (log + capture refused,
retail's own OpenMapWarnDialog failure shape) instead of crashing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three findings from the user's first Configure Keyboard look (OP8 gate,
2026-08-14), each root-caused against the named retail decomp:
- #394 row-caption font: the synthesized action-label UiText never set
DatFont and fell to the debug bitmap font. The authored row template
(0x21000009/0x1000002F, retail UIOption_ActionKeyMap) carries FontDid
0x4000000A (18px serif) — Bind now takes resolveTemplateFont and applies
the template's own authored font, resolved once per template pair.
- #395 key captions: raw enum spellings ("Shift+ShiftLeft") replaced by the
port of CInputManager_WIN32::GetNameFromKey @0x00687F40 /
GetNameFromKey_Internal @0x00687800 (RetailKeyNames): DAT string-table
override by DIK-name hash (key enum 4 -> 0x2300000A, meta enum 5 ->
0x2300000B, delimiter enum 3 -> 0x23000007 — GetDIDByEnum category 4,
live-probed), else the OS keyboard layout's own key name ("SKIFT") via
PlatformKeyNameProvider (Win32 GetKeyNameTextW — register row AD-96 for
the DirectInput-vs-GetKeyNameText adaptation), else the DIK-suffix
spelling. Bare modifier-key bindings show only the key name.
- #396 capture feedback: clicking a mapping button now opens retail's
instruction dialog (InitiateBinding @0x004899D0 -> OpenMapWarnDialog
@0x00488A00): a type-2 WAIT dialog on retail's MapWarn queue key
0x10000001 with ID_ActionKeyMap_MapInstructions (0x23000004, ACTION
variable interpolated), closed on key hit or ESC through the capture
callback; capture is not armed if the dialog cannot open, matching
retail. New RetailWaitDialogView (wait root 0x31 — same authored
popup/message pair 0x3D/0x3E as the confirmation root, live-DAT probed)
behind a shared IRetailDialogView presenter seam.
Probe evidence (env-gated, kept):
KeyboardConfigLiveMountProbeTests.ProbeKeyboardFontsAndKeyNameStrings.
Register: AD-96 filed. Gate script OP8 section updated (step 4 rewritten;
the "pressed/active state is enough" contract is retired).
Full Release solution suite green (13,424 passed / 4 skips).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The two-client user gate PASSED 2026-08-14 (open both ways, stage with
the retail trading marker, accept/decline, executed swap, Clear All,
cancel text). Every TEMPORARY [trade] probe line from gate rounds 1-2 is
stripped (ItemInteractionController, SelectionInteractionController,
SecureTradeUiController, LiveSessionCommandRouter, WorldSession,
RuntimeTradeState). Roadmap gains the shipped-trade ledger row.
Suites after strip: App 4,992/3, Runtime 1,626 - green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
staged-item trading marker
- Cancel text: ClientTradeSystem::Handle_Trade__Recv_CloseTrade
@0x0056DE30 shows "The trade has been cancelled." UNCONDITIONALLY
(every close reason) as 0x1A ClientLocal - the yellow top-center
SpewBox line. Wired at the router's onTradeClose beside ApplyClose;
the string lives in ClientTextRefusals with its citation.
- Staged-item marker: retail's mechanism decoded end-to-end - the
UIItem prototype (catalog 0x21000037) authors overlay child
0x10000438 (sprite 0x06001DAE, the green frame + corner trade icon),
bound @0x004E18FC and SetVisible(tradeState != 0) @0x004E2420;
gmSecureTradeUI::AddItem @0x004CA801 sets
ACCWeenieObject::SetTradeState(1) on YOUR staged items. Ported as:
UiItemSlot.ShowTradeOverlay + TradeOverlaySprite (drawn over the
icon), set on the trade window's self-grid cells; and
RuntimeTradeState now borrows the canonical object table and
maintains ClientObject.TradeState (1 at stage, 0 at remove/failure/
reset/close/clear) - which also brings the ALREADY-PORTED placement
policy's "You cannot move an item while it is being traded" refusal
to life (its input field previously had no live producer).
Runtime 1,626, App 4,992/3 - green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
root cause for every dead interaction) + retail's Total Items caption
The round-2 probes nailed it: the request seam fired for BOTH open
paths (use AND drag - "drag-release pick" -> "drag-on-player" ->
"request"), but no open-cmd, no wire-open, and no LiveCommandBus
drop-warning ever printed. MountSecureTrade captured
_bindings.Options.CommandBus() ONCE at mount time - the pre-session
surface whose Publish routes into a null route silently. CommandBus is
a Func for exactly this reason; the social mounts resolve it inside
each lambda. Every trade command - open (use + drag), accept (the
"unpressable" Trade button - the click FIRED, the publish died),
Clear All, close, and drop-on-grid staging - died on that one captured
bus. All six lambdas now resolve the Func per call.
Also: ID_SecureTrade_TotalItemsLabel probe-verified token-free
(fragments ["Total Items: ", ""], one ITEMS variable 0x004E8A23) and
composed via ResolveTemplate - the count texts read retail's exact
"Total Items: N". AD-95 RETIRED same-day.
The pre-feature stub-toast test row (drag-on-player option-on expecting
"Secure trade is not open.") now pins the SecureTradeRequested seam
instead. App suite 4,991/3 skips.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
layout-persisted; deeper [trade] wire probes
A session that ended mid-trade saved Visible=true for "secure-trade" in
the window-layout file, restoring an empty open trade window at every
launch. Added to the stateManagedVisibilityWindows set beside
Combat/JumpPowerbar/ExternalContainer/Vendor - visibility belongs to
RuntimeTradeState's open/closed lifecycle exclusively.
Also carries the round-2 [trade] probes for the still-open "open never
registers" diagnosis: the round-1 log proved the request seam fires
(4x "request partner=0x50000001 item=0 open=False") but no window ever
opened - the new probes bracket the command router (open-cmd sent
flag), the wire send (wire-open seq), and the inbound RegisterTrade
apply, so the next gate log pinpoints whether the send leaves the
client and whether ACE replies.
App suite 4,990/3 skips.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>