acdream/docs/research/2026-08-11-campaign-op-test-script.md
Erik 7e9f372ce8 docs: OP7 live bot-vs-ACE gate PASSED — slice CLOSED
Evidence appended to the test script: run-1 diff-and-send exact to
contract; mid-run reconnect idempotence; run-3 cross-process persistence
proof (the fresh seed echoed the blob-only SalvageMultiple value);
graceful converged exits; no refusals, no pre-LoginComplete sends.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 03:31:23 +02:00

319 lines
17 KiB
Markdown

# 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).