# Campaign OP connected-gate test script **Status:** OP3 and OP7 sections. Later slices (OP4-6, OP8) append their own sections here as they land; the campaign's OP9 closeout gate is this document complete plus every slice code-complete. This document is the script the user runs against the live connected client (`ACDREAM_LIVE=1` against the local ACE server) to accept each slice. Each item states what to do and what retail-faithful behavior to expect. Anything marked **INERT** is authored and clickable but deliberately does nothing yet — that is the correct, contracted behavior for this slice, not a bug. --- ## OP3 — panel shell, open paths, Gameplay tab ### Opening and closing the panel 1. **Press F11.** The Options panel opens — a floating, resizable window titled by its own chrome (no title-bar text is authored; the window itself is the visual identity). The **Gameplay Options** tab is selected by default (leftmost tab, matching the user's own retail screenshot). 2. **Press F11 again.** The panel closes. 3. **Click the toolbar's Options button** (the icon at the right end of the shortcut row, next to Inventory). The panel opens. If a DIFFERENT `gmPanelUI`-family panel was already open (Character Info, Skills, Vitae, Inventory, etc.), opening Options closes it first — this is retail's real "one active panel" behavior (`gmPanelUI`'s shared geometry/exclusive-child model), not new to this panel. **Note the button's STATE change, not just its behavior:** before this slice this button was greyed out/disabled (panel id 10 was not in the catalog) and clicking it did nothing. It should now be fully enabled, AND it should highlight (same visual as the Inventory/Character/Magic buttons) while the Options panel is open, un-highlighting when it closes. 4. **Click the panel's own close (X) button**, top-right of the window chrome. The panel closes — same action as F11. 5. **Drag the window by its border/chrome; resize it from the BOTTOM edge only.** Options shares its geometry policy with every other `gmPanelUI` sibling (Character/Inventory/Spellbook/the indicator-detail panels): draggable, resizable from the bottom edge only (NOT the top, left, or right edges, and NOT a corner — the panel does not resize horizontally at all), and it remembers its HEIGHT across a close/reopen within the same session, same as every sibling. This is a correction from OP3-as-landed, which briefly gave Options all-four-edge/horizontal resize that no sibling has and that silently reverted to 300px wide the next time a sibling panel was shown. ### Tab switching 6. **Click each of the four tabs** (Gameplay Options, Character, Chat, Config). Each switches the visible page; exactly one page is visible at a time. Character/Chat/Config show their AUTHORED content (row templates, Apply/Reset/Defaults buttons, scrollbars) but nothing on those three tabs is wired to live data yet — that is OP4/OP5/OP6's scope, not a bug in this slice. Only the currently-selected tab's button highlights as "open"; the others read "closed". 7. **Switch away from Gameplay and back.** No crash, no stuck state. ### The seven Gameplay-tab buttons 8. **Exit Game.** Click it. The client logs off and closes gracefully — same behavior as pressing Escape then confirming, or closing the window. **No confirmation dialog** (retail has none on this path). 9. **Exit to Character Selection.** Click it. A confirmation dialog appears ("Are you sure you want to end this character session?" or the DAT-resolved retail equivalent). Click **Yes** while standing on solid ground: the client logs off and closes — **today this behaves identically to Exit Game**, NOT a return to a character-select screen (acdream has no pre-world character-select flow yet — register row AD-74). Click **No**: nothing happens, panel stays open. 10. **Exit to Character Selection while airborne** (jump and click the button mid-air, or confirm while still in the air). Expect the chat line **"Cannot log off while in mid-air."** in the SpewBox/chat scroll instead of logging off — this is retail's own byte-verified refusal, faithfully ported. 11. **Configure Keyboard.** Click it. **INERT — nothing happens.** This is the contracted behavior for OP3; OP8 wires the real Configure Keyboard screen, and OP8's own gate re-tests this exact button. Do not report this as a bug for OP3. 12. **In-Game Help Files.** Click it. **INERT — nothing happens.** Retail's own help viewer is a third-party plugin (`plugins\ACHelpPlugin.dll`) acdream does not have; retail itself fails silently with the plugin absent, and this button mirrors that (register row AD-76). 13. **Urgent Assistance.** Click it. Expect a chat line explaining the web browser could not be launched and pointing at `http://support.turbine.com/ics/support/ticketnewwizard.asp?style=classic` — retail's own byte-verified failure text, always shown (the URL is dead in 2026, so acdream never attempts to open a browser — register row AD-75). **No native browser window opens, no OS dialog box.** **Legibility check (blast review SHOULD-FIX 4):** this is a THREE-LINE, ~110-character-URL body landing in the SpewBox overlay, which auto-expires after 5 seconds. Read the whole body BEFORE it disappears — if you cannot finish reading it (especially the URL) in that window, report it as a legibility problem; the honest fix would be routing it to the scrolling chat transcript (which stays and scrolls back) instead of shortening the timeout. 14. **Report Abuse.** Click it. Same shape as Urgent Assistance (including the same legibility check), with its own retail text ("...to submit an abuse report..."). 15. **Use Mouse Turning Settings.** Click it. Expect one retail chat line per changed value among: Camera Stiffness, Camera Adjustment, Mouse Sensitivity, Align To Slope, Invert Mouselook Axes, Turn to Face Camera — a FRESH character (never touched these settings) sees all six lines; clicking the button a second time in the same session sees zero lines (everything already at the macro's target). **These six lines are byte-verified `RetailLogTextType.Magic` (0x07), NOT the client-local refusal type** — expect LIGHT BLUE text in the SCROLLING CHAT TRANSCRIPT (not the transient SpewBox overlay). Do NOT expect a timestamp prefix: acdream renders no chat timestamps yet (the Display Timestamps consumer is OP4 scope, and there is no chat-log file — TS-69), so a bare light-blue line is CORRECT here (OP3 re-review R1). If you see bright red text in a transient 5-second overlay instead (the ClientLocal/SpewBox look used by the mid-air refusal and UA/RA above), that is the pre-fix-round bug — report it. **Known, registered gap (TS-74): acdream has no persistent "turn to face camera" mouse-turning MODE yet, so this button changes stored preferences and sends the wire bit, but you will not see the camera actually start turning with mouse movement** — that consumer does not exist yet. Verify the CHAT LINES and that the button is clickable/does not crash; do not expect a camera-behavior change. ### Cross-session persistence (mechanism review S6) 16. **Relog the SAME character** (log off through Exit Game, or disconnect/reconnect, then log back in) after step 15 has already shown all six lines once. Open the Options panel and click "Use Mouse Turning Settings" again. Expect **zero** chat lines this time — `UseMouseTurning` is an auto-save id (`SetSingleCharacterOption 0x0005`), so the bit reached ACE on the first click and the fresh post-relog `PlayerDescription` echoes it back as already-on. Seeing the "Turn to Face Camera was changed..." line AGAIN after a clean relog means the bit did not actually persist server-side — report it. 17. **Fully relaunch the client** (close the window, start a new process) after step 15. Reopen the Options panel — no button click needed. The five CLIENT-LOCAL preferences (Camera Stiffness, Adjustment Speed, Mouse Sensitivity, Align To Slope, Invert Mouselook Axes) persisted to `settings.json`'s `cameraTurning` section should still read at the macro's target values from the PREVIOUS session — click "Use Mouse Turning Settings" once more and confirm you again see zero lines (proving the five client-local prefs, not just the one server bit, survived the relaunch). ### What to report - Any of the above NOT matching (wrong text, wrong tab default, window not draggable/resizable, close button not working, F11/toolbar not opening the panel, opening Options not closing a sibling panel, toolbar button not highlighting while the panel is open). - Any exception/crash on any of the 17 steps. - Whether the confirmation dialog's exact wording looks retail-correct (it resolves from the DAT string table at runtime; a fallback English string only appears if that resolution fails, which would itself be worth reporting). ### Explicitly NOT in scope for this gate - Character/Chat/Config tab content (rows, checkboxes, sliders, live data) — OP4/OP5/OP6. - Configure Keyboard's actual screen — OP8. - Any observable camera change from "Use Mouse Turning Settings" — no acdream consumer exists yet (TS-74). --- ## OP7 — headless `characterOptions` Unlike OP3-OP6, this is not a graphical-client gate: no window is launched. The coordinator runs `acdream-headless run` against local ACE with a config declaring a small `characterOptions` block, and inspects the ACE-side persisted `CharacterOptions1`/`CharacterOptions2` (and, for the `ListenTo*Chat` ids, the actual Turbine room membership) before and after. Automated coverage (schema rejection, the diff-and-send engine, the wiring from a real `PlayerDescription` game event through to a captured wire action, and dedicated-update-thread affinity) already runs in `tests/AcDream.Headless.Tests/HeadlessConfigurationLoaderTests.cs`, `HeadlessCharacterOptionsSeederTests.cs`, and `HeadlessCharacterOptionsSeederWiringTests.cs` — this script is for the ONE thing those tests cannot prove: that a REAL ACE server actually accepts and persists the sends. ### Recipe 1. **Confirm the character's starting state.** Before running the bot, note (or reset) the target character's `IgnoreAllegianceRequests` (0x01, auto-save, `SetSingleCharacterOption 0x0005`) and `ListenToTradeChat` (0x24, auto-save, also `0x0005` — additionally joins/leaves the Turbine trade room) options — e.g. via an existing graphical login, or by inspecting ACE's stored `CharacterOptions1`/ `CharacterOptions2` for the character row directly. Pick a THIRD, batched (non-auto-save) id for the blob path — `SalvageMultiple` (0x22, tier 2) is a safe choice: it has no observable server-side effect beyond the stored bit, so a mismatch is purely a persistence question, not a behavior one. 2. **Author a headless config** with all three declared at values that DIFFER from the character's current stored state, e.g.: ```json { "version": 1, "sessions": [{ "id": "op7-bot", "endpoint": { "host": "127.0.0.1", "port": 9000 }, "account": "testaccount", "character": { "name": "+Acdream" }, "policy": { "id": "idle" }, "credential": { "provider": "environment", "reference": "OP7_BOT_PASSWORD" }, "characterOptions": { "IgnoreAllegianceRequests": true, "ListenToTradeChat": true, "SalvageMultiple": true } }] } ``` (Flip each `true` to `false` instead if the character already has that bit set — the point is a genuine diff in both directions, not specifically "everything ON".) 3. **Run it**: `acdream-headless run --config op7-bot.json` with `OP7_BOT_PASSWORD` set in the environment. Let it sit in-world a few seconds, then stop it (Ctrl+C — the process scheduler's graceful- shutdown path already sends a real logoff). ### Expected wire sends on first connect - One `SetSingleCharacterOption (0x0005)` for `IgnoreAllegianceRequests` (id `0x01`) — immediately, no batching. - One `SetSingleCharacterOption (0x0005)` for `ListenToTradeChat` (id `0x24`) — immediately, and ACE's handler additionally joins the Turbine trade-chat room server-side for this session (observable via ACE's own Turbine-chat membership logging if available). - **No** `SetSingleCharacterOption (0x0005)` for `SalvageMultiple` (id `0x22`) — it is batched, not auto-save, so `TrySetOption` only marks the module dirty and never sends `0x0005` for it. The single `SetCharacterOptions (0x01A1)` blob flush is the ONLY wire evidence of this id changing (OP7's diff-and-send calls the explicit `SaveOptions` verb once, after every declared id has been diffed, never interleaved — so this same `0x01A1` also carries whatever the loop already wrote locally for `IgnoreAllegianceRequests`/`ListenToTradeChat`, even though those two ALSO got their own immediate `0x0005`). - **No other option bits change.** ACE's stored `CharacterOptions1`/ `CharacterOptions2` for every UNDECLARED id stay exactly what they were before the run. - **No 0x01A1 blob before the client's own `GameActionLoginComplete`.** If ACE logs a refusal ("SetCharacterOptions received before FirstEnterWorldDone" or equivalent), that is a real OP7 defect — the diff-and-send is contracted to run only after both LoginComplete has been sent AND a real `PlayerDescription` has seeded local state (`HeadlessCharacterOptionsSeeder`'s own two-precondition latch). ### Expected silence on reconnect 4. **Run the SAME config a second time** (a fresh process, or the process's own reconnect path if it fires) without changing anything on the ACE side in between. Expect **zero** `0x0005`/`0x01A1` sends for the three declared ids — the fresh `PlayerDescription` now echoes back exactly what the first run persisted, so the declared-vs-actual diff finds nothing (idempotent by construction: a bot that already has what it wants sends nothing, matching retail's own "re-set to the current value produces nothing" rule — set-character-options-wire.md §3.1/§3.5). 5. **Flip one declared value** in the config (e.g. `SalvageMultiple` back to `false`) and run again. `SalvageMultiple` is batched, so expect exactly ONE `0x01A1` blob flush and **no** `0x0005` for it — the other two declared ids, still matching, produce nothing at all (not even the blob). ### What to report - Any declared id that does NOT persist across a fresh reconnect (ACE either rejected it silently, or the diff-and-send never actually ran). - Any UNDECLARED id whose stored value changed — a sign the blob echoed something it shouldn't have (wire research §5.3's "echo real values, never zero them" rule, or a stale/incorrect snapshot read). - Any 0x0005/0x01A1 send observed BEFORE the client's own LoginComplete action on the wire (a WireMCP capture on loopback `127.0.0.1:9000` settles this precisely if ACE's own log line is ambiguous). - Whether `ListenToTradeChat` actually joined the Turbine trade room server-side (not just the stored bit) — the one declared id in the sample config with a real behavioral consumer. ### Explicitly NOT in scope for this gate - Every other tier-1/tier-2 option name — the three above exercise both wire paths (auto-save `0x0005`-only and batched `0x0005`+`0x01A1`); the remaining 23 names share the same two code paths and are already covered by the id-ascending completeness assertions in `CharacterOptionTableTests` (OP1) and the schema tests here (OP7). - Any presentation-only (tier-3) option — `HeadlessConfigurationLoader` refuses to load a config that declares one; there is nothing to run. - The graphical Options panel's own Character tab — OP4. ### OP7 gate result — PASSED (coordinator, 2026-08-11) Three live runs against local ACE (`127.0.0.1:9000`, `+Acdream`, `lifecycle-smoke` policy, temporary seeder diagnostics since reverted): - **Run 1 (declared: IgnoreAllegianceRequests=true, ListenToTradeChat=true, SalvageMultiple=true):** the seeder sent exactly two `SetSingleOption` diffs (`IgnoreAllegianceRequests` false→true auto-save; `SalvageMultiple` false→true batched) plus ONE `SaveOptions (0x01A1)` flush; `ListenToTradeChat` was already true and sent nothing. After the policy's own portal AND a genuine mid-run reconnect (fresh ACE `PlayerDescription`), all three reported `already ..., no send` — proving ACE accepted and persisted both wire paths within the session. - **Run 3 (fresh process, SalvageMultiple flipped to false):** the fresh seed echoed `SalvageMultiple=true` from run 1 — a value ONLY the run-1 `0x01A1` blob could have carried (batched id, never `0x0005`), which is the cross-process ACE-persistence proof. The flip sent one `SetSingleOption` + one `SaveOptions` and nothing for the other two ids; post-portal and post-reconnect re-diffs were silent. - Both runs: no pre-LoginComplete sends observed, no ACE refusals, graceful zero-code exits, `disposed` with converged runtime and zero leases. Pre-existing noise unrelated to OP7, noted for the record: an unmapped `[weenie-error] code=0x051D`, and the K4 resource envelope's `handle-count` violation (the `k4-linux-30-session` Linux profile evaluated on Windows).