Compare commits

..

2 commits

Author SHA1 Message Date
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
ef7bbf9f99 SD: addresses for the ship-design composer
Six entries: the composer 0x006ad700 and its only caller 0x006ae620, the
one-line random-element helper 0x00691e90 that IS a live draw site lane PAR's
inventory omitted, the 0x124-stride three-section design context (which agrees
with lane D2's independent read of the same array at the stat aggregator), and
two ShipSectionDef offsets -- the weapon-bank vector and the section name the
composer's PointDefence test reads.

Scratch-validated: 1,234 -> 1,240 entries, no duplicate name and no
same-address-different-name.
2026-09-08 20:30:28 -04:00
3 changed files with 70 additions and 0 deletions

View file

@ -386,3 +386,4 @@ Status flow: `backlog → in-progress → mapped → verified` (or `blocked`).
| ID declined the third target, correctly | meta | verified | high | 100% | 2026-09-08 | It did NOT run the cbtrap set: that needs an End Turn on VM140, **which rewrites the three oracle autosaves**, and lane CB had already judged it the weaker check next to RB's zero-residual ModCount = 24. **Confirmatory value did not justify putting the reference bytes at risk.** It stays cheap for anyone who backs up SavedGames first, and the exact hashes are recorded in its finding. VM140 released with the oracle verified intact and the game never launched |
| ID's falsified prediction, with the probe named | verify | backlog | — | 0% | 2026-09-08 | Its prediction that client counters restart at 0 on load is **falsified by a corpus save**: lane CB's reloaded run produced fleet **34**, not 18. Either something restores the counter (six IDMap/InitGame functions read, no such write found) or **the client makes one earlier unsaved allocation per turn** - the second fits everything and implies the id collision has already happened harmlessly in CB's run. One hook on 0x008b8ae0 logging (map, node, id, return address) separates them |
| LAB TRAP: cmd.exe eats parentheses in save names | meta | verified | high | 100% | 2026-09-08 | `ssh ... certutil -hashfile "...(Autosave).sav"` fails with **"Check the spelling"**, which reads exactly like a missing file. Lane ID's first attempt **reported the three oracle saves as absent, and they were never absent** - a false alarm about the campaign's most important bytes |
| MY ERROR: over-corrected rule 24, poisoned a remote build | meta | verified | high | 100% | 2026-09-08 | Implementing "fresh build dir per measurement" I **dropped `--exclude 'build*'` from the gate rsync**, which shipped a LOCAL `build-host` to CT111. A CMakeCache records **the absolute path it was created in**, so the remote build failed with "is different than the directory /home/alex/sots-engine where CMakeCache.txt was created" and **0% of 48 tests passed**. THE CORRECT FORM IS BOTH HALVES: exclude local build dirs from the transfer AND `rm -rf` the remote ones. **AND THE SIGNATURE TELLS YOU WHICH FAILURE YOU HAVE**: a real regression changes the PASS count (57/58); a broken configure changes the TOTAL (0% of 48 where healthy is 58). If the denominator moved, stop reading failures and fix the build. Re-gated clean: 58/58, shim OK. Rule 24 amended |

View file

@ -0,0 +1,53 @@
{
"_note": "Lane SD 2026-09-08 -- THE SHIP-DESIGN COMPOSER 0x006ad700, where lane PAR localised the AI's RNG variance. Read from the instruction stream swept to the next function start (rule 17: the body ends at 0x006ae61a with int3 padding to 0x006ae620, and Ghidra's 3864-byte size is right here), plus a live sub-bracket on VM145. Nine of the AI turn's live draw sites are in this one function, including the only loop-carried one.",
"entries": [
{
"name": "AIComposeShipBlueprint",
"addr": "0x006ad700",
"convention": "custom",
"prototype": "int (ECX: StrategyAIAgent* agent, EDX: SectionContext parts[3], STACK: DesignRequest* req, STACK: char costOnly) /* ECX+EDX in registers, TWO stack arguments, and the CALLER cleans them (`add esp,0x8` at 0x006ae719) -- neither cdecl nor MSVC __fastcall, but the private convention MSVC gives a static function whose address never escapes. Returns 0 on success and 1..8 for eight distinct bail-outs: 1 over budget, 2 no hull candidate, 3 no command section, 4 no engine section, 5/6/7 a forced-tech lookup failed for request-flag bit 4/8/0x10, 8 no weapon matched a bank. `req` = {int hullSize; float budget; int role; uint flags}. `parts` is the same 0x124-stride three-section array lane D2 found on the other side of the pipeline at Game::ShipDesign::AggregateSectionStats: +0 ShipSectionDef*, +4.. one weapon id per weapon BANK, +0xcc the bank count (min(hull banks, 0x32)). `costOnly` non-zero returns right after pricing, having already drawn at sites A/B/C -- which is why one AI design request can cost two composer calls */",
"status": "verified",
"source": "findings/subsystems/ship-design-composer.md -- lane SD 2026-09-08; instruction-stream read of dumps/sots.exe 0x006ad700..0x006ae61a plus a live MinHook sub-bracket on VM145"
},
{
"name": "AIComposeShipDesign",
"addr": "0x006ae620",
"convention": "custom",
"prototype": "void (ECX: ?, EDX: StrategyAIAgent* agent, STACK: ..., DesignRequest* req, char costOnly) /* the composer's ONLY caller. Builds a list of 1..3 hull sizes from req.hullSize and request-flag bits 0 and 1 (bit 0 adds size-1, bit 1 adds size+1 when size<2), clears both bits, then calls AIComposeShipBlueprint once per size and BREAKS ON THE FIRST SUCCESS. So one design request costs 1..3 composer calls, and every failed attempt has already spent the RNG words it reached before failing. Three call sites: 0x006cdaa6 and 0x006cdb1c in 0x006cda40 (the real design pass) and 0x006b7ecb in 0x006b7e40, which passes costOnly=1 */",
"status": "verified",
"source": "findings/subsystems/ship-design-composer.md -- lane SD 2026-09-08; instruction-stream read of dumps/sots.exe"
},
{
"name": "AIPickRandomElement",
"addr": "0x00691e90",
"convention": "custom",
"prototype": "void* (EAX: int count, STACK: void** array) /* 0x1f bytes whole: `if (!count) return 0; return array[cl_RandRange(0, count-1)];`. The count arrives in EAX. This is a LIVE DRAW SITE that lane PAR's 23-row table does not list -- PAR measured it live at return address 0x00691ea0 (2 calls, 2 words on turn 1) without adding it to the static inventory, so the AI turn has 22 live sites, not 21. The composer calls it once, at 0x006ada61, to pick the command section */",
"status": "verified",
"source": "findings/subsystems/ship-design-composer.md -- lane SD 2026-09-08; instruction-stream read, and live in verify/results/shim/airng/par-t1-br-p3.txt"
},
{
"name": "AIShipSectionContext_stride",
"offset": "0x00000124",
"convention": "offset",
"prototype": "size -- one entry of the three-section design context array the composer fills and Game::ShipDesign::AggregateSectionStats 0x00826af0 consumes. +0x000 ShipSectionDef* (null = empty slot), +0x004..+0x0cb up to 0x32 weapon ids, ONE PER BANK, which is exactly the DGbnk2 list the design record carries on the wire (SHIP_DESIGN_RULES B1: one weapon per bank{} block, in file order); +0x0cc the bank count = min((def+0x320 - def+0x31c)/0x30, 0x32); +0x0d0..+0x11f up to 20 candidate weapon ids gathered for that section; +0x120 that candidate count, capped at 20 -- the cap the string \"AIComposeShipBlueprint: SectionBlueprint::MAX_OPTIONS\" names. Slot order in memory is mission, command, engine (verify/design-rules/SHIP_DESIGN_RULES.md 1), NOT the on-disk order. Live: on turn 1 a Human destroyer design gives bank counts 3 / 1 / 2 for DEExtendedRange / DECommand / DEFission",
"status": "verified",
"source": "findings/subsystems/ship-design-composer.md -- lane SD 2026-09-08; agrees with lane D2's independent read of the same array at the aggregator"
},
{
"name": "ShipSectionDef_off_Banks",
"offset": "0x0000031c",
"convention": "offset",
"prototype": "std::vector<Bank> begin -- weapon-bank descriptors on a Game::ShipSectionDef, stride 0x30, end pointer at +0x320. Bank+0x00 = turretclass index (0 = `standard`, the class 1,533 of the 3,721 shipped banks carry; 0xc and 0x17 are two others the composer's large-bank pass also accepts), Bank+0x04 = turretsize index (1 = `small`, 3 = `large`). The composer's only loop-carried draw fires once per (class 0, size 1) bank -- a small standard bank -- and its job is to overwrite a fraction of them with a point-defence weapon",
"status": "verified",
"source": "findings/subsystems/ship-design-composer.md -- lane SD 2026-09-08; instruction-stream read at 0x006adec4 and 0x006ae533, class/size naming cross-checked against verify/design-rules/SHIP_DESIGN_RULES.md 3's bank census"
},
{
"name": "ShipSectionDef_off_Name",
"offset": "0x000001b4",
"convention": "offset",
"prototype": "Mars::String -- the section's file name on a Game::ShipSectionDef; capacity word at +0x1c8, so the payload is the inline buffer when capacity < 0x10 and *(char**)(def+0x1b4) otherwise. The composer compares it case-insensitively against \"DEPointDefence\" and \"CRPointDefence\" at 0x006ae4e6/0x006ae4fd: a section whose name matches gets a point-defence weapon in EVERY qualifying bank instead of every D'-th one",
"status": "verified",
"source": "findings/subsystems/ship-design-composer.md -- lane SD 2026-09-08; instruction-stream read"
}
]
}

View file

@ -308,6 +308,22 @@ like nondeterminism in the engine).
So: **a fresh build directory per measurement**, or `rm -rf` the build tree before building. And when
a merged result does not reproduce a lane's own number, suspect the build before suspecting the lane.
**Implementation, both halves — I got this wrong once by doing only the second.** Exclude local build
directories from the transfer *and* remove the remote ones:
```sh
rsync -a --delete --exclude .git --exclude 'build*' ./ host:/path/ # do not ship local build dirs
ssh host 'cd /path && rm -rf build-host build-shim && cmake --preset host && …'
```
A `CMakeCache.txt` records the **absolute path it was created in**, so a local build directory copied
to another machine poisons the build there — `CMake Error: … is different than the directory … where
CMakeCache.txt was created`, and every test fails.
**The signature tells you which failure you have:** a real regression changes the *pass* count
(`57/58`); a broken configure changes the *total* (`0% of 48`, where a healthy run is 58). If the
denominator moved, stop reading the failures and fix the build.
## 25. Commit with a pathspec — the index is shared, staging by path is not enough
Four times in one session, a lane's `git commit` swallowed another lane's staged files in the shared