108 lines
7.3 KiB
Markdown
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.
|