sots-engine/docs/W2-predictions.md

108 lines
7.3 KiB
Markdown

# W2 — predictions, written before any run
Lane W2, 2026-09-08. Committed **before** the VM runs and before the shim build (method rule 2).
## Part 1 — multiplayer Tier 0
Restating lane G2's `multiplayer-gamespy.md` §7 as the things this lane will actually observe, and
where my expectation differs from G2's.
Setup: one Win10 guest (VM140), GOG 1.8.1, `hooks=off` (rule 19 — the instrument is removed for a
test whose whole point is "does the shipped game do this"). Capture is `tcpdump` on **spicy's
`tap140i0`**, i.e. outside the guest, so it cannot perturb the guest and it sees every packet that
leaves the VM. It does **not** see loopback traffic between the two instances — that is a known and
accepted blind spot, and it is the right trade: the predictions that matter (P1's "no GameSpy
traffic", P2's DNS query) are all about traffic that *leaves*.
Pre-run fact established before the test, and it matters for P2: from the guest,
`swordots.available.gamespy.com` returns **NXDOMAIN** (`[System.Net.Dns]::GetHostAddresses` throws
"No such host is known") while `www.google.com` resolves. So there is no stale wildcard record, and
the availability check will take exactly the `gethostbyname == NULL` arm G2 read at `0x0040a0f0`.
That makes P2 a test of the *fail-open* reading specifically.
**W2-P5 (G2's P5) — `/concurrent` reaches the main menu.** A second `Sword of the Stars.exe`
started with `/concurrent` while the first is running exists as its own process with its own window,
rather than foregrounding the first and exiting. *Falsified if* the second process exits within
~20 s and only one PID remains.
Confidence: high. The `CreateMutexA` / `ERROR_ALREADY_EXISTS` / argv-compare chain is read out of
the binary and there is nothing conditional about it.
**W2-P1 (G2's P1) — a direct join succeeds with no GameSpy traffic.** With instance A hosting a LAN
multiplayer game and instance B started as
`"Sword of the Stars.exe" /concurrent /join 127.0.0.1:3369`, B reaches the multiplayer lobby and
appears in a slot on A. Over the whole run the tap capture shows **zero** packets to UDP 27900, UDP
27901, TCP 28910, TCP 6667 and **zero** DNS queries for any `*.gamespy.com` name after `/join` is
parsed. *Falsified if* any of those appear on the join path, or the join fails with a network error
while both instances are up.
Confidence: medium-high on the "no GameSpy traffic" half (static reading is clear), medium on the
"join succeeds" half — see G2's own (a)/(b)/(c) caveats, to which I add a fourth: **the two
instances share `C:\SOTS`**, one `Profiles` directory, one `sots.ini` and one `SavedGames`. Nothing
in G2's reading covers what the host does when a second process opens the same profile. If the join
fails, that is the first thing to rule out before concluding anything about the protocol.
**W2-P2 (G2's P2) — the availability check fails open.** On entering *Join Multi-Player*, at most
one DNS query for `swordots.available.gamespy.com` leaves the guest, it is answered NXDOMAIN, and
the UI does **not** show `MATCHINGSERVICE_UNSUPPORTED` / "Online support … is no longer available".
*Falsified if* that dialog appears.
I differ from G2 here in one respect: G2 predicts "exactly one DNS query". The Windows resolver
negative-caches, and the SDK's own retry is a *socket* retry not a DNS retry, so I expect **one or
two** queries and will not read two as a falsification of anything.
**W2-P3 (G2's P3) — LAN discovery.** B's *LAN* page lists A's game after a refresh. I rate this
**lower** than G2 does, for a reason G2 could not have known: the two instances are on the **same
host**, so the broadcast to `255.255.255.255:3369` and the QR2 socket are contending for one UDP
port on one machine. `SO_REUSEADDR`-less binds mean the second instance may fail to bind 3369 at
all. *Falsified if* the LAN list stays empty while a manual join to the same address succeeds — and
I expect that outcome to be **more likely than not**, and to be an artifact of the single-guest
setup rather than evidence about the mechanism.
**W2-P4 (G2's P4) — self-hosted master server.** Not attempted this lane. Tier 3 is a container
deployment plus a DNS redirect; it is a full lane's work, it is not on the critical path to
*playing*, and spending VM time on it before Tier 0 is proved would be the wrong order. Reported as
not-run, not as unknown.
**New — W2-P6: the dedicated server.** `sots_server.exe` and `Dedicated Server Launchpad.exe` are
both present in `C:\SOTS`. The forum report G2 quotes says the dedicated server does not work. If
Tier 0 via two clients is obstructed by the shared-install problem, this is the fallback host; if
Tier 0 works, running it is a cheap bonus data point on a reproducible community bug. No prediction
is offered — I have not read it, and G2 did not either.
## Part 2 — `ModCount` watchpoint
Lane A2's prediction is adopted unchanged (`alliance-mask-and-modcount.md` §3): **exactly 12 hits**
on a 4-byte write watchpoint at `(char*)S + 8`, two at `0x007dc6f0` and `0x007d92ca`, ten with a
return address in `StrategySim::ApplyTurnCommandBatch` `0x0088f9b0` or its callers, none at
`0x007b9e20`, and **all before `ProcessTurn` is entered**. Falsifiers (a)-(d) as A2 wrote them.
**My own addition, and it is a rule-19 addition.** A hardware watchpoint is an instrument too, and a
more invasive one than a MinHook detour: it single-steps nothing but it does take an exception on
every write, and the handler runs in the game's thread. So the run is only trustworthy if the
autosave it produces is **byte-identical to the `hooks=off` autosave from the same input save**. I
will take that control. If the watchpoint perturbs the turn, the hit list is still useful as an
enumeration of *writers* but the *count* is not a measurement of the un-instrumented game, and I
will say so rather than reporting 12-or-not as if it were.
**Prediction on the control itself:** the watchpoint is byte-neutral. A `#DB` on a data write is
delivered after the store retires, the handler restores state exactly, and there is no code
modification anywhere — unlike a detour, which is what §2 of `tail-probes.md` caught. If this
control *fails*, that is the more interesting result and it says something about the perturbation
mechanism lane H left undetermined.
## Part 3 — `Player.Status` and `TShn`
Same instrument, different addresses; both are write watchpoints with a per-hit EIP + return-address
record.
- **`Player.Status`.** Prediction: **at least one writer after tail phase 31 and before the
autosave**, at an EIP outside the tail-phase code, writing 4. The regression is that the phase
writes 1 and the file carries 4. Falsified if the only write of 4 happens *inside* phase 31, which
would mean the phase catalog's attribution of phase 31 is wrong rather than a later writer
existing.
- **`TShn` (`player+0x274` map).** E3 named Spica vs Bismol as the discriminating pair and proved
the gate is not `AFlags`. Prediction: the writer is a **single** site reached once per qualifying
system, and the discriminant is a property of the *system* rather than of the player — because
a player-level gate would have moved `TShn` for Spica too. Falsified if the writes come from two
or more EIPs, or if the EIP is inside a per-player loop with no system in scope.
Both of these are lower priority than `ModCount` because `ModCount`'s prediction is numeric and
theirs is not.