Adds an optional, strict `characterOptions` block to the headless bot config (D8): keys are exactly the lane-B tier-1 (22) + tier-2 (4) bot-declarable CharacterOptionId enum-member spellings; an unknown/out-of-tier name fails config load naming the offending key, before it can ever reach the wire. HeadlessCharacterOptionsSeeder diffs declared-vs-actual once both of ACE's real preconditions are known true — GameActionLoginComplete sent (the FirstEnterWorldDone gate SetCharacterOptions 0x01A1 needs) and a real PlayerDescription has seeded RuntimeCharacterOptionsState (HasServerSeed) — learned from whichever of two hooks lands second. Every differing id routes through OP1's shared IRuntimeCharacterCommands seam: auto-save ids send SetSingleOption (0x0005) immediately; batched ids also call SetSingleOption (which only dirties the module) followed by exactly one SaveOptions flush after the whole declared set has been walked. Idempotent on reconnect by construction — no dedupe latch, the diff simply finds nothing once the server agrees. RuntimeLiveEntitySessionController gains a passive onLoginCompleteSent observation hook (additive only, never changes when/whether it sends) so the headless host can learn ACE's gate opened from any of its own two internal send sites; the third site (direct first-entry completion) is already owned by HeadlessSessionHost itself. All wiring is synchronous delegate calls on Runtime's one dedicated update thread — no new async/Task continuation, honoring #368. Tests: schema (valid parse, unknown/tier-3 name rejected naming the key, non-bool rejected, empty/absent no-op), the diff engine against a fake IRuntimeCharacterCommands (nothing-to-send, auto-save-only, batched-with- flush, mixed ordering, reconnect idempotence), and two wiring integration tests — one dispatching a real PlayerDescription game event end-to-end to a captured wire action, one proving the send lands on the same dedicated thread every Tick runs on. Full solution suite: 12,935 passed / 4 skipped / 0 failed (+17 over baseline 12,918/4/0). No register row: the characterOptions bot-config surface is acdream- native tooling over retail's own wire mechanisms (both already ported by OP1), not a retail UI port with a divergence to record. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
237 lines
12 KiB
Markdown
237 lines
12 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.
|
|
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 any edge or
|
|
corner.** Behaves like the CH6 chat floaties: draggable, resizable on
|
|
all four edges, remembers its geometry across a close/reopen within
|
|
the same session.
|
|
|
|
### 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.**
|
|
14. **Report Abuse.** Click it. Same shape as Urgent Assistance, 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).
|
|
**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.
|
|
|
|
### 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).
|
|
- Any exception/crash on any of the 15 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).
|
|
- One `SetSingleCharacterOption (0x0005)` for `SalvageMultiple` (id
|
|
`0x22`) followed by exactly one `SetCharacterOptions (0x01A1)` blob
|
|
flush — `SalvageMultiple` is batched (dirties the module; OP7's
|
|
diff-and-send calls the explicit `SaveOptions` verb once, after every
|
|
declared id has been diffed, never interleaved).
|
|
- **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. Expect exactly ONE `0x0005` +
|
|
`0x01A1` pair for that id only — the other two, still matching,
|
|
produce nothing.
|
|
|
|
### 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.
|