Engine: tiny legal loan rates (2^-62) make amortPayment divide by zero, +Inf books #95

Closed
opened 2026-09-30 02:10:13 +00:00 by Winnie · 0 comments
Owner

Engine: a legal tiny loan rate can push +Inf through the books (found during the #80 round evaluation re-bench).

Repro: duel preset (players 3, survivors 1, d15, loans 5/turn), seed 87113, roster super/empire/mill. Empire is the sole bidder on loans 35 and 36 and bids exactly 4.3368086899420177e-19 (= 2^-62, the float64 epsilon signature of its clearing-spread arithmetic). That rate is legal: it lands inside the [0, loan-rate-cap] clamp.

Blowup: amortPayment(P, r, n) uses P*r / (1 - (1+r)^-n). At r = 2^-62, (1+r) rounds to exactly 1.0 in float64, the denominator becomes 0, and every payment on those loans becomes +Inf. Trace from the game (turn 8):

· NON-FINITE EQUITY Bank 2 turn 8: equity +Inf
  ACCT DIVERGENCE Bank 2 turn 8: dEq +Inf netInc -958 diff +Inf cash +Inf deps 76179 loans 53183
  CF DIVERGENCE Bank 2 turn 8: cf 15396 actual +Inf

Cascade: the poisoned books flow into AI inputs. Turn 9: empire's bond-offer size goes non-finite (the #91 guard fails it), mill's bankruptcy-bid goes non-finite from the +Inf market-acquisition-net (guard fails it too), and the auction winner (super) books +Inf bargain gain. One 2^-62 bid takes down the whole game.

Secondary bug: iterations.json marshaling fails on +Inf (iterlog: json: unsupported value: +Inf), so the iteration record is silently dropped for any run containing a blowup.

Scope: 1 of 30 duel seeds (87113) on current master 8ec3eca. The old engine (pre-#91, cda484d) runs the same seed clean. Fix is in the amortization formula, not the rate (the rate is legal).

Engine: a legal tiny loan rate can push +Inf through the books (found during the #80 round evaluation re-bench). **Repro**: duel preset (players 3, survivors 1, d15, loans 5/turn), seed 87113, roster super/empire/mill. Empire is the sole bidder on loans 35 and 36 and bids exactly 4.3368086899420177e-19 (= 2^-62, the float64 epsilon signature of its clearing-spread arithmetic). That rate is legal: it lands inside the [0, loan-rate-cap] clamp. **Blowup**: `amortPayment(P, r, n)` uses `P*r / (1 - (1+r)^-n)`. At r = 2^-62, `(1+r)` rounds to exactly 1.0 in float64, the denominator becomes 0, and every payment on those loans becomes +Inf. Trace from the game (turn 8): ``` · NON-FINITE EQUITY Bank 2 turn 8: equity +Inf ACCT DIVERGENCE Bank 2 turn 8: dEq +Inf netInc -958 diff +Inf cash +Inf deps 76179 loans 53183 CF DIVERGENCE Bank 2 turn 8: cf 15396 actual +Inf ``` **Cascade**: the poisoned books flow into AI inputs. Turn 9: empire's bond-offer size goes non-finite (the #91 guard fails it), mill's bankruptcy-bid goes non-finite from the +Inf market-acquisition-net (guard fails it too), and the auction winner (super) books +Inf bargain gain. One 2^-62 bid takes down the whole game. **Secondary bug**: iterations.json marshaling fails on +Inf (`iterlog: json: unsupported value: +Inf`), so the iteration record is silently dropped for any run containing a blowup. **Scope**: 1 of 30 duel seeds (87113) on current master 8ec3eca. The old engine (pre-#91, cda484d) runs the same seed clean. Fix is in the amortization formula, not the rate (the rate is legal).
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Winnie/bankwars#95
No description provided.