acdream/docs/plans/2026-08-10-options-panel-campaign.md
Erik 5242de9f15 fix(ui): OP2 re-review closure (coordinator pass) — AP-195, tooltip port, zero-children pin
Closes the mechanism lens's REOPEN (one MUST-FIX) and both lenses' small
residuals on the OP2 rework (b236a442); the blast lens re-review was
CLOSED outright. Fable-direct per the two-failure escalation rule.

- AP-195 filed: UIOption_CheckboxBitfield64 ports HALF of Refresh
  @0x004859C0 — the ANY-set checkbox predicate is exact, but the ALL-set
  LED media swap (P0x10000082=0x06004D17 / P0x10000083=0x06004D19) and
  the ListBox self-sizing tail (ResizeTo/CalculatePaperSize — the block
  IS a UIElement_ListBox in retail) are unported, and the block's row
  stacking is a second divergent implementation beside UiTemplateListBox.
  All due at OP5 before the Chat tab's connected gate; the IsSet doc
  comment now names both halves instead of quoting only the ported one.
- Row tooltips: UiButton gains settable TooltipText surfaced through the
  shared GetTooltipText hover pipeline (UiCatalogSlot's pattern);
  UiCheckboxBitfield64.AddChild applies the row tooltip retail stamps in
  CreateChildren @0x00485DF0, and documents that the 0x10000084 row-index
  attribute stamp is deliberately replaced by the typed mask closure.
- AD-73 addendum: UiTemplateListBox.ConsumesDatChildren=true is inert
  only while no authored Type-5 element carries children — that premise
  is now conformance-PINNED across all 32 fixtures (a future DAT
  regeneration surfacing an authored child fails the build instead of
  silently dropping it).
- Plan doc: OP2's contract names UiTabPanel.cs (retail UIElement_Panel),
  not the fictional-class-named UiTabControl.cs; ledger records OP1 and
  OP2 both CLOSED.

Full Release suite: 12,871 passed / 4 skipped / 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 01:26:29 +02:00

24 KiB
Raw Blame History

Campaign OP — retail four-tab Options panel

For agentic workers: slices are executed by ONE Sonnet implementer at a time against this contract, then dual-lens Opus-reviewed, per the binding process rules in §8. The four research docs in §1 are the spec's data appendix — implementers MUST read the cited sections before coding; every table this plan references by section number is committed there in full.

Status: ACTIVE — planned 2026-08-10. No slices landed yet.

Goal: retail's four-tab in-game Options panel (Gameplay Options / Character / Chat / Config), retail-faithful mechanics end-to-end: authored LayoutDescs, the real option storage/wire split (0x0005 single-option vs the batched 0x01A1 PlayerModule blob), retail Apply/Reset/Defaults semantics, live consumers where acdream has the subsystem and honest store-only + register rows where it doesn't, plus the headless-bot characterOptions seam. Campaign handoff: docs/research/2026-08-10-settings-track-handoff.md.

Architecture: the panel is retained retail UI exactly like Campaign CH's chat windows — LayoutDesc 0x2100002B imported through LayoutImporter, mounted in the gmFloatyPanelUI host 0x2100006E under RetailWindowManager, driven by a focused controller. All option storage/policy is Runtime-owned (RuntimeCharacterOptionsState widened to the full retail table); the graphical panel and the headless host are both consumers of the one generation-gated IRuntimeCharacterCommands seam (CH3's precedent). Wire builders live in AcDream.Core.Net beside the existing 0x0005 codec.

Tech stack: existing retained UI stack (DatWidgetFactory, LayoutImporter, UiRoot, RetailWindowManager), AcDream.Runtime gameplay owners, AcDream.Core.Net message builders, DatCollection for DAT reads. No new dependencies.


