Commit graph

23 commits

Author SHA1 Message Date
alex
8f3106ae10 campaign: checkpoint research ABI reconstruction 2026-09-09 23:38:44 -04:00
alex
51243ef8b3 lane BS merged: the per-history residue is an absent Colonize task; BR's generator row scoped to encounter-free turns 2026-09-09 03:14:28 -04:00
alex
05db2b30af resolver: the residue is fleet VISIT ORDER not id allocation, and the block records it; rule 26 gains (d), my 26(c) over-read corrected 2026-09-09 02:19:33 -04:00
alex
fca5c6cd56 lane BQ merged: load-boundary scope on AR's pair, peaceful auto-resolve is not combat-free, rule 26(c) marked as a screen not a decision procedure 2026-09-09 02:15:42 -04:00
alex
10ddf09cb4 lane BP merged: C-exact false on a rich turn, residue is one fleet-id transposition; rule 26(c) qualified 2026-09-09 02:02:10 -04:00
alex
1c30a95c9d lane AP merged: P is two species branches not three sites; three gates closed; rule 23 gains predict-at-the-site 2026-09-09 01:36:22 -04:00
alex
0029d022f2 resolver: seed exposure is a predicate on the pre-turn save; rule 26 gains corollary (c) 2026-09-09 00:56:38 -04:00
alex
5e4c27772d lane AS merged: the detection roll fires, spies2 closed, rule 28 grown to six practices 2026-09-09 00:14:24 -04:00
alex
9eaec9ba2c resolution: V2 vindicated, my Rung B claim corrected, AC's probes=11 caveat; rule 28 on gated negatives 2026-09-08 21:43:00 -04:00
alex
67cd78b3f5 rule 24 amended: exclude local build dirs from the transfer too; a moved denominator means a broken build 2026-09-08 20:34:00 -04:00
alex
6e34c72906 rule 27: a ratchet is meant to break when the corpus grows; scope controls by explicit list 2026-09-08 19:33:16 -04:00
alex
af67d31394 resolution: byte-match survives as C-exact/C-set; tie set is a draw support not equal cost; rule 26 on self-consistent controls 2026-09-08 18:38:37 -04:00
alex
9d722815a5 rule 25: commit with a pathspec - the shared index is why staging by path was not enough 2026-09-08 18:37:07 -04:00
alex
2d6184888f standalone 209 -> 124 (closed 85); rule 24: never reuse a build directory across trees 2026-09-08 17:01:57 -04:00
alex
962cdb6981 tools: live screen wall + contact sheet for the five lab guests
The lab went from one Windows guest to five and there was no way to see what
they were all doing without issuing a QEMU screendump per guest by hand.

- tools/vmwatch.py: always-on HTTP service serving an auto-refreshing wall of
  live guest screens. Runs on spicy as vmwatch.service; browse it at
  http://192.168.3.201:8140/. Click a tile for that guest full size. Guests are
  discovered from /etc/pve/qemu-server by matching sots-re, so clones appear and
  vanish on their own. A stopped, paused or unreachable guest gets a labelled
  placeholder tile carrying the monitor's own error, never a broken image or a
  500. Python 3 stdlib only.
- tools/vmwatch-install.sh: install/update/uninstall the unit on the host.
- tools/vmshot.py: one-shot contact sheet, and --one <id> for a full-size grab.
  Pulls frames from the vmwatch service when it is up (0.5s) and falls back to
  ssh + qm monitor when it is not (4s).
- guides/lab-screen-wall.md: how to use both, and why.

Capture goes over each guest's QMP socket rather than forking qm: qm is a Perl
program, and one fork per guest per tick cost ~90% of a host core and a 728 MB
cgroup peak. Direct QMP is 0.33 CPU-seconds per 88s and 23 MB RSS. QEMU 11 here
dumps PNG natively; the fallback PPM encoder was verified pixel-identical to
QEMU's own on a real framebuffer.

