findings/subsystems/players-residual.md is the deliverable: every one of the 54
leaves on the reference pair and the 24 on pair 2 attributed to a named
mechanism, with the rung split. 53 of 54 are pass-through -- the block is
unmodelled, not mismodelled, and only player 32's Sav is a number we compute and
get wrong.
Three corrections to the record:
- T31's self-check compared its post-turn result against the pre-turn stored
value, so its 6-of-8 covered only the players whose limit never moves.
- the AI difficulty column IS recoverable from the save, by recomputing BnkEl
under both columns against the value the save carries; 1 AI + 1 non-AI on all
eleven corpus saves.
- BANKRUPTCY_PROTECTION_LIMIT_FACTOR is read fmul dword ptr, so it is a float32
in the image; the decimal disagrees at every max income divisible by ten and
the corpus' seven records land where the two agree (rule 23). formula-gaps Q1
addendum + ghidra/addresses.d/lane-pl.json (3 data entries, fragment validated
to a scratch path, no duplicate names).
verify/results/standalone/{status.json,report.txt} refreshed from a build of main
aabd8a3: the committed copy was stale at 131/55, the real number is 128/54.
T34 RecordObservedDesigns is now readable off the corpus and is NOT an intel
pass: a design's creator registers the design in its own odes, its weapons in
owep, and re-stamps otnL on its techs in otch -- odes on build, otch on creation.
Both are hypotheses until a save exists where a player observes someone else's
design.
Status stays open and is flagged as NOT identifiable: eighteen fields of the
player record split the roster the same way and only two saves carry a non-zero
value. The probe is an entry hook on W2's writer.
28 lines
2.3 KiB
JSON
28 lines
2.3 KiB
JSON
{
|
|
"entries": [
|
|
{
|
|
"name": "g_BankruptcyEliminationDivisor",
|
|
"addr": "0x00a2ec30",
|
|
"convention": "data",
|
|
"prototype": "double 0xBFC3333340000000 = -0.15000000596046448 = (double)(float)-0.15f -- the elimination-limit divisor, loaded by ServerPlayer::UpdateBankruptcyLimits at 0x00818612 with `DD 05` (fld QWORD). It is the negation of g_DebtInterestRate (0x009ed188), and the two are stored separately. Named here because lane N's fix cited the value but not the address",
|
|
"status": "verified",
|
|
"source": "lane PL own disassembly pass 2026-09-08 (reva_call read-memory 0x00818600 +200, 0x00a2ec30)"
|
|
},
|
|
{
|
|
"name": "PTR_g_BANKRUPTCY_PROTECTION_LIMIT_FACTOR",
|
|
"addr": "0x00aedfdc",
|
|
"convention": "data",
|
|
"prototype": "float* -> 0x00b23e28. The config loader's pointer slot for the key BANKRUPTCY_PROTECTION_LIMIT_FACTOR, the same indirection pattern as ADDICTION_INCOME_MOD's (0x00aeca48 -> 0x00aeca44). Read once in the image, by UpdateBankruptcyLimits at 0x0081866e",
|
|
"status": "verified",
|
|
"source": "lane PL own disassembly pass 2026-09-08 (reva_call read-memory + find-cross-references)"
|
|
},
|
|
{
|
|
"name": "g_BANKRUPTCY_PROTECTION_LIMIT_FACTOR",
|
|
"addr": "0x00b23e28",
|
|
"convention": "data",
|
|
"prototype": "FLOAT32 -- and the width is the finding. UpdateBankruptcyLimits multiplies it in at 0x00818674 with `D8 09` = FMUL m32fp, i.e. a DWORD, while the elimination divisor two instructions earlier is `DD 05` = FLD m64fp. So whatever decimal the data file carries for this key is narrowed to a 4-byte float before the multiply, and a `double` multiply truncates to a different integer at every boundary -- for a factor of 3.3 that is EVERY max income divisible by ten, starting at maxIncome = 10 (decimal 33.0 -> 33, image 32.99999952 -> 32). The file image of the slot is zero: the loader fills it at run time, so the value itself is a data-file fact, not a binary one. The corpus cannot see the difference -- all seven of its non-zero BnkPr records invert to a max income where the two agree -- so this is an instruction-stream reading, thin-covered by construction (rule 23)",
|
|
"status": "verified",
|
|
"source": "lane PL own disassembly pass 2026-09-08; docs/PL-players-residual.md in sots-engine"
|
|
}
|
|
]
|
|
}
|