1. Research base (committed; the spec's data appendix)

Doc What it pins
docs/research/2026-08-10-options-panel-structure.md (lane A) Layout 0x2100002B structural inventory (§10.1), tab table property 0x2E, row-template mechanism (ListBox P0x64 + AddItemFromTemplateList), Apply/Reset/Defaults + visibility semantics (§6, §10.4), Chat tab's 13 checkbox masks + defaults (§8), Config tab's 27 rows + UserPreferences.ini keys (§9), open path (F11 action 0x1000001A, toolbar button 0x1000019B)
docs/research/2026-08-10-character-options-map.md (lane B) The 50-row / 6-group Character-tab inventory with per-row storage bit, wire route, ACE handling, acdream consumer state (§2§5); implement-vs-store split (§7.1); bot tiers (§5.2)
docs/research/2026-08-10-set-character-options-wire.md (lane C) The 0x01A1 body = PlayerModule::Pack field order (§2.3§2.7), header invariant 0x460, the 21-id auto-save table (§3.2), 480 s timer + logout + Apply flush triggers (§3.3§3.5), ACE acceptance/landmines (§5), CH3 builder post-mortem (§6)
docs/research/2026-08-10-keyboard-config-and-gameplay-tab.md (lane D) The seven Gameplay-tab button behaviours with byte-verified strings (§1§4), gmKeyboardUI structure + DAT ActionMap storage (§5§6), per-button implementability (§7.1), Config-tab ordered dump (§7.3)

Coordinator-verified during planning (this session): toolbar button 0x1000019B authors P0x12 = 0x1000001A (committed fixture); retail-default.keymap.txt:148 binds ToggleOptionsPanel to DIK_F11; PlayerModulePackHeader verbatim at acclient.h:7835; the headless local-write gap at src/AcDream.Runtime/Session/DirectGameRuntimeCommandAdapter.cs (SetSingleOption) vs src/AcDream.App/Net/LiveSessionRuntimeFactory.cs:347; U1 closed: UIOption::InqDefaultGameplayOptionProperty @0x004ef8d0 resolves per-option defaults from the DAT DBPropertyCollection at DBCache::GetDIDFromEnumStatic(0x16, 2) — the Defaults button restores DAT-authored values.

2. Design decisions (stated, per the campaign directive; reactable at gates)

  • D1 — the retail Options panel is acdream's one in-client settings surface. Lane D established the F11 SettingsPanel was never rendered post-V11 (ToggleSettingsPanel() no-op; only IPanelRenderer is a test fake). Retail's own F11 IS ToggleOptionsPanel. So: F11 + the toolbar button open THIS panel; acdream's client-only settings live on the Config tab (retail's own client-settings tab — its 27 rows are UserPreferences.ini preferences, nothing on the wire), backed by acdream's existing settings store. The old SettingsPanel/SettingsVM IPanel surface is retired in OP9; its tested keybind model feeds OP8.
  • D2 — retail's wire split ships exactly. The 21 auto-save ids send 0x0005 immediately; the rest dirty the module and ride the real 0x01A1 blob with retail's three flush triggers (Apply, logout, 480 s timer). No "send everything as 0x0005" shortcut (lane C: the split is load-bearing in both directions).
  • D3 — the 50th Character row ships. "Listen to PK death messages" is 2015-client; the DAT string exists (0x0D16E9A3), ACE maps id 0x34CharacterOptions2 0x02000000 but never reads it. Ship the row (user-gate axiom: the user's retail memory includes it), wire+store only, register row for the ACE-sourced 2013-unverifiable mapping.
  • D4 — Configure Keyboard is the campaign's rebind screen (it is the ONLY rebind screen — D1). Port gmKeyboardUI's shape and DAT ActionMap data (lane D Option C) but persist to keybinds.json; retail .keymap file interchange is a register-row deferral.
  • D5 — dead-endpoint buttons short-circuit to their own retail failure strings. Urgent Assistance / Report Abuse open a defunct support.turbine.com URL in retail; acdream skips the browser launch and emits the button's own byte-verified failure notice through the interface-text seam (retail text, not invented), register rows filed. In-Game Help mirrors retail-with-missing-ACHelpPlugin.dll behaviour.
  • D6 — Exit to Character Selection behaves as Exit Game (+ register row) until a pre-world character-select flow exists, but retail's confirmation dialog (ID_Client_EndCharacterSessionConfirm) and mid-air refusal ("Cannot log off while in mid-air.", byte-verified) ship now.
  • D7 — Group C re-points to server truth. The seven currently-live settings.json-backed options (lane B §7.1 group C) become server-bit-authoritative per CH3 precedent (reseed from PlayerDescription; local-write-then-send). GameplaySettings' 16 server-shadowing booleans die in OP9.
  • D8 — bots declare options by NAME. The K1 strict config gains an optional characterOptions block accepting exactly the lane-B tier-1+2 names; unknown names fail load (ACE throws on unknown ids — never let one reach the wire). Diff-then-send after PlayerDescription seeds state; idempotent on reconnect.

3. Slice map

Dependencies: OP1 → (OP4, OP7); OP2 → (OP3, OP4, OP5, OP6, OP8); OP3 → OP4/5/6 (the shell hosts the tabs). OP7 needs only OP1. Execution order below is the default; OP7 may run any time after OP1 when the tree is free.

Slice Contract (summary) Gate
OP1 Runtime option map + dirty model + the real 0x01A1 builder + headless local-write fix automated only
OP2 Type 8 tab control + Type 5 template-list ListBox + remaining UIOption_* widget mappings + fixtures automated only
OP3 Panel shell + open paths + Gameplay tab end-to-end user (connected)
OP4 Character tab: 50 rows, consumers, Apply/Reset/Defaults user (connected)
OP5 Chat tab: opacity sliders + 5 per-window filter blocks user (connected)
OP6 Config tab: 27 rows over the client settings store user (connected)
OP7 Headless characterOptions block automated + bot-vs-ACE run
OP8 Configure Keyboard screen user (connected)
OP9 Closeout: retire dead surfaces, bookkeeping, test script user (final matrix)

4. Slice contracts

OP1 — the Runtime option map, dirty model, and the real blob

Files.

  • Modify src/AcDream.Runtime/Gameplay/RuntimeCharacterState.cs (RuntimeCharacterOptionsState, :628): widen to the full table.
  • Create src/AcDream.Runtime/Gameplay/CharacterOptionTable.cs: the ONE typed table PlayerOption id → (word: Options1|Options2, mask, isAutoSave, clientDefault) for ids 0x00..0x34, transcribed from lane B §2 (verbatim acclient.h:4162 enum names) + lane C §3.2 (auto-save column) + D3's 0x34 row. Reject 0x35/0x36 and unknown ids at the seam (lane C §5.4.3 — ACE throws).
  • Modify src/AcDream.Runtime/GameRuntimeCommands.cs: add IRuntimeCharacterCommands.SaveOptions(RuntimeGenerationToken) (the blob-flush verb) beside SetSingleOption.
  • Modify src/AcDream.Runtime/Session/DirectGameRuntimeCommandAdapter.cs and src/AcDream.App/Runtime/CurrentGameRuntimeCommandAdapter.cs + src/AcDream.App/Net/LiveSessionRuntimeFactory.cs:335-350: move local-write-then-send INTO the shared Runtime seam so BOTH hosts get retail's ordering (fixes the headless gap lanes B+C independently found). The LiveSessionRuntimeFactory local closure is deleted, not duplicated.
  • Create src/AcDream.Core.Net/Messages/SocialActions.cs addition: BuildSetCharacterOptions(...) per lane C §2.7 EXACTLY — header always 0x460 OR-ed with present optional sections; four unconditional u32s (header, options1, spellbookFilters, options2 in pack order §2.3); echo last-parsed shortcuts / 8 spell lists / desired comps rather than zeroing (§5.3); NEVER set 0x100 (lane C U2); omit 0x200 while acdream packs nothing (safe per §2.5); 4-byte tail pad.
  • Modify src/AcDream.Core.Net/WorldSession.cs: SendSetCharacterOptions.
  • Dirty model in RuntimeCharacterOptionsState: MarkDirty on any non-auto-save change, FirstDirtiedAt, flush triggers = explicit SaveOptions command, session logout, and the 480 s timer (retail constant, lane C §3.3) driven from the existing Runtime tick.
  • Tests: tests/AcDream.Runtime.Tests/ (table completeness ×53, auto-save split ×53 vs lane B §2's byte-verified column, local-write-then-send on BOTH adapter paths, dirty/flush state machine, unknown-id rejection); tests/AcDream.Core.Net.Tests/ blob conformance: golden byte vector + round-trip through PlayerDescriptionParser (lane C §9 S-c — the CH3 builder died of green tests pinning a wrong shape; the golden vector is non-negotiable).

Register rows (same commit): 0x34 mapping ACE-sourced (D3); the 480 s autosave if any part is deferred (target: not deferred); GetDefaultOptionValue @0x005D2A30 vs ctor-default disagreement recorded when the client-default column lands (lane C §8.2 — reproduce, don't fix).

Acceptance: build + FULL Release suite green; blob golden vector byte-exact; both adapters share one code path for the local write.

OP2 — the two widget primitives + remaining UIOption mappings

Files.

  • Modify src/AcDream.App/UI/Layout/LayoutImporter.cs + src/AcDream.App/UI/Layout/ElementReader.cs: read tab-table property 0x2E (struct array {0x30 button, 0x31 page, 0x32 isDefault}) and ListBox template-list property 0x64 (entries {0x63 layout DID, 0x62 element id}) into ElementInfo.
  • Create src/AcDream.App/UI/UiTabPanel.cs (element Type 8 — retail UIElement_Panel; renamed from this plan's original UiTabControl at the OP2 rework, and a dormant UiDatElement subclass per AD-73): tab-button ↔ page-slot switching per lane A §5; default tab honoured.
  • Create src/AcDream.App/UI/UiTemplateListBox.cs (element Type 5 with authored template list): AddItemFromTemplateList(index) instantiates a row from the authored template layout/element via DatWidgetFactory, scrollbar named by P0x72.
  • Modify src/AcDream.App/UI/Layout/DatWidgetFactory.cs: map Types 5/8 and UIOption_Slider 0x10000037, UIOption_Menu 0x10000038, UIOption_CheckboxSlider 0x10000036, UIOption_CheckboxBitfield64 0x10000044 (LED checkbox 0x10000035 already maps to BuildCheckbox).
  • Modify tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs (Layouts, :17): add 0x2100002B, 0x2100002A, 0x21000028, 0x2100005C, 0x21000029. Coordinator runs the generator (ACDREAM_REGENERATE_UI_FIXTURES=1, serial tree) and commits fixtures.
  • Conformance tests pin: the tab table (4 entries, Gameplay default), all three template arrays (lane A §10.1), the Character ListBox's 6-header / 49-toggle authored row build, scrollbar linkage.

Acceptance: build + FULL suite; fixtures committed and pinned; no change to any existing widget's behaviour (the S2/AP-192 outline seams from aa6635ae must be preserved in new widget builds).

OP3 — panel shell + open paths + Gameplay tab (first vertical)

Files.

  • Create src/AcDream.App/UI/Layout/OptionsPanelController.cs: mounts 0x2100002B in host 0x2100006E slot 0x1000018D (stack key 10) via RetailWindowManager; tab control wiring; close button fires 0x1000001A.
  • Create src/AcDream.App/UI/Layout/OptionPageModel.cs: the OptionPage/PlayerOptionPage model — per-page option array of (current, saved, default) triples + verbs Apply/Reset/Defaults with retail's exact semantics (lane A §10.4): LED click applies immediately (SetCurrentValue → Apply(1)); Apply commits baseline + SaveOptions flush; Reset reverts to baseline; Defaults applies live without committing and is never disabled; Apply/Reset disable when clean; hide → revert uncommitted; show → apply + commit. Pure logic, unit-tested without DAT.
  • Input action ToggleOptionsPanel (0x1000001A, F11) in src/AcDream.UI.Abstractions/Input/ (KeyBindings.RetailDefaults() — the ONLY production table; #358's lesson) + src/AcDream.App/Input/GameplayInputCommandController.cs routing + toolbar button 0x1000019B (already authors P0x12).
  • Gameplay tab (0x2100002A, class gmGameplayOptionsUI), seven buttons per D5/D6 and lane D §1: Exit Game → the existing graceful-close path; Exit to Char Selection → retail confirm dialog (RetailDialogFactory) + mid-air refusal via the interface-text seam, then D6's Exit-Game behaviour; Configure Keyboard → opens OP8's screen; until OP8 lands the button is INERT (authored, clickable, no handler — no invented text, no stub screen), the OP3 gate script says so explicitly, and OP8's gate re-tests it (the campaign cannot close with the button inert); Use Mouse Turning Settings → the six-option macro (lane D §4.4) with its six retail chat lines, camera-mode consumer verified against the camera digest in-slice (register row if the mode is absent); In-Game Help / Urgent Assistance / Report Abuse per D5.

Register rows: Exit-to-char-select adaptation (D6); UA/RA dead-URL short-circuit (D5); help-plugin behaviour (D5); mouse-turning consumer row if needed.

Gate: connected — panel opens via F11 AND toolbar; tabs switch with Gameplay default; the seven buttons behave per contract; window drags / resizes / stacks like the CH6 floaties.

OP4 — the Character tab

Files.

  • Create src/AcDream.App/UI/Layout/CharacterOptionsPageController.cs: binds layout 0x21000028 root 0x100001F9 ListBox 0x100001FA through OP2's template mechanism; 6 headers + 50 toggles (D3) in lane B §2's authored order; every row bound by PlayerOption id through OP1's table and the shared seam (auto-save ids → 0x0005 on click-apply; batched ids → dirty + blob per OP1).
  • Defaults: extract the DAT DBPropertyCollection (GetDIDFromEnumStatic(0x16, 2), §1 U1-closure) via DatCollection at import; conformance-pin extracted values; cross-check overlapping ids against lane B/C's byte-verified default words and RECORD any disagreement as a finding (never silently pick).
  • Consumers (lane B §7.1): group B one-line binds — timestamps + filter language at RuntimeCommunicationState.AddText; daylight (ForcedDayGroupIndex), weather, fog; run-as-default in RuntimeLocalPlayerMovementState; main-pack default at the pickup path; the UI-display bits at their existing retained controllers. Group C re-pointing per D7. Group A wire+store only. Group D register rows.
  • Conformance test: all 50 rows ↔ table ↔ storage bit ↔ wire route pinned in both directions (the CH4 registry-conformance pattern — an invented row or a dropped row fails the build).

Register rows: group D deferrals (salvage, housing, fellowship-share field, PK-deaths already rowed in OP1, mouse-turning row lives in OP3); each group-C re-point that changes an observable default.

Gate: connected — LED rows toggle + persist across relogin (server echo), timestamps/daylight/fog/weather/run-default observably switch, Apply / Reset / Defaults exercise retail semantics, tab-switch reverts uncommitted edits.

OP5 — the Chat tab

Files. Create src/AcDream.App/UI/Layout/ChatOptionsPageController.cs binding 0x2100005C root 0x1000050A: the two LINKED opacity sliders (bound to the existing RetailWindowOpacityController values through ChatOpacityLink — AP-190's model, now user-reachable) and the five per-window filter blocks (SetUserData ids 8/2/3/4/5; main window 12 rows, floaties 13 — lane A §8's byte-decoded masks) writing the per-window filter state CH6 already consumes (ChatWindowState.ShouldDisplay). The 0x1000008C per-window blob stays local-only (already-anticipated register row from 2026-08-09-chat-retail-window-shell.md §6.3 — cite, don't duplicate).

Gate: connected — filter checkboxes change window routing live; opacity sliders drive the focus fade; settings survive relogin locally.

OP6 — the Config tab

Files. Create src/AcDream.App/UI/Layout/ConfigOptionsPageController.cs binding 0x21000029 root 0x100001FF: all 27 rows in lane A §9 / lane D §7.3's authored order, backed by acdream's client settings store (%LOCALAPPDATA%\acdream\ — the D1 home). Rows with live subsystems bind now: the three volume trios → the audio pipeline, "play sound only when active", mouse-look sensitivity + invert Y, FOV, chat font size/face if the chat pipeline exposes them. Rows without a subsystem (resolution + fullscreen + sync, degrades, texture detail family, multi-pass alpha) persist store-only under ONE register row enumerating them (the goal's "honest store-only handling"); resolution's SetConfirmChange flow ships whenever the consumer lands, not now.

Gate: connected — audio sliders audibly change mix; mouse sensitivity observably changes; store-only rows persist across relaunch.

OP7 — headless characterOptions

Files. Modify src/AcDream.Headless/Configuration/HeadlessConfiguration.cs + HeadlessConfigurationLoader.cs: optional characterOptions block, strict — keys are exactly the lane B §5.2 tier-1+2 option NAMES, values bool; unknown key = load failure (D8). Modify the session host (src/AcDream.Headless/Hosting/) to diff declared vs PlayerDescription-seeded state after LoginComplete and send changes through the shared seam (auto-save ids as 0x0005, remainder via one SaveOptions blob), honouring #368's one-dedicated-update-thread contract. Idempotent on reconnect (retail itself no-ops unchanged options — lane C §3.5). Headless tests: schema rejection, diff-only sends, reconnect idempotence, thread affinity preserved.

Gate: automated + one live bot-vs-local-ACE run (no graphical client) showing declared options land and survive reconnect.

OP8 — Configure Keyboard

Files. Create src/AcDream.App/UI/Layout/KeyboardConfigController.cs (+ a src/AcDream.Core/... DAT ActionMap (DBO type 0x27) reader if DatCollection lacks one): gmKeyboardUI's six ActionClass list boxes via OP2's Type 5 widget, rows = label + tooltip + N key buttons + Clear from the DAT master maps (DIDs 0x14000000/0x14000002 — the exact enum→DID pairing is lane D unknown #4, resolved in-slice by dumping both), merged with live KeyBindings; left-click key button → InputDispatcher modal capture; right-click erases; N-way cross-map conflicts + the non-user-bindable refusal per lane D §5; Save/Cancel; Reset-to-defaults reloads the DAT maps. Persistence: keybinds.json (D4).

Register rows: .keymap file interchange not implemented (D4); any retail column/behaviour consciously narrowed.

Gate: connected — rebind a movement key, conflict prompt on a taken chord, persistence across relaunch, reset restores retail defaults.

OP9 — closeout

  • Retire SettingsPanel/SettingsVM's IPanel surface + SettingsDevToolsComposition wiring + DevToolsGameplayCommands no-ops; delete GameplaySettings' 16 server-shadowed booleans (lane B §7.2.5), re-scoping true client-only settings into the D1 store. Every deletion checked against consumers; no behaviour regression.
  • Bookkeeping: plan status flips, ISSUES sweep (#358 retest against OP8's screen — its Ctrl+M lesson lives in RetailDefaults()), register reconciliation, CLAUDE.md Current-state paragraph (per feedback_claude_md_staleness), memory digest update (project_chat_digest addendum or a new settings digest).
  • Write docs/research/2026-08-10-campaign-op-test-script.md: the connected-gate script covering every OP3OP8 gate item, per-tab, with expected retail behaviours — the campaign's stop condition is this script ready plus all slices code-complete.

Gate: the user's final connected matrix (their eyes, their pace).


5. What is explicitly OUT of scope

  • Packing the 0x200 GameplayOptions blob section (per-window chat state on the wire) — a follow-on (CH6f shape), pre-anchored by lane C U1 and 2026-08-09-chat-retail-window-shell.md §6.3's register row.
  • A pre-world character-select flow (D6 adapts; its register row carries the future work).
  • Retail .keymap file read/write (D4 register row).
  • The 0x21000017 docked gmPanelUI host variant — acdream ships the floating host only (register row in OP3 if the review deems it a divergence; retail exposes both).

6. Verification discipline

Per commit: dotnet build + FULL Release suite green (baseline at plan time: 12,611 / 4 skips / 0 failures at 29138430+). Golden byte vectors for every wire builder. Conformance pins for every authored inventory (row lists, template arrays, tab table, checkbox masks). Register rows in the SAME commit as the deviation. No user-visible placeholder text ever — all user-facing strings resolve from the DAT string tables (0x23000001/0x23000003) by compute_str_hash name, never hard-coded English.

7. Review protocol

Dual-lens Opus review per slice (mechanism-faithfulness lens × regression/blast-radius lens), fixes applied by the implementer; REJECT → focused re-review; TWO failures → Fable fixes directly (user-directed 2026-08-10). Review findings that feed both a fixer and a re-reviewer are persisted to a committed findings doc BEFORE dispatch.

8. Process rules (binding, inherited from Campaign CH via the handoff)

Max 34 agents in parallel INCLUDING children; every agent prompt carries an explicit no-subagent clause; ONE builder/tester on the tree at a time (read-only research may overlap); agents never launch the graphical client; the user runs all connected gates; screenshots transcribed into docs immediately; ledger placeholders anchored per-row, post-amend SHAs recorded by the coordinator; decomp claims byte-verified against the PDB-paired binary (check_exe_pdb.py MATCH first); stalled agents resumed via SendMessage before any redo; agent claims spot-verified at the seams before anything builds on them.

9. Ledger

Slice Status Commit(s) Review Gate
OP1 CLOSED 86c0a7e0 + fixes 09029f9f + residuals 6f48e341 dual APPROVE-WITH-FIXES (2026-08-10-op1-review-{mechanism,blast}.md) → re-review CLOSED (2026-08-11-op1-rereview.md); residuals R1/R2/R3 landed automated only — n/a
OP2 CLOSED df9c7a35 (REJECTED) → rework b236a442 → closure (this commit) dual REJECT (2026-08-11-op2-review-{mechanism,blast}.md) → re-review blast CLOSED / mechanism REOPEN-on-one (2026-08-11-op2-rereview-{mechanism,blast}.md) → coordinator closure: AP-195 filed, AD-73 addendum, tooltip port, zero-children pin automated only — n/a
OP3
OP4
OP5
OP6
OP7
OP8
OP9