Read-only throughout: screendump does not perturb the guest (method-rule 19),
so reading VM 140's screen is not an experiment and does not take its lock.
2026-09-08 16:53:04 -04:00
alex
63347c7c17 rule 23: a live-verified module can still be wrong - thin coverage is how; float literals are widened floats 2026-09-08 15:57:09 -04:00
alex
2b7758123f rules 21-22: lanes never touch the shared worktree; union-resolve is not concatenation. VM released 2026-09-08 15:34:12 -04:00
alex
8b6c946614 rules 19-20: the instrument can perturb the measurement; a count cannot separate did-not-fire from fired-and-found-nothing 2026-09-08 13:56:06 -04:00
alex
351acc07c3 rules: 18 - measure first, the lab exists; static reading explains what you measured 2026-09-08 11:20:30 -04:00
alex
1d7c6ef63e board+rules: lane J combat resolver; inlined draws invisible to call-graph sweeps; Ghidra sizes untrustworthy 2026-09-08 09:38:55 -04:00
alex
136f98b564 guides: the 15 earned method rules, each traced to the finding that produced it 2026-09-08 08:29:20 -04:00
alex
f965c8c769 lane S: std::string is 0x1c binary-wide; ObservedTech element fully mapped
Settles the 0x18-vs-0x1c contradiction lane X raised. 0x1c is right, everywhere,
and there is exactly one std::string instantiation in this binary:
_Bx@0, _Mysize@0x10, _Myres@0x14, _Alval@0x18.

ObservedTech+0x24 is that string's trailing empty-allocator word, not the
unaccounted data field it was read as. Three complete enumerations of the element
each skip it: ObservedTech::Write 0x00817cf0, the ctor 0x008562a0, and the copy
ctor inlined at 0x0079a184. Generalised with a new scanner, tools/strfootprint.py,
which recovers every (base, disp, tag) handed to the Mars::Stream string helpers:
65 std::string members off a non-stack base across every serializer in the exe,
ZERO with a sibling member inside the 0x1c span, and 51 of the 52 measurable
inter-member gaps exactly 0x1c. Corroborated by the vector<string> walk stride
(add esi,0x1c @0x00699c29), PostEvent's by-value strings at [ebp+8]/[ebp+0x24]
with RET 0x4c, and MoraleEvent 0x50 = name@0x34 + 0x1c.

Blast radius: zero recovered struct tables were wrong. Every string-bearing layout
already used 0x1c spans and 0x1c gaps -- ServerPlayer::pswd @0x2dc..0x2f7, the row
flagged for re-checking, included. Only prose carried the 0x18 number: the
loader-prototypes conventions line, the GlobalConst_ParseString prototype, and the
ObservedTech element table. struct-recovery S0 additionally had _Mysize/_Myres
transposed (size@0x14, res@0x18) while every table in the same file used the
correct offsets; fixed.

ObservedTech's four on-disk fields are now mapped rather than guessed, by reading
the serializer as lane X suggested: +0x04 uint16 otnF, +0x06 uint16 otnL, +0x08
bool odet (ONE BYTE, WriteBool), +0x0c std::string otch (0x1c), +0x28 int owith
= 0x2c exactly. That matches save_reader.py's on-disk order already. Game::
ObservedWeapon (0x00817bc0/0x00817b10) is the same element with tag owep.

Oracles unaffected and re-run: save_reader 36/36 and --strict exit 0 on all three
real saves; state_checksum 38 tests OK, coverage PROVED byte-for-byte on turn1 and
turn3. sots-engine wip/strings 32d3e36 syncs the header and corrects two stale
"unpinned" comments: clean_room_check OK, host ctest 33/33.

Standing rule this produced: never size a struct member from the offsets the code
touches. This build's STL puts the empty allocator LAST in both string (0x1c) and
vector (0x10), and an empty allocator is never loaded or stored, so a touch-scan
undercounts by exactly 4 every time. Size from an enumeration instead.
2026-09-08 05:09:33 -04:00
alex
5d877d373b guides: RE how-to for 2000s MSVC/DX9 games (98 sources); queue replay-checksum, class recovery, apitrace/TTD 2026-09-07 18:05:58 -04:00