28 KiB
Campaign LA — launcher / installer / updater + retail character-select
Status: ACTIVE (started 2026-08-14)
Spec (approved): docs/superpowers/specs/2026-08-14-launcher-campaign-design.md
Memory crib: claude-memory/project_launcher_direction.md
Branch: claude/acdream-launcher-credentials-4d2f7c (merge to main at coherent checkpoints)
Campaign LA ships the alpha launcher (Avalonia, Windows + Linux): triple-duty
launcher + installer + updater, ThwargLauncher-model profiles with full in-UI
CRUD, plaintext credential file (user-decided), file-contract orchestration of
AcDream.App and AcDream.Headless, plugins + login commands on both hosts,
the headless character probe, and the retail character-select screen (no
Create). All architectural decisions live in the spec — this plan sequences
the work.
Process (binding)
- Fable plans/sequences/integrates. Sonnet implements bounded slices. Opus
reviews at every slice boundary, dual-lens: (a) architectural — ownership,
layering, dependency-guard integrity, seams; (b) retail fidelity vs
docs/research/named-retail/wherever the slice touches retail behavior. Findings → fixes → narrow re-review. - Max 3–4 agents in parallel including children; subagents never spawn subagents; implementer prompts carry spec+plan paths, files-to-read, acceptance criteria, commit style.
dotnet build+dotnet testgreen before a slice is DONE; ≥1 commit per slice taggedCampaign LA; retail deviations add theirdocs/architecture/retail-divergence-register.mdrow in the same commit; no workarounds without explicit user approval.- Connected/visual gates are the ONLY stop-and-wait points; each gets an
exact script under
docs/research/and non-blocked slices keep moving.
Slice map
| Slice | Deliverable | Depends on |
|---|---|---|
| LA0 | AcDream.Platform extraction (ApplicationPathSet) + guard amendments |
— |
| LA1 | Launch contract: App --session-config + stdin credential; status.jsonl writer both hosts; roster plumbing |
LA0 |
| LA2 | Headless probe mode + idle policy |
LA1 |
| LA3 | AcDream.Launcher.Core: profile store CRUD, config composition, spawn/supervise, status reader |
LA0 (LA1 contract shapes) |
| LA4 | AcDream.Launcher Avalonia UI: CRUD views, per-char settings, sessions, probe action |
LA3 |
| LA5 | Plugin hosting: headless IPluginHost + capability flag; session-driven plugin set both hosts |
LA1 |
| LA6 | Login commands: parser-core extraction + execution on both hosts | LA1, LA5 |
| LA7 | Character-select: Runtime selection state + wire (delete/restore/error) + no-selector flow | LA1 |
| LA8 | Character-select authored retail screen (flat listbox — NO 3D preview, recon-corrected) | LA7 |
| LA9 | Installer: first-run wizard (DAT locate/validate, bake w/ progress, SHA record) | LA3, LA4 |
| LA10 | Updater: GitHub Releases manifest, download/verify/install/swap, self-update | LA3, LA4 |
| LA11 | Closeout: connected-gate script, roadmap/CLAUDE.md/memory, program ledger | all |
Parallelism guide: LA3/LA4 (launcher side) proceed alongside LA5–LA8 (client side) — different assemblies, no shared files. LA9/LA10 close the launcher side; LA11 closes the campaign.
Linux posture (binding — user decision 2026-08-14)
Everything the launcher does must WORK ON LINUX in this campaign, except GUI client launches: the Linux graphical client is Slice L, parked at L1, resuming later ("ok we will do it later"). Concretely:
- Linux-shipping in LA: the Avalonia launcher UI, profile CRUD +
0600-permission file, installer (manual DAT picker — the auto-detect
paths are Windows-only;
acdream-bakeis GL-free and runs on Linux), updater (staged swap; Linux can replace a running binary but keep the same staged-atomic flow), headless launches with plugins + login commands, and the character probe. - Launcher UX on Linux: the
gui/guiSelectlaunch modes render disabled with an explicit "requires the Linux graphical client (Slice L)" note — never a silent failure. - Per-slice enforcement: every slice touching Launcher.Core, Headless,
Runtime, Bake, or Platform runs its test projects on Linux (native
Ubuntu or WSL, matching the K-slice practice) before the slice is DONE;
LA4/LA9/LA10 additionally prove a real
linux-x64self-contained publish. LA11's connected-gate script gets a Linux section: launcher on Ubuntu doing CRUD, probe, headless launch with plugin + login commands, first-run install with a manual DAT path, and an update swap. - When Slice L later ships, the launcher's Linux GUI modes light up with NO launcher changes (the session-config contract is host-agnostic) — that expectation is part of LA's design acceptance.
LA0 — AcDream.Platform extraction
New BCL-only project src/AcDream.Platform/ holding ApplicationPathSet +
IApplicationPathEnvironment (today
src/AcDream.Runtime/Platform/ApplicationPathSet.cs — self-contained, no
intra-Runtime dependencies; clean cut). Runtime/App/Headless reference it.
Recon facts (2026-08-14): blast radius is the definition, six source files
(GraphicalHostPlatformServices.cs, GraphicalLegacyConfigurationMigrator.cs,
App/Program.cs, GameWindow.cs:533, HeadlessPathSet.cs,
HeadlessPlatformEnvironment.cs; two more files are doc-comment-only), two
test files (ApplicationPathSetTests.cs moves to a new
tests/AcDream.Platform.Tests/;
GraphicalLegacyConfigurationMigratorTests.cs fixtures), and the dependency
guards — CORRECTED post-review (the original recon here asserted the wrong
guard, the C4-closeout failure mode): the K0 Headless guard
(HeadlessAssemblyReferencesOnlyTheRuntimeProject) asserts HEADLESS's own
csproj reference list, which this move does not touch — it stays UNCHANGED;
the guard that actually needs amending is Runtime's own
RuntimeDependencyBoundaryTests.RuntimeProjectDeclaresOnlyApprovedProjectDependencies
(Runtime gains the AcDream.Platform reference), amended with a cited
comment in the same commit. Namespace stays AcDream.Runtime.Platform?
NO — rename to AcDream.Platform and fix the eight usings (clean naming beats
avoiding a mechanical edit). Register new projects in AcDream.slnx.
Acceptance: build + full test suite green; guard test asserts the new
exact reference set; launcher-side consumability proven by the LA3 project
referencing only AcDream.Platform.
LA1 — launch contract (client side)
Pinned launch-contract schema (v1, BINDING — committed per LA3 review)
This text is the single source of truth for the launcher↔host file contract. Both host readers (LA1), the composer (LA3), and the probe loader (LA2) implement EXACTLY this; any change is an amendment to THIS section first, implementations second. The LA1+LA3 merge adds a cross-assembly test feeding a composer-produced document to both host loaders — that test is the seam's permanent enforcement.
Session-config document (System.Text.Json, camelCase,
UnmappedMemberHandling.Disallow, camelCase string enums):
{
"version": 1,
"process": {
"content": { "datDirectory": "...", "preparedAssetPath": "..." }
},
"sessions": [{
"id": "sess-1",
"endpoint": { "host": "127.0.0.1", "port": 9000 },
"account": "testaccount",
"mode": "probe",
"character": { "id": 1342177290 },
"policy": { "id": "idle" },
"credential": { "provider": "standardInput", "reference": "session" },
"plugins": ["ExamplePlugin"],
"loginCommands": ["/vt start"],
"loginCommandDelayMs": 500,
"statusFile": ".../launcher/sessions/sess-1/status.jsonl"
}]
}
Field rules:
process.pathsis OMITTED unless a caller genuinely supplies overrides (never an empty object — the App reader has nopathsmember and strict parsing rejects unknown keys; LA3 review finding 1).mode: ABSENT for normal play sessions;"probe"for the LA2 probe (connect → characterList → graceful disconnect, no EnterWorld). The headless loader accepts the field starting at LA2.character: exactly ONE of index|id|name; OMITTED entirely (not null) for guiSelect and for probe sessions.policy:{ "id": "idle" }for headless play sessions ONLY; omitted for gui/guiSelect/probe.credential: always{ "provider": "standardInput", "reference": "session" }for launcher-composed configs.plugins/loginCommands/loginCommandDelayMs/statusFile: optional, omitted-when-unset (never null, never[]for empty). AbsentloginCommandDelayMsmeans 500.
Status stream (statusFile, one JSON object per line, writer flushes per
line, writer opens FileShare.Read, tailer opens
Read/FileShare.ReadWrite|Delete): events started, connected,
characterList{accountName,slotCount,characters[{id,name,secondsGreyedOut}]},
enteredWorld{characterId,characterName}, pluginLoaded{plugin},
pluginFailed{plugin,error}, disconnected{reason},
exited{code,reason} — every line carries "v":1, "e", "t"
(ISO-8601 UTC), "sessionId". secondsGreyedOut is a uint on BOTH
sides. Unknown e values must parse to a typed Unknown event, never
throw; a known e with a wrong payload shape should be distinguishable
from an unknown e (LA3 review finding 12).
Three pieces, one slice, because they share the session-config/status seam:
- App
--session-config <path>: parsed once inProgram.csintoRuntimeOptions(code-structure rule 4); carries endpoint, account, optional character selector,Plugins,LoginCommands,Content(DatDirectory/PreparedAssetPath), status-file path, credential reference. Recon:Program.cshas NO subcommand dispatch today — args handling is one positional DAT-dir (Program.cs:35), so the flag is purely additive (preserve the positional arg). The live-credential seam is a single call site (SessionPlayerComposition.cs:1128-1135→LiveSessionConnectOptions); the config path populates the sameRuntimeOptionsfields from a different source. Env-var dev flow untouched. App gains theStandardInputcredential read (mirroringHeadlessCredentialResolver.ResolveStandardInput— one line, immediately wrapped in an erasable secret, redactedToString; todayRuntimeOptions.LivePassis a bare string — the config path must not widen that exposure). - Status stream both hosts: per-session
status.jsonl(path given in config; absent → no writer constructed, zero cost). Versioned event vocabulary ("v":1):started,connected,characterList,enteredWorld,pluginLoaded/pluginFailed,disconnected,exited. Recon: today'sHeadlessDiagnosticWriteris a single shared-stdout JSONL sink with four kinds (lifecycle/failure/event/resources) and NO per-session file — the status writer is a second, separate sink, not a rework of the diagnostics writer. App has no structured writer today; it gets the same shared implementation (lands in Runtime so both hosts borrow it). - Roster plumbing:
CharacterList.Parsedis consumed insideLiveSessionController.StartCore(LiveSessionController.cs:612) and never escapes — add a typed roster report on the lifecycle-host seam (ILiveSessionLifecycleHost) so hosts can emit thecharacterListstatus event and (later) the char-select screen can populate. No behavior change to selection itself in this slice.
Acceptance: round-trip tests (config → RuntimeOptions; stdin credential;
status events in order with exact shapes; roster surfaced); App/Headless/
Runtime suites green; redaction test proves the password never appears in
status/diagnostics output.
LA2 — headless probe mode + idle policy
Recon facts: the probe's shape already exists as the NoCharacters early-exit
(LiveSessionController.cs:613-622 → StopCore() → 4-stage
SessionScope.DrainTeardown, graceful, _inWorld == false so no pre-logoff
flush) — but it fires only on selection FAILURE and maps to exit code 5
(HeadlessProcessHost.RunOnUpdateThread:203-212 treats any non-Connected
start as ConnectionError).
- Probe: a
Probeflag on the connect options short-circuitsStartCoreright afterGetCharacters(beforeTrySelectCharacter): report roster,StopCore(), return a NEWLiveSessionStartStatus.ProbeComplete.HeadlessProcessHostmaps it to exit code 0 with a finalcharacterList+exited(reason: "probe")status pair. Config:mode: "probe"on the session descriptor relaxes theJsonRequiredcharacter selector + policy for probe sessions ONLY (loader keeps strict validation otherwise — recon: violations currently surface as rawJsonException→ exit 3; probe relaxation must be shape-level in the loader, not attribute removal). idlepolicy: new consumerHeadlessBotPolicyid — enter world, run plugins/login-commands (arrive in LA5/LA6), stay until stopped, clean SIGINT teardown (K4's graceful-logout path already proves the mechanism).
Acceptance: probe test (fixture session → roster event → graceful teardown
receipt → exit 0, no EnterWorld on the wire); loader tests for probe-shape
relaxation + strict normal validation; idle-policy lifecycle test; suites
green. Connected verification (user gate, LA11 batch): live probe against ACE
twice in a row with no lingering session (spec §11.9).
LA3 — AcDream.Launcher.Core
New BCL-only project + tests/AcDream.Launcher.Core.Tests/. References
AcDream.Platform ONLY.
- Profile store:
launcher-profiles.json(spec §5 schema) — load/save/ validate, full CRUD operations, roster merge (foldcharacterListevents in, preserving per-character user settings), 0600 on Linux. - Session-config composition: profile + install records → the LA1 config shape (typed writer; probe shape included). Passwords excluded — stdin only.
- Process orchestration: spawn App/Headless per launch mode, feed password to
child stdin then close, supervise lifetime, tail
status.jsonl(share-tolerant reads), surface typed session state. - SHA-256 utility (pak record + download verify — consumed by LA9/LA10).
Acceptance: CRUD/round-trip/merge tests; composition tests (all three modes + probe); supervision tests against a fake child process (echo script); status-tail tests including partial-line handling; suites green.
LA4 — AcDream.Launcher (Avalonia)
New Avalonia project (Windows + Linux). MVVM over Launcher.Core; no game
solution references beyond AcDream.Platform transitively.
- Views: server list → accounts → characters tree; add/edit/remove dialogs for servers (name/host/port) and accounts (account + password entry); per-character settings editor (launch mode, plugin set, login commands); per-account "refresh characters" (probe); running-sessions status column.
- Launch actions per mode (
gui/guiSelect/headless); probe disabled while the launcher runs a session for that account. - First-run wizard shell + update prompt shell (bodies land in LA9/LA10).
Acceptance: ViewModel tests in Launcher.Core.Tests patterns (VMs live in
the Avalonia project but stay logic-thin; anything testable pushes down);
build green on Windows; linux-x64 publish compiles. Visual polish is gated
at LA11 (user).
LA5 — plugin hosting on both hosts
Recon facts (2026-08-14): PluginLoader/PluginDiscovery/PluginManifest
already live in AcDream.Core (Headless-reachable). App's single load loop
(App/Program.cs:110-121) loads ALL discovered plugins from two roots
(AppContext.BaseDirectory/plugins + ApplicationPathSet.PluginsDirectory,
dup-id skip) — no allow-list exists on either host. AppPluginHost is a
26-line pass-through; three of four IPluginHost surfaces (State →
WorldGameState, Events → WorldEvents, Selection → SelectionState)
are backed by Core-owned types already; only Ui (BufferedUiRegistry) is
genuinely App-only. Headless has zero plugin hosting today (confirmed).
- Session-config
Pluginsallow-list filters the discovery result on BOTH hosts (absent list = load all, preserving today's dev behavior). HeadlessPluginHost : IPluginHostin Headless over the same Core-ownedState/Events/Selection;Uiis an explicit no-op behind a new capability flag onIPluginHost(e.g.HasUi) so plugins can detect headless. Contract documented inPlugin.Abstractions.pluginLoaded/pluginFailedstatus events from both hosts' load loops.
Acceptance: fixture plugin in Headless suite (load, capability flag, markup no-op, teardown via collectible ALC); allow-list filter tests both hosts; status events asserted; suites green.
LA6 — login commands on both hosts
Recon facts (2026-08-14): the command core is dependency-CLEAN —
ChatInputParser (zero usings), ChatCommandRouter (BCL +
AcDream.Core.Chat), RetailClientCommandCatalog (FrozenDictionary),
ChatVM (Core.Chat/Combat + System.Numerics only), ICommandBus + the
four command records (BCL-only). The block is assembly identity, not
coupling. ChatCommandRouter.Submit's two entanglements: a hard ChatVM
parameter (uses only ShowInterfaceText/ShowSystemMessage/
LastIncomingTellSender/LastOutgoingTellTarget) and the ICommandBus,
whose production implementation (LiveSessionCommandRouter,
App/Net/LiveSessionCommandRouter.cs) is App-only and wraps wire-send
delegates from the live session. GUI already has a login-command analog:
RetailUiAutomationScriptRunner feeds ChatCommandRouter.Submit at
RetailUiRuntime.cs:523-527.
- Extraction: move parser/router/catalog +
ICommandBus+ the four command records (+ sibling tables they require) into Runtime (AcDream.Runtime/Chat/...); the router'sChatVMparameter becomes a narrow feedback interface defined beside it (exactly the four members used);ChatVM(stays in UI.Abstractions) implements it. GUI path stays bit-identical — same call sites (ChatWindowController.cs:326,FloatingChatWindowController.cs:157), same routing, CH-accepted behavior regression-checked by the existing chat suites. - Headless dispatch: a Runtime/Headless
ICommandBusbinding the same session send delegates (SendTalk/SendTell/SendChannel/SendTurbineChat) + Runtime state that App's router binds — parallelingLiveSessionCommandRouter's registrations, feedback lands inRuntimeCommunicationState.AddText. - Execution: both hosts run
LoginCommandssequentially as-if-typed (default 500 ms inter-command delay, config-overridable) once entered-world; per-command failures → status stream, never abort. - K0 guard: if the code folds into Runtime, the single-reference assertion stands untouched; the forbidden-prefix closure tests keep passing. Any guard text change is deliberate and documented.
Acceptance: extraction lands with zero GUI chat test regressions (UI.Abstractions + App chat suites bit-green); headless executes a login-command script against a fixture session with ordered wire sends; delay + failure-tolerance tests; suites green.
LA7 — character-select: state + wire
Recon facts (2026-08-14): retail's screen is gmCharacterManagementUI
(acclient.h:56545) — flat listbox + Create/Enter/Delete/Restore buttons +
dialog contexts. No 3D preview exists on retail's select screen (the
gmCG3DView/CreatureMode viewport is chargen-only; the old
"rotating pedestal" line in retail-ui/05-panels.md §13 is uncited and
wrong). Our CharacterList parse already matches ACE's serializer exactly
(two-array shape, status/deleted always zero from ACE) and the two-phase
enter-world (0xF7C8 → 0xF7DF → 0xF657) is implemented. Missing wire:
delete/restore/error.
- Wire messages (
AcDream.Core.Net/Messages/, retail citations in file docs per house style):CharacterDelete0xF655 — outbound account String16L + slot index (Proto_UI::SendDeleteCharacter @0x00546b30; NOT guid), inbound opcode-only ack followed by a fresh CharacterList;CharacterRestore0xF7D9 guid-only (ACE + holtburger consensus; the decomp's apparent extra strings are a decompiler artifact — spec §11.4), response 0xF643 (flag + guid + name + secondsDisabled);CharacterError0xF659 parser (new — today NO character-stage server error can be surfaced). - Runtime selection state (J-owner pattern): roster with per-entry
greyed/pending-delete state (
SecondsGreyedOut != 0⇒ pending; ACE sends a constant 1 during the grace window — treat as boolean, never a countdown), highlight, pending-delete dialog state, typed commands (highlight / enter / delete-request / delete-confirm / restore). Retail behavior oracles:RebuildCharacterList@0x004ec3a0,SelectCharacter@0x004ec160,UpdateButtons@0x004ec240(Delete↔Restore swap on greyed state),EnterGame@0x004ed440. - No-selector flow: a graphical session config without a character
selector stops at selection state instead of auto-enter; the
first-available fallback (
CharacterList.TrySelectFirstAvailable, used atLiveSessionController.cs:848-851) remains ONLY for selector-carrying/headless sessions. Selection feeds the existingEnterWorldpath unchanged.
LA7b hazards carried from the LA7a review (2026-08-14): ACE's restore
handler has a SILENT no-reply path (unknown guid → return;, no 0xF643,
no 0xF659) — selection state must never block awaiting a restore reply;
outbound routing is delete via retail's SendToLogon, restore via
SendToControl, ACE replies on UIQueue; charError.NumErrors (0x19) is an
enum-range sentinel and must never render as a user-facing message.
Register row AD-97 (guid-only restore request, an adaptation) rides the
LA7a branch.
Acceptance: message round-trip tests against ACE's serializer shapes; selection-state tests (greyed transitions, delete→list-refresh, restore, error surfacing); no-selector stop + enter flow tests; suites green.
LA8 — character-select: authored retail screen
Scope: project LA7's state through the REAL retail screen. No 3D preview (recon-corrected; a preview would be an unapproved divergence).
- Layout resolution: retail resolves the root via
UIMainFramework::CreateAndAddRootElement(0x10000005, 0x1000039a)+DBObj::GetDIDByEnum(..., 5)— reuse OP8's ported GetDIDByEnum machinery (category 4 precedent) for enum-table 5; slice starts by dumping that table from installed DATs to pin the concrete DataID. Child ids: listbox0x1000039d, create0x100003a0(present, disabled — Create is a future campaign), enter0x100003a2, delete0x1000039f, restore0x1000039e. - Dialogs: delete-confirm, please-wait, entering-world, error — the
retail dialog machinery from the OP8 WaitDialog work (
2a81e813mapped WaitDialog class type 0x19) is the base. - Open item resolved here: whether retail draws a render-loop background scene behind the UI (pseudo-C proves only that the UI class owns no viewport) — settle via user recollection + the visual gate before polishing.
Acceptance: authored screen builds from DAT assets; button-state
matrix matches UpdateButtons oracle (incl. Delete↔Restore swap);
enter/delete/restore/error flows drive LA7 state end-to-end; suites
green. User visual gate at LA11 (screen look, dialog flows, delete +
restore against local ACE).
LA9 — installer (first-run)
- DAT locate: auto-detect
%USERPROFILE%\Documents\Asheron's CallandC:\Turbine\Asheron's Call+ manual picker; validate the four DATs. - Bake: spawn
acdream-bake --dat-dir <dats> --out <DataDirectory>/pak/acdream.pak --threads N. Recon: default--outis INSIDE the DAT dir — the launcher always passes--outexplicitly. Progress: add--progress-jsontoAcDream.Bake(JSONL progress lines alongside the existing 5-second human text, which stays default) — scraping human text is fragile and we own the tool. Recon: the bake has NO whole-file SHA — after a successful bake the LAUNCHER computes and records SHA-256 + size +BakeToolVersionin its install record, and re-verifies on subsequent startups (fast corruption check trades a few seconds of hashing for never launching against a half-written pak). - Install record feeds LA3's session-config composition (DatDirectory/PreparedAssetPath).
Acceptance: wizard flow tests over Launcher.Core (fake bake child emitting
--progress-json lines); bake-tool progress flag tests in
tests/AcDream.Bake.Tests; SHA record/verify tests; suites green. Connected
gate (user): clean-profile first-run against real DATs.
LA10 — updater
- Manifest: GitHub Releases;
manifest.jsonrelease asset — version, per-RID client zip URL + SHA-256 + size, minimum-launcher version. Launcher pins owner/repo in its config. - Client update: poll on launch (+ manual check), download to staging, SHA
verify, unpack to
DataDirectory/app/<version>/, atomiccurrent.jsonpointer swap, refuse while any session runs, keep previous version for one-step rollback. - Launcher self-update: staged download + rename-dance on next start.
- Session-config composition targets
app/current's binaries.
Acceptance: manifest/download/verify/swap tests against a local HTTP fixture; rollback test; refusal-while-running test; self-update staging test; suites green. Connected gate (user): staged-manifest update swap end-to-end.
LA11 — closeout
- One connected-gate script
docs/research/2026-XX-XX-campaign-la-test-script.mdcovering: all three launch modes vs local ACE, probe round-trip ×2 (no lingering session), char-select visual matrix + delete flow, login-commands- plugin behavior on both hosts, add-server/add-account purely in UI, clean-profile first-run wizard, staged update swap.
- Roadmap shipped-table entry, CLAUDE.md Current-state flip, memory distill, ledger below completed, program closeout section.
Review protocol
Per slice: implementer commit(s) → Opus dual-lens review (architectural +
retail-where-applicable) → fix round → narrow re-review of fixes → slice DONE
in ledger. Reviews name blast radius explicitly
(claude-memory/feedback_blast_radius_single_lens.md). Slices LA7/LA8 add the
retail-fidelity lens against named-retail symbols cited in the slice body;
LA6 adds CH-regression scrutiny; LA0 adds guard-integrity scrutiny.
Ledger
| Slice | Status | Commits | Review | Notes |
|---|---|---|---|---|
| LA0 | DONE 2026-08-14 | cb6502c8, a49e92df |
Opus dual-lens PASS; all 6 findings CLOSED in narrow re-review | Byte-identity proven; Linux CI lanes restored; Platform BCL-only self-guard added; K0 guard untouched |
| LA1 | implemented; Opus review in flight | db9ad53c (mixed — see e1322a06) |
review in flight | Runtime 1630 / Headless 126 / App 5025+3skip / Core.Net 905 green; Runtime+Headless green on WSL; shared fixture parsed by BOTH host readers |
| LA2 | review fix round complete; automated gates GREEN; narrow re-review pending | c6019424, 000ea979 + review fixes (this commit) |
Opus dual-lens FIX FIRST; all 3 findings fixed, narrow re-review pending | ProbeComplete now requires a reported roster and is still before selection/EnterWorld; terminal status derives from the actual start outcome and matches failed-start process code 5; conditional config fields distinguish omission from explicit null without weakening strict JSON shape/type checks. Release build green on Windows and Ubuntu/WSL; Runtime 1,632/1,632 and Headless 149/149 on both |
| LA3 | review FIX FIRST; fix round in flight | 37d74e44 + fixes pending |
Opus 2026-08-14: 12 findings — 1 CRITICAL ("paths": {} breaks App loader), probe composition owed, Stop→SIGKILL hazard, 0600 temp window |
Contract text now COMMITTED into LA1 section (review process note); cross-assembly loader test owed at LA1+LA3 merge; CI lane addition at merge |
| LA4 | — | |||
| LA5 | — | |||
| LA6 | — | |||
| LA7 | LA7a DONE + MERGED (fa2de1c4); LA7b (state+flow) unblocked by LA1 |
6a32f375, 4338b1c1, 0c8643a7, merge fa2de1c4 |
Opus retail-lens PASS; narrow re-review MERGE; AD-97 filed | Review decoded the retail binary: restore is ≥16 bytes, guid-only is an ADAPTATION (AD-97); conditional 0xF643 parse CONFIRMED; enum corrects ACE's 0x08 misnaming. Core.Net 953/0/0 post-merge |
| LA8 | — | |||
| LA9 | — | |||
| LA10 | — | |||
| LA11 | — |