From 289bf5bc6e6d984d48fcb1b0cec74d5bc475cebf Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 11 Aug 2026 13:35:45 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20OP9=20review=20=E2=80=94=20APPROVE-WITH?= =?UTF-8?q?-FIXES?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Combined mechanism-faithfulness x regression/blast-radius pass over 371197a3 (OP9 retirement of the dead F11 settings surface + GameplaySettings), per the OP5/OP7 single-reviewer precedent for closeout-shaped slices. Retirement verified correct: zero production readers of GameplaySettings or SettingsVM existed pre-commit (checked against the pre-commit tree, not the diff), SetUiLocked first-call/repeat-call behavior is provably unchanged by deleting _uiLockConverged, the wire paths (SetAcceptLootPermits 0x0005, ToggleUiLock, PlayerDescription convergence) are untouched, the settings.json unknown-key carry-forward is real, and the AP-196 register edit reconciles (143 -> 142 active, 29 -> 30 retired, total unchanged). MUST-FIX 1: SaveAudio -> ApplyAudio (OP6 live-apply, live consumer in ConfigOptionsPageController) lost its only assertion when SettingsViewModelSavePreservesSectionAndTargetOrder was deleted. SHOULD-FIX 2-5: stale architecture-doc seam naming SettingsVM; three dead residues (uncallable private SaveCharacter, orphaned IngressShutdownRoots.Settings, writerless CharacterSettings path); test delta enumerates to -84, not the claimed -80 "exactly"; dangling comment referencing the deleted assertion. Two nits. Co-Authored-By: Claude Fable 5 --- docs/research/2026-08-11-op9-review.md | 391 +++++++++++++++++++++++++ 1 file changed, 391 insertions(+) create mode 100644 docs/research/2026-08-11-op9-review.md diff --git a/docs/research/2026-08-11-op9-review.md b/docs/research/2026-08-11-op9-review.md new file mode 100644 index 00000000..298ff0cd --- /dev/null +++ b/docs/research/2026-08-11-op9-review.md @@ -0,0 +1,391 @@ +# Campaign OP slice OP9 — combined-lens review (mechanism × blast radius) + +**Target:** `371197a3` — *refactor(settings): OP9 — retire the dead F11 +settings surface + fully-superseded GameplaySettings* +**Reviewer:** combined mechanism-faithfulness × regression/blast-radius pass +(one reviewer, OP5/OP7 precedent for closeout-shaped slices). +**Base:** `1a57f96e` (campaign tip after the OP8 merge). Worktree clean at +review time; HEAD == the target commit. + +## VERDICT: APPROVE-WITH-FIXES + +The retirement itself is correct. Every claim in the commit message's +per-file disposition list that bears on **live behavior** verifies against +the post-commit source: no production code path read `GameplaySettings`, no +production code path constructed `SettingsVM`, `SetUiLocked`'s observable +contract is unchanged, the wire behavior of `SetAcceptLootPermits` / +`ToggleUiLock` / the PlayerDescription convergence is untouched, and the +settings.json unknown-key carry-forward mechanism is real. The register +edit is arithmetically and semantically correct. + +Five findings, none of them a behavior regression: one lost regression +guard on a live mechanism (MUST-FIX 1), one stale **architecture** doc +seam (SF-2), three dead residues the retirement created and left (SF-3), +a test-delta claim that does not reconcile (SF-4), and a dangling +comment (SF-5). Plus two nits. + +--- + +## MUST-FIX 1 — `SaveAudio` → `ApplyAudio` (OP6's live-apply) lost its only test + +`RuntimeSettingsController.SaveAudio` pushes the saved snapshot into the +live engine — `src/AcDream.App/Settings/RuntimeSettingsController.cs:420` +(`_runtimeTargets?.ApplyAudio(audio);`), documented at `:407-413` as the +Campaign OP slice OP6 change that made Audio-tab edits take effect +without a relaunch. Its live production consumer is +`ConfigOptionsPageController`'s Sound rows (`ConfigOptionsPageController.cs:103`). + +Pre-commit, exactly one test asserted that push: the now-deleted +`SettingsViewModelSavePreservesSectionAndTargetOrder`, whose ordered event +list contained `"target-audio"` with an explicit OP6 comment +(`371197a3~1:tests/AcDream.App.Tests/Settings/RuntimeSettingsControllerTests.cs:296-312`). + +Post-commit, `grep -rn ApplyAudio src tests` shows **no assertion anywhere** +that `SaveAudio` invokes `IRuntimeSettingsTargets.ApplyAudio`. The fake +still records the event — +`tests/AcDream.App.Tests/Settings/RuntimeSettingsControllerTests.cs:817-820` +(`events.Add("target-audio");`) — and nothing ever reads it. The two +surviving audio tests (`ComputeEffectiveCategoryVolumes_*`, `:241-278`) +test the static volume helper, not the controller push. `ApplyStartup`'s +own audio call (`RuntimeSettingsController.cs:226`) is still covered by +`StartupApplyIsOrderedExactlyOnceAndRuntimeBindingDoesNotReplay` (`:54`), +so this gap is specific to the **save** path. + +This is the class the slice contract cannot absorb: the commit asserts +"no behavior change to anything live, verified", and one live mechanism is +now unverified. Note the contrast — the *other* three target pushes were +kept covered, deliberately or not: + +| Push | Post-commit coverage | +|---|---| +| `SaveDisplay` → `ApplyDisplayWindowState` | implicit but real — `DisplayTargetFailurePreservesEstablishedStoreThenPublishBoundary` (`:566`) sets `ThrowOnDisplay` and asserts the resulting state boundary, which only happens if the target was called | +| `ReapplyQualityPreset` → `ApplyQuality` | positive (`:633`, `:653`) | +| `SetUiLocked` → `ApplyUiLock` | positive (`:672-679`, `:694`, `:702`) | +| `SaveChat` → `SetChatOpacity` | positive — `SaveChat_PushesOpacityToRuntimeTargets_LiveApply_NoRestart` (`:338`) | +| **`SaveAudio` → `ApplyAudio`** | **none** | + +**Fix (minimal):** in `RuntimeSettingsControllerTests`, add the missing +positive assertion in the same shape as the chat-opacity test — bind a +`FakeRuntimeTargets`, call `controller.SaveAudio(controller.Audio with +{ Master = 0.25f })`, assert the recorded events contain `"target-audio"` +(ideally assert the *value* forwarded, per the `:236-240` comment's own +lesson about M2's inversion). + +--- + +## SHOULD-FIX 2 — the architecture doc still names `SettingsVM` as a live seam + +`docs/architecture/code-structure.md:209-212`: + +> `WorldRenderFrameBuilder -> RuntimeWorldFrameSettingsPreview -> +> IRuntimeSettingsPreviewSource -> RuntimeSettingsController -> optional +> SettingsVM` for the live settings draft preview applied before world drawing. + +This sits inside a present-tense list of current presentation seams +("These are presentation state/command seams…", `:217`), not a historical +narrative. It is the one surviving reference that would send a future +reader looking for a type that no longer exists, and CLAUDE.md's +architecture rule ("when the architecture doc and reality diverge, update +one or the other") makes it in-scope for the same commit. + +Secondary, lower severity, same file: `:738-739` — "The controller also +preserves unrelated unsaved Gameplay draft fields when combat preferences +change" is now false. It is inside the Slice-8 checkpoint-G/H ledger +narrative, so it reads as dated history; a one-clause "(retired at +Campaign OP slice OP9)" is enough. + +Everything else the repo-wide sweep turned up is genuinely historical and +correctly left alone: `docs/plans/2026-07-22-gamewindow-slice-8-*.md`, +`memory/project_gamewindow_decomposition.md:81/85/181`, +`docs/research/2026-08-1*-op{3,4,6,8}-*.md`, +`docs/research/2026-08-10-keyboard-config-and-gameplay-tab.md`, +`docs/research/2026-08-10-character-options-map.md`, +`docs/ISSUES.md:15859`, and the `docs/research/2026-05-21-a6-captures/*.log` +run logs. + +--- + +## SHOULD-FIX 3 — three dead residues left behind by the retirement + +The slice deleted the *callers* without sweeping what became unreachable: + +1. **`RuntimeSettingsController.SaveCharacter(CharacterSettings)`** — + `src/AcDream.App/Settings/RuntimeSettingsController.cs:547-566`. Private, + **zero callers** post-commit (its only caller was the `saveCharacter` + callback handed to the deleted `CreateViewModel`). C# emits no warning + for an unused private method, so the build stays green while the method, + its `_defaultCharacter` promotion branch, and the whole write half of + the `CharacterSettings` store are unreachable: + `IRuntimeSettingsStorage.SaveCharacter` (`:31`), + `JsonRuntimeSettingsStorage.SaveCharacter` (`:68`), and + `SettingsStore.SaveCharacter` now have no production writer. + (`ActiveToonKey` is genuinely live — `InteractionRetainedUiComposition.cs:507` + and `:612` key per-character window layouts off it — so only the + `Character`/`SaveCharacter` half is dead. `RuntimeSettingsController.Character` + likewise has no production reader.) + The commit also deleted that method's only test + (`CharacterSaveUsesActiveToonAndDefaultSaveBecomesResetContext`) without + re-targeting it — consistent with the code being dead, but it means the + code is now both uncallable and untested rather than removed. +2. **`IngressShutdownRoots.Settings`** — + `src/AcDream.App/Rendering/GameWindowLifetime.cs:75`, captured at + `src/AcDream.App/Rendering/GameWindow.cs:1670`. Its **only** reader was + the `Soft("settings view model", …UnbindViewModel())` stage this commit + deleted; `grep` finds no other `ingress.Settings` use. Harmless (a + reference held until shutdown) but it is now a shutdown-roots member + that roots nothing. + +Either delete these in the OP9 follow-up, or — if `CharacterSettings` +persistence is meant to come back on a later Options-panel tab — say so in +a one-line comment so the next reader does not re-derive that it is dead. +The commit message's "CharacterSettings … UNTOUCHED per the campaign +contract" covers keeping the *record and store*; it does not explain the +now-uncallable controller method. + +--- + +## SHOULD-FIX 4 — the −80 test delta does not reconcile; static enumeration gives −84 + +The commit states the delta "is exactly the deleted SettingsVM/ +SettingsPanel/GameplaySettings test surface". Enumerating the touched +files at both commits: + +| File | Removed | Added | +|---|---:|---:| +| `tests/.../Panels/Settings/GameplaySettingsTests.cs` (deleted) | 3 | — | +| `tests/.../Panels/Settings/SettingsVMTests.cs` (deleted) | 43 | — | +| `tests/.../Panels/Settings/SettingsPanelTests.cs` (deleted) | 29 | — | +| `tests/AcDream.App.Tests/Settings/RuntimeSettingsControllerTests.cs` | 13 | 6 | +| `tests/.../Panels/Settings/SettingsStoreTests.cs` | 4 | 2 | +| `tests/AcDream.App.Tests/Input/GameplayInputCommandControllerTests.cs` | 2 (InlineData) | 2 (InlineData) | +| `tests/.../Rendering/GameWindowSlice8BoundaryTests.cs`, `tests/.../Input/InputDispatcherCaptureTests.cs` | 0 | 0 | +| **Net** | **94** | **10** | + +**−84**, not −80. All three deleted files declare `public sealed class` +(discoverable), none carries `[Theory]`/`[MemberData]`/`Skip`, and the +skip count is 4 on both sides — so no discovery artifact explains the gap. +The baseline is quoted twice in the immediately preceding commits +(`f1d50207`, `1a57f96e`: 13,155 passed / 4 skipped), so either that +baseline is off by 4 or the post number is. Nothing in the diff adds four +tests elsewhere. + +This is an evidence-accounting defect, not a behavior defect — **no live +behavior test is unaccounted for by it** (the one real coverage loss is +MUST-FIX 1, which is inside the 13-removed RuntimeSettingsControllerTests +bucket). Re-measure and restate, or drop the word "exactly". + +For the record, the 13 removed `RuntimeSettingsControllerTests` cases +disposition cleanly: 6 were re-targeted onto the surviving API +(`CharacterContext*`, `DraftPreview*`, `RuntimeTargetLoans*`, +`SetUiLocked_*`, `SyncChatFromServerOptions*`, `UnboundRuntimeTargets*`), +5 were SettingsVM-draft-only, 1 (`UiLockPersistenceFailureCanRetry…`) +pinned a retry against a save that no longer exists — and its +target-failure sibling `UiLockTargetFailureCanRetryTheSameRequestedValue` +(`:683`) survives and still pins the real invariant — 1 covered the now-dead +`SaveCharacter` (SF-3), and 1 (`SettingsViewModelSavePreservesSectionAndTargetOrder`) +carried the `"target-audio"` assertion (MUST-FIX 1). + +--- + +## SHOULD-FIX 5 — dangling comment referencing a deleted assertion + +`tests/AcDream.App.Tests/Settings/RuntimeSettingsControllerTests.cs:236-240`: + +> "The FakeRuntimeTargets-based `"target-audio"` assertion **above** (and its +> predecessor before this rework) only ever checked that ApplyAudio fired…" + +There is no longer any `"target-audio"` assertion above (or anywhere); what +is now immediately above is +`ConcreteRuntimeTargetPublishesSetSingleCharacterOptionOntoTheBus`. Reword +when fixing MUST-FIX 1 — the two are the same edit. + +--- + +## NIT 6 — "bound through … `CharacterOptionsPageController`" is imprecise for 2 of the 13 + +Commit §4 and the AP-196 retirement note both say all 13 members "were +ALREADY bound through `CharacterOptionTable`/`CharacterOptionsPageController`'s +server-bit seam (the OP4 Character tab)". All 13 are in the table — +verified one by one at `src/AcDream.Runtime/Gameplay/CharacterOptionTable.cs` +`:117,119,121,123,125,127,130,131,132,158,160,161,162` — but **two are +deliberately not Character-tab rows** (`CharacterOptionsPageControllerTests.cs:26` +names the exclusion set: `AppearOffline`, `UseMouseTurning`, `LockUI`, per +research §2.7): + +- `LockUI` — bound through `/lockui` + the radar control: + `LiveSessionRuntimeFactory.cs:419-421` (server-bit read, `0x0005` send, + `SetUiLocked` visual push), `InteractionRetainedUiComposition.cs:492` + (mount-time seed), `:647` (radar binding). +- `UseMouseTurning` — bound through the Gameplay-tab mouse macro: + `InteractionRetainedUiComposition.cs:843-845` (`IsUseMouseTurningEnabled` + live server-bit read) and `RetailUiRuntime.cs:2009-2019` (wire send). + Note this is the *server* bit, correctly distinct from the Config tab's + own client-local `CameraTurningSettings.UseMouseTurning` (TS-74, + `ConfigOptionsPageController.cs:605-612`). + +The **substance holds** — all 13 have a live server-bit home and none was +orphaned — only the sentence's channel attribution is loose. Worth +correcting in the register row, which is the durable artifact. + +## NIT 7 — OP9's own bookkeeping is still outstanding + +`docs/plans/2026-08-10-options-panel-campaign.md:428` still reads +`| OP9 | — | | | |`, and `:9` still says "ACTIVE — planned 2026-08-10. No +slices landed yet" (stale for the whole campaign, pre-existing). §OP9's +bullet list (`:352-370`) also owes the ISSUES sweep (#358 retest), the +CLAUDE.md Current-state paragraph, and the memory digest update. None of +that is a defect in *this* commit — it is the closeout half of the slice — +but it should not be forgotten when OP9 is declared complete. + +--- + +## Verified clean (mechanism lens) + +1. **`RuntimeSettingsController` live core** — all present and unaltered: + storage load/save for Display/Audio/Chat/Character (`:170-181`), + `ResolvedQuality`/`ReapplyQualityPreset` (`:369-383`), `ApplyStartup` + (`:213-235`), `SaveAudio`'s OP6 live-apply (`:414-427`), public + `SaveDisplay` (`:391-405`, still the `ConfigOptionsPageController` seam), + `IRuntimeSettingsTargets.SetSingleCharacterOption` (`:115`) and + `SetChatOpacity` (`:124`), `ServerOptionsSeeded` / + `NotifyServerOptionsSeeded` (`:542-545`), `SetUiLocked` (`:292-299`). + The only removals are `Gameplay`, `SetAcceptLootPermits`, the private + `SaveGameplay`, and the four `_viewModel?` notification calls. + + **`SetUiLocked` first-call / repeat-call equivalence — verified.** + The old guard was `if (_lastAppliedUiLocked == locked && _uiLockConverged) + return;` with `_uiLockConverged` initialised **true** and + `_lastAppliedUiLocked` initialised **null**. On the first call + `(bool?)null == locked` is `false`, so the conjunct never mattered: the + first `ApplyUiLock` push happened then and happens now. Deleting + `_uiLockConverged` therefore cannot have changed whether the first push + fires. Repeat same-value calls returned early then (after a successful + `SaveGameplay` set `_uiLockConverged = true`) and return early now. + Failure ordering is also preserved: `_lastAppliedUiLocked` is still + assigned *after* `ApplyUiLock`, so a throwing target leaves the guard + un-advanced and the same value can be retried — pinned by + `UiLockTargetFailureCanRetryTheSameRequestedValue` (`:683-704`). The one + behavioral difference is that a *storage* failure no longer forces a + redundant re-push on the next same-value call; there is no storage call + left to fail, and `ApplyUiLock` is idempotent. All three call sites + traced and unchanged: PlayerDescription convergence + (`LiveSessionRuntimeFactory.cs:339-340`), `ToggleUiLock` (`:419-421`), + radar/Character-tab path (`InteractionRetainedUiComposition.cs:647`). + +2. **`IRuntimeSettingsPreviewSource`** — the "already always false in + production" claim is **true**. Searching the *pre*-commit tree + (`git grep … 371197a3~1`) for `CreateViewModel` / `CreateViewModelBinding` + / `new SettingsVM` / `new SettingsPanel` returns **only** test files plus + the controller's own definitions. `_viewModel` was therefore always + `null` in production, so `HasDraftPreview` was always `false` and + `DisplayPreview`/`AudioPreview` always fell through to + `Display`/`Audio` — exactly what they now return unconditionally. Both + consumers see identical values: `WorldRenderFrameBuilder.cs:370-380` + (the whole draft block was already dead in production) and + `LiveObjectFrameController.cs:59-62` (`SettingsParticleRangeSource`, + which reads `DisplayPreview` unconditionally — unchanged). Third + consumer `InteractionRetainedUiComposition.cs:685` + (`DisplayPreview.ShowFps`) likewise unchanged. + +3. **`GameplayInputCommandController`** — diff confirms exactly three cases + touched. `AcdreamToggleDebugPanel` (`:190-197`) and `ToggleChatEntry` + (`:208-213`) keep `return true`, matching the prior no-op's handled + contract; `ToggleOptionsPanel` (`:214-224`) still calls + `_retained.ToggleOptionsPanel()` with only the comment reworded. No + other `InputAction` case changed, and the removed constructor parameter + is the only signature change (`SessionPlayerComposition.cs:1194` + updated). Worth recording: the return value is inert in production — + `GameplayInputActionRouter.HandleCommand` (`:170-171`) discards it and + the command controller is the **last** stage of the priority graph — so + the `true`-vs-`false` question could not have been observable either + way. The new `RetiredDevToolsCommand_IsConsumedWithoutClaimingATypedOwner` + theory (`GameplayInputCommandControllerTests.cs:43-56`) re-targets the two + deleted mapping rows rather than dropping them. No `ToggleSettingsPanel` + `InputAction` exists (`grep` over `AcDream.UI.Abstractions` is empty), so + `KeyBindings.RetailDefaults()` and the keymap fixtures needed no edit — + claim confirmed. + +4. **`LiveSessionRuntimeFactory`** — `SetAcceptLootPermits` still sends + `0x0005` via `SendSingleCharacterOption` (`:494-496`); only the + `_interaction.Settings.SetAcceptLootPermits(value)` write-behind line was + removed, and it had no reader. `AcceptLootPermits` still reads the server + bit (`:491-493`). `ToggleUiLock` (`:415-422`) and the PlayerDescription + `OnCharacterOptionsChanged` convergence (`:337-345`) are byte-identical + apart from comments. + +5. **settings.json migration** — mechanism verified at + `SettingsStore.SaveSection` (`src/AcDream.UI.Abstractions/Panels/Settings/SettingsStore.cs:643-687`): + every top-level key other than the target section and `"version"` is + carried forward by `prop.Value.GetRawText()`, so an orphaned + `"gameplay"` block survives *every* subsequent save, and no `Load*` + method reads it. The two new tests + (`SettingsStoreTests.cs:219-260`) pin exactly that, not something weaker: + the first proves a leftover block does not disturb Display/Audio/Chat + loads; the second writes an unrelated section and asserts the raw + `"gameplay"` / `"lockUI": true` / `"showHelm": false` text is still in the + file afterwards. The re-targeted coexistence and window-position/layout + tests correctly substitute a Display assertion for the deleted Gameplay + one, preserving each test's actual point. + +## Verified clean (blast lens) + +- **Forward references.** Repo-wide sweep (`src`, `tests`, `tools`, + `scripts`, `docs`, `.claude`) for `SettingsVM`, `SettingsPanel`, + `RuntimeSettingsViewModelBinding`, `CreateViewModel`, + `IDevToolsGameplayCommands`, `DevToolsGameplayCommands`, + `GameplaySettings`, `LoadGameplay`, `SaveGameplay`, + `SetAcceptLootPermits` (the controller method), `ToggleSettingsPanel`, + `UnbindViewModel`: the only *code* hits are OP9's own explanatory + comments, plus `ICombatGameplaySettingsSource` substring matches + (`RuntimeCharacterState.cs:850`) and an unrelated + `DebugVM CreateViewModel()` local helper in + `tests/AcDream.App.Tests/Combat/CombatFeedbackSlotTests.cs:29`. One + vacuous-but-harmless residue: `GameWindowSlice8BoundaryTests.cs:319` + still asserts `DoesNotContain(".LoadGameplay()")` (and `:328/:330` + `_persistedGameplay`/`_settingsVm`) — negative source-text guards that + can no longer fail wrongly; leaving them is fine. +- **No string/reflection lookup** of the deleted panel: `acdream.settings` + (the old `SettingsPanel.Id`) appears nowhere in the repo. +- **`IPanel`/`IPanelRenderer` survive with real implementers** — + `ChatPanel.cs:34`, `DebugPanel.cs:22`, `VitalsPanel.cs:16`. Claim + confirmed. +- **Headless / UI Studio / plugin surfaces** (the three the implementer's + 15-file list did not cover): `src/AcDream.Headless`, + `src/AcDream.Plugin.Abstractions`, `src/AcDream.Runtime`, + `src/AcDream.Core*`, `tools/`, `scripts/`, `.claude/` — **zero** hits for + any deleted type. `src/AcDream.UI.ImGui/` is an empty leftover directory, + not in `AcDream.slnx`. +- **`SettingsDevToolsComposition` survives correctly.** Despite its name it + is a live composition phase (`ApplyStartup`, + `SettingsDevToolsComposition.cs:53`); only its doc comment needed the + update it got. The plan's "retire … `SettingsDevToolsComposition` wiring" + bullet referred to the ImGui wiring already removed at V11. +- **Register (§7 of the brief).** `AP-196` is struck through with a + substantive retirement note (`retail-divergence-register.md:192`), the + section-3 header moves 143 → 142 (`:178`), and the counts verify by + enumeration: active `| AP-` rows 143 → 142, retired `| ~~AP-` rows + 29 → 30, total row count unchanged. No other row cites a deleted type — + the only other `GameplaySettings` hit is `TS-73` (`:367`), a substring + match inside `ICombatGameplaySettingsSource`. +- **Gate script (§9 of the brief).** + `docs/research/2026-08-11-campaign-op-test-script.md` is clean: every + "F11" and "Gameplay tab" reference is to the retail Options panel + (post-OP3), not to the deleted ImGui surface. No step exercises anything + this commit removed. + +--- + +## Fix list (ordered, for the fixer) + +1. Add the `SaveAudio` → `ApplyAudio` assertion to + `RuntimeSettingsControllerTests`; reword the `:236-240` comment in the + same edit (MUST-FIX 1 + SF-5). +2. Update `docs/architecture/code-structure.md:209-212` (drop the + `-> optional SettingsVM` link) and tag `:738-739` as OP9-retired (SF-2). +3. Delete `RuntimeSettingsController.SaveCharacter` (`:547-566`) and + `IngressShutdownRoots.Settings` (`GameWindowLifetime.cs:75`, + `GameWindow.cs:1670`), or document why they stay (SF-3). +4. Re-measure and restate the test delta, or drop "exactly" (SF-4). +5. Correct the LockUI/UseMouseTurning channel wording in the AP-196 + retirement note (NIT 6); fill the OP9 plan-ledger row at closeout + (NIT 7).