Skip to content

Filter verbs: confirm yes|no + appraise id:<guid> - #14

Draft
Awful-Waffle-Rofl wants to merge 1 commit into
masterfrom
feature/confirm-verb
Draft

Filter verbs: confirm yes|no + appraise id:<guid>#14
Awful-Waffle-Rofl wants to merge 1 commit into
masterfrom
feature/confirm-verb

Conversation

@Awful-Waffle-Rofl

Copy link
Copy Markdown
Owner

Why

Two gaps found while vetting the ACE test harness (ACE draft PR #287) blocked automated testing outright.

1. Nothing could answer a server confirmation dialog. An ACE server asks a yes/no question with GameEventConfirmationRequest, game event 0x0274 inside the 0xF7B0 game-event container. The only thing that answers it is GameActionConfirmationResponse, game action 0x0275, and only the client can send that: in the ACE source, ConfirmationManager.HandleResponse has no caller for a normal (non-timeout) response other than that game action. Tinkering, augmentations, skill and attribute changes, allegiance and fellowship offers all pass through it. Worse, a harness could not even see that a question was outstanding - the only record was the ACE server's own log, which a remote or CI run does not have.

2. appraise could not reach inventory. /usewith <sourceGuid> works server-side and drives real tinkering, but it takes its target from the server's last-appraised object. The filter's appraise resolves names through the world object list, so it cannot see an item in the player's pack. That forced rigs into a fragile race - spawn a fresh item, poll itemkeys until the guid the audit reported appeared, fire immediately - and an item that had already been tinkered could never be re-aimed at all.

What this adds

verb effect
confirm yes / confirm y click Yes on the outstanding confirmation
confirm no / confirm n click No
confirm yes at:X,Y click one explicit client-relative point instead of the computed button
confirm yes force click even when nothing is outstanding
appraise id:<guid> appraise one explicit object id, decimal or 0x hex, no resolution and no ambiguity

Both are reachable over the launcher channel and typed in game as /tf ..., and both are in /tf help.

The mechanism for 0x0275, and how it was determined

Decal cannot send a raw or arbitrary network message. Established by reflection over the entire Decal surface (Decal 3.0, Decal.Adapter 2.9.8.3), not by assumption:

  • Decal.Adapter.Wrappers.HooksWrapper - what CoreManager.Current.Actions is, and what everything outbound in this filter already goes through - is a fixed list of client-function hooks: UseItem, RequestId, CastSpell, TradeAccept, SpellTabAdd and so on. There is no Send, no SendMessage, and nothing confirmation related.
  • Decal.Interop.Net.INetworkFilter2 exposes only DispatchServer / DispatchClient / Initialize / Terminate. Those are inbound observation callbacks; there is no outbound injection point.
  • Decal.Interop.Net.INetService exposes only Decal / Filter / FilterVB / Hooks.
  • Decal.Interop.Net.IMessageFactory.CreateMessage builds a message object for parsing an observed buffer. It puts nothing on the wire.
  • C:\Program Files (x86)\Decal 3.0\messages.xml, the authority for message shapes, marks exactly three messages direction="outbound": F657, F7B1, F7DE. The F7B1 (ordered game action) switch lists actions 0x0005, 0x0010, 0x0019, 0x001A, 0x001B, 0x0036, 0x0044-0x0048, 0x004A, 0x00CD, 0x019C, 0x019D, 0x01A1, 0x01E3, 0x01E4 - 0x0275 is not among them, so Decal has no schema for the response either.

Option (i) in the brief is therefore closed, with source evidence. Option (ii) is what shipped: the answer comes from the client itself, by operating the client's own confirmation panel. This filter already does exactly that for the client's other yes/no boxes - FastQuit answers the quit box with PostMessageTools.ClickYes, AutoRetryLogin dismisses login boxes with ClickOK. Those post WM_MOUSEMOVE / WM_LBUTTONDOWN / WM_LBUTTONUP at a position derived from the client window rect, at three vertical offsets that were tuned against real clients (see the comments in Shared\PostMessageTools.cs: "800x600 +32 works, +33 does not ... 1600x1200 +31 works"). ClickNo is added here as the mirror of ClickYes, at the +80 x offset that the sibling KeyTestApp\PostMsgs.cs has always used for its own ClickNo.

With nothing outstanding, a bare confirm yes deliberately does nothing: a stray click in the 3D window selects or attacks whatever is under it, which would silently corrupt the test that asked for the confirmation. force is the explicit override.

Detecting an outstanding confirmation

Confirmer watches ServerDispatch for 0xF7B0 and switches on the event id. Decal's messages.xml parses both cases already, so no raw byte work is needed:

  • 0x0274 -> type (the ConfirmationType), number (the context id), text
  • 0x0276 -> unknown (the type; ACE writes the type there), number (the context)

State is published three ways:

  • Heartbeat game_<pid>.txt, file version bumped 1.5 -> 1.6, compat prefix still "1". New fields ConfirmationState (none / outstanding / answered / aborted / expired), ConfirmationType, ConfirmationContext, ConfirmationText (flattened to one line, capped at 200 chars so it cannot corrupt the line-oriented file), ConfirmationAnswer. Read back with the same optional-read helpers the 1.5 fields use, so an older heartbeat still parses during a rolling upgrade.
  • dumpstate gains a confirmation section with the full untruncated text and the request timestamp.
  • chatlog_<pid>.jsonl gains ConfirmationRequest, ConfirmationAnswer and ConfirmationDone records, all with "source": "confirmation". The answer record names the confirmationType, context and text of the dialog it answered, so a test can prove which dialog it answered rather than assuming.

A request that is never answered and whose abort is missed expires locally after 30 s (ConfirmationManager.confirmationTimeout), so a polling test cannot hang on a stale outstanding.

Reading the outcome: silence is success. ACE sends GameEventConfirmationDone only from ConfirmationManager.EnqueueAbort, i.e. the 30 second timeout - never after a normal answer. So an answer followed by silence and then the effect means the click landed; an answer followed by ConfirmationDone (state aborted, usually with "You waited too long to answer the question!") means it did not.

Verified

  • MSBuild.exe ThwargFilter.csproj /t:Rebuild /p:Configuration=Debug "/p:SolutionDir=C:\Users\danie\source\repos\ThwargLauncher\ThwargLauncher\" - clean, 0 errors. Only pre-existing warnings, both in files this PR does not touch.
  • MSBuild.exe ThwargLauncher.sln /t:Build /p:Configuration=Debug - whole solution clean, 0 errors.
  • ThwargFilter.dll smoke-loads under C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe (image runtime v2.0.50727).
  • tools\confirm-verb-smoke.ps1 (new, built on the existing tools\filter-smoke.ps1 fixture): 24 of 24 checks pass. It covers the heartbeat contract, the cold state machine, the dumpstate section, guid parsing for appraise id: (signed decimal, unsigned decimal above int.MaxValue, 0x hex below and above 0x7FFFFFFF, and rejection of garbage), at:X,Y parsing, and the launcher command routing end to end - proved by the filter log line the confirm branch emits, with a control showing that an unrouted verb instead falls through to the executor and fails, so the routing result is not vacuous.
  • CLR 2.0 / x86 constraints respected: no var, no LINQ, no async; the new file is listed in the csproj's explicit <Compile> list.
  • No em dashes or en dashes anywhere in the diff (scanned with Select-String -Pattern "[\u2013\u2014]").

NOT verified, pending deploy

Stated plainly rather than implied away:

  1. That the click answers the dialog at all. The ClickYes / ClickNo offsets were tuned against the client's own message boxes (quit, login). Whether the server-driven confirmation panel (0x0274) is drawn at the same screen position is a hypothesis, not a measured fact. It cannot be settled without a live client.
  2. That the observation hook fires. The 0x0274 / 0x0276 field names come from Decal's messages.xml and the field order from the ACE source; both are read evidence, but no live message has been observed through this code.
  3. That appraise id: moves the server's last-appraised object. The parsing is unit-tested; Actions.RequestId on an inventory guid is not.

Nothing here was deployed. C:\Program Files (x86)\Thwargle Games\ThwargLauncher\ was not touched.

Deploy and live-verify checklist

For whoever deploys (needs a client restart, so schedule it):

  1. Copy the built ThwargFilter.dll into C:\Program Files (x86)\Thwargle Games\ThwargLauncher\ and restart the client.
  2. Heartbeat version. Confirm game_<pid>.txt reads FileVersion:1.6 and that the launcher still lists the game (it must, the compat prefix is unchanged).
  3. Detection. Trigger any confirmation - /augmentation or a tinker attempt is easy - and confirm within 30 s that:
    • game_<pid>.txt shows ConfirmationState:outstanding with a plausible ConfirmationType, ConfirmationContext and ConfirmationText;
    • chatlog_<pid>.jsonl gained a "source":"confirmation", "type":"ConfirmationRequest" line.
      If detection fails but the dialog is visible on screen, the fault is the 0x0274 parse, not the click.
  4. Answering. With the dialog up, send confirm yes. Expect a ConfirmationAnswer record with "outcome":"clicked" naming that dialog, and then the action to complete.
  5. The negative that proves it. Watch for ~35 s. If ConfirmationDone arrives and state goes aborted (usually with "You waited too long to answer the question!"), the click missed. Then:
    • re-run with confirm yes force while no dialog is up to confirm clicks are being posted at all;
    • note the actual Yes button position on screen and re-run with confirm yes at:X,Y (client-window-relative pixels);
    • if at: works, fold the corrected offsets into ClickYes / ClickNo in Shared\PostMessageTools.cs as a follow-up.
  6. confirm no. Repeat step 4 with confirm no on a cancellable dialog and confirm the action does not happen. The No button's +80 offset is the least evidenced constant in the change.
  7. appraise id:. Put a known item in the pack, read its guid from itemkeys, send appraise id:0x<guid>, then run the ACE admin command that consumes the last-appraised object (/usewith <sourceGuid> against it) and confirm it hit the intended item. Then re-aim at the same item a second time - that is the case the old race could never do.
  8. Once live-verified, update the "NOT verified" section of TESTING_CHANNEL.md section 14 with what was actually measured.

Draft on purpose. Do not merge before step 4 and step 7 have been run against a live client.

Two gaps blocked automated testing of ACE server actions that ask a question
or that act on the last-appraised object.

confirm yes|no
--------------
An ACE server asks a yes/no question with GameEventConfirmationRequest (game
event 0x0274 inside the 0xF7B0 container). Only GameActionConfirmationResponse
(game action 0x0275) answers it, and only the client can send that: in the ACE
source, ConfirmationManager.HandleResponse has no caller for a normal response
other than that game action. So a harness could neither answer a confirmation
nor even see that one was outstanding.

Decal cannot send a raw network message. Verified by reflection over the whole
Decal surface (Decal 3.0, Decal.Adapter 2.9.8.3): HooksWrapper is a fixed list
of client-function hooks with no send; INetworkFilter2 exposes only inbound
DispatchServer/DispatchClient; INetService exposes only Decal/Filter/FilterVB/
Hooks; IMessageFactory.CreateMessage parses an observed buffer rather than
sending; and messages.xml defines only F657, F7B1 and F7DE as outbound, with
0x0275 absent from the F7B1 switch.

The answer therefore comes from the client itself, by posting a mouse click at
the confirmation panel's Yes or No button - the same mechanism FastQuit already
uses for the quit box and AutoRetryLogin for login boxes. ClickNo is added to
PostMessageTools as the mirror of the existing ClickYes, at the +80 x offset the
sibling KeyTestApp/PostMsgs.cs already uses. Whether the server-driven panel sits
at the same screen position as those client-local boxes is unverified until this
runs against a live client, so the verb also accepts "at:X,Y" to click an
explicit point and "force" to click with nothing outstanding, which separates a
detection failure from a missed click.

Detection: Confirmer watches ServerDispatch for events 0x0274 and 0x0276 and
publishes the outstanding request three ways - heartbeat fields (file version
1.6, read optionally so older parsers still work), a "confirmation" section in
the dumpstate snapshot, and chatlog records with source "confirmation". The
answer record names the confirmation type, context and text it answered, so a
test can prove which dialog it answered.

ACE only sends ConfirmationDone on the 30 second abort, never after a normal
answer, so ConfirmationDone arriving after an answer is direct evidence the
click did not land. That is documented as the pass/fail rule.

appraise id:<guid>
------------------
The existing appraise resolves names through the world object list, so it cannot
reach an item in the player's pack. That forced rigs to race a freshly spawned
item and made an already-acted-on item impossible to re-aim. The id: form skips
resolution and sends the identify request for the guid given, accepting decimal,
unsigned decimal and 0x hex (guids are unsigned on the wire but Decal's object id
is a signed Int32, so all three spellings of the same object parse).

Verification
------------
Solution and project build clean. tools/confirm-verb-smoke.ps1 covers routing,
argument parsing, guid parsing, the state machine and the chatlog records with
no live client, and passes 24 of 24 checks. The click landing on the panel and
the server accepting the answer are NOT verified and cannot be without a live
client; the PR carries a deploy-and-live-verify checklist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TmAUeyy5smytFsieYapeuK
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant