diff --git a/src/game/ai/orders.h b/src/game/ai/orders.h index 215c973..878f5fa 100644 --- a/src/game/ai/orders.h +++ b/src/game/ai/orders.h @@ -162,6 +162,45 @@ struct ColonizeOrder { int trailing = 0; }; +// --------------------------------------------------------------------------------------------- +// A CONTRACT FOR WHOEVER BUILDS THE FLEET GATHER +// +// These take `fleetId` as a parameter: this layer emits orders and does not form fleets or mint +// ids. The subsystem that does is not written yet, and when it is, it inherits a requirement that +// was measured rather than guessed, so it is recorded here where it cannot be missed. +// +// In the original, two processes running the same turn from the same save with the AI's seeds +// pinned produce different saves -- 35 leaves of 61,147, and all 35 are one transposition: two +// fleets exchange contents. The command blocks differ in exactly three words. The id counter is +// deterministic; the ids are minted in the same order every time. What varies is WHICH GROUP EACH +// ID LANDS ON, because the pass that assigns them walks a container ordered by +// `fleet->Location` -- a HEAP POINTER, with the fleet pointer breaking ties. Under LFH +// randomisation two allocations of the same size land in either relative order, so the original's +// whole observed outcome set on that turn is the two orderings of two heap pointers. +// +// Three consequences bind us: +// +// 1. **Order the groups by a key that is a function of the save, and mint in that order.** +// `(systemId, then minimum ship id)` with a total tie-break. Any deterministic key would do: +// the original's order is NOT ascending systemId and NOT ascending minimum ship id in any +// process observed, so THERE IS NO ORIGINAL ORDER TO MATCH -- we are choosing one member of +// its outcome set, not reproducing a hidden rule. Say so; do not let a later reader mistake +// this for a recovered behaviour. +// 2. **Impose it where the groups are gathered and the ids are minted, not at assignment.** +// The fleets already carry their new ids when the assignment walk first touches them, so +// canonicalising at assignment is one level too low and fixes nothing. +// 3. **A byte-exact replay needs the original's order, and the block already carries it.** +// List 10 records `{systemId, fleetId, ships[]}` per group in visit order, so every captured +// run states its own order for free. An optional override that replays a captured order is +// what turns "equal modulo a relabelling" into "equal" -- three words per new fleet, the same +// cost class as pinning a seed. +// +// Evidence: sots-re `findings/control-flow/fleet-visit-order-mechanism.md` (the container and its +// key, measured across three processes including a branch flip), +// `findings/control-flow/pinned-seed-rich-turn.md` (the residue), +// `findings/resolutions/2026-09-09-fleet-id-order-residue.md` (the ruling). +// --------------------------------------------------------------------------------------------- + // List 8. The route is a counted vector of system ids, so a multi-hop order is longer on the wire // than a single-hop one -- but it is still ONE element and therefore ONE counter bump. struct FleetMoveOrder {