sots-engine/src/app/trade_raid.h
lane Y a98ae6db5b Y: model the turn's dominant generator cost, and the tail's turn record
The trade-raid block is 16 of a measured turn's 18-22 generator words and it is the
first thing the standalone can model as a COUNT rather than as a formula: two chance
rolls per entry of the player vector, neither site inside a back edge, both
probabilities strictly inside (0,1) so neither early-out fires. src/app/trade_raid
implements it with the word cost reported rather than assumed, so a tuning table that
pushes an odds value to 0 or 1 removes the draw and the ledger says so.

Against lane Z's two calibrated oracle pairs the standalone now consumes 16 words and
lands 4 and 2 short, which is exactly the per-call-site ledger's split for those two
turns. It does NOT match the oracle's state, and the report says which sites are
missing instead of netting them off the total.

The tail's last phase -- the per-player turn record -- is modelled for the six fields
that are recoverable from the wire, and self-checked every run against the record the
input save already carries for its own turn: 480 fields over 80 player-records across
the corpus, 0 mismatches. It is not committed. Under --commit-blocked it closes 24
container-shaped divergences on the reference pair and opens 17 leaf-shaped ones, all
of them in the five fields the model does not hold, so the block is a measurement now
rather than an argument.

phases 14/44 of the two turn drivers (8 committed), 3/37 of the tail.
divergence unchanged: 209->204 and 108->103, 5 closed / 0 regressed on both.
2026-09-08 11:33:36 -04:00

55 lines
2.7 KiB
C++

// Trade-raid encounter generation -- the strategic turn's dominant RNG consumer.
//
// The original runs this from inside encounter detection, over the server's player vector,
// rolling up to three chances per player. Two of the three roll on every turn measured; the
// third is behind a subsystem that was not present. The important property, and the reason
// this is modelled at all while the rest of encounter detection is not, is that NO BACK EDGE
// contains any of the three roll sites: the cost is one word per player per firing site, as a
// bound rather than as an observation.
//
// The probabilities are simulation constants that come from the game data. Their in-image
// defaults are used when no tuning table is loaded; either way all three are strictly inside
// (0, 1), so the chance helper takes neither of its early-outs and every roll is exactly one
// word. That is why the WORD COUNT is independent of the probabilities and the RAID DECISIONS
// are not.
#pragma once
#include "game/sim/rng.h"
namespace sots::app {
// The chance helper's contract, transcribed: p <= 0 is false with NO draw, p >= 1 is true with
// NO draw, otherwise exactly one draw and the test is `p > r` -- equality is a failure. A NaN
// probability falls through both early-outs and draws.
bool Chance(sots::sim::IRandom& rng, float p);
struct TradeRaidOdds {
// In-image defaults. A loaded tuning table overrides them; the word cost does not move.
float player = 0.2f;
float npc = 0.05f;
float refugee = 0.05f;
};
struct TradeRaidGates {
// The NPC-raid roll is behind a single server-level test that does not depend on the
// player, so it is all-or-nothing for the whole turn. Measured open on 8 of 8 turns
// across two saves; it is not on the wire, so this is a HYPOTHESIS with a stated default.
bool npcRaids = true;
// The refugee-raid roll needs a subsystem manager that was absent on every measured turn.
bool refugeeRaids = false;
};
struct TradeRaidResult {
int players = 0; // entries of the player vector the loop visited
int words = 0; // generator words consumed
int playerRaidHits = 0; // rolls that SUCCEEDED -- each one may cost a further word we do
int npcRaidHits = 0; // not model (the target-selection draw), so a non-zero total
int refugeeRaidHits = 0;// here is the signal that the ledger is about to fall short
};
// Roll the raid chances for `players` players, in the original's order: player raid, then NPC
// raid, then refugee raid, per player, before moving to the next player.
TradeRaidResult RollTradeRaids(sots::sim::IRandom& rng, int players, const TradeRaidOdds& odds,
const TradeRaidGates& gates);
} // namespace sots::app