// game::sim -- ship construction: what a completed build order writes. // // The point-consuming half of the pass lives in colony.h as `ProcessBuildQueue`, because // that is where the colony turn's other point channels live. This file holds the half that // runs *per completed order*: the player's `ShipRecords`, which is the only thing a // completion writes that the save can see without also creating the ship. // // WHERE THIS RUNS // --------------- // `StrategyServer::ProcessTurn` phase 11 -> `ServerSystem::ProcessTurn` -> // `ServerSystem::ProcessBuildQueue` -> `BuildQueue::ProcessTurn`. A second caller exists // (the ship-borne queue reached from the ship-action dispatcher, i.e. a construction ship // building a station), and it reuses the same function. // // WHAT A COMPLETION WRITES, in the order the original writes it // ------------------------------------------------------------- // 1. the ship is created and attached, and the new ships of the pass are collected into a // vector that is handed to the fleet-forming step after the loop; // 2. `ShipRecords.built[hullClass]` is incremented -- indexed by the design's cached hull // class ordinal, stride 4; // 3. the per-design record whose key equals the design's object id is found, appended if // absent, and its own `built` field is incremented; // 4. a build-completed event is pushed onto the system's event list; // 5. `points -= conleft`, `conleft = 0`. // // Step 2 has EXACTLY ONE writer in the whole executable -- an image-wide scan for the // indexed increment at that displacement returns one site, inside this function. So a ship // that reaches the wire with the per-class counter bumped came through this pass and no // other. (Losses, kills and in-service are three further parallel arrays with the same // stride; nothing increments them here, and nothing in the save corpus is ever non-zero for // losses or kills, so they carry no model.) // // The record layout is settled by ENUMERATION, not by what this function touches: the wire // writes `srnc` groups of {srb, srl, srk, sri} followed by `srbd` records of // {srd, src, srb, srl, sri}, and the class array's base plus four arrays of three ints lands // exactly on the per-design vector's first word. Three classes, four arrays, then the // vector. #pragma once #include #include #include "game/sim/colony.h" namespace sots::sim { // Destroyer / cruiser / dreadnought. The wire's `srnc` is 3 in every save in the corpus. constexpr int kHullClassCount = 3; // One element of the second counted section (`srbd`). `src` is the design's hull class and // is written once, when the record is appended; a later completion of the same design only // touches `built`. struct ShipDesignRecord { int designKey = 0; // srd -- the design's OBJECT id, not its index int hullClass = 0; // src int built = 0; // srb int lost = 0; // srl int inService = 0; // sri }; // Game::ShipRecords, held inline in the player. struct ShipRecords { int built[kHullClassCount] = {}; // srb int lost[kHullClassCount] = {}; // srl int killed[kHullClassCount] = {}; // srk int inService[kHullClassCount] = {}; // sri std::vector designs; }; // Linear search for `designKey` over the per-design vector, appending a fresh record when // there is no hit. The search is a plain forward scan and the append is a push_back, so the // vector's order is first-seen and is load-bearing for the wire. // CONFIDENCE: high -- read instruction by instruction, including the append's field order. ShipDesignRecord& FindOrAppendDesignRecord(ShipRecords& r, int designKey, int hullClass); // One completed hull: bump the class counter and the design record's own counter. // A hull class outside [0, kHullClassCount) leaves the class array alone -- the original // indexes it unchecked, so an out-of-range class is a corrupt design, not a policy. // CONFIDENCE: high on both increments; the guard is ours. void RecordShipBuilt(ShipRecords& r, int designKey, int hullClass); // --------------------------------------------------------------------------------------- // One system's construction pass // --------------------------------------------------------------------------------------- struct Completion { int orderId = 0; int designId = 0; }; struct SystemConstructionResult { std::vector completed; int pointsIn = 0; int pointsLeft = 0; // the original's return value int pointsSpent = 0; // pointsIn - pointsLeft int moneyCharged = 0; int ordersBefore = 0; int ordersAfter = 0; int ordersRemoved = 0; // completed here, plus any that were already at or below zero bool advancedPartially = false; // an order absorbed everything and stopped the pass bool sweepRan = false; // false when points <= 0: the whole body is skipped }; // FIFO consumption with the design id of every completion kept, which the queue pass alone // does not report. `queue` is modified in place exactly as the original modifies the list. // CONFIDENCE: high -- see colony.h's ProcessBuildQueue for the rules and their evidence. SystemConstructionResult RunSystemConstruction(std::vector& queue, int points); // --------------------------------------------------------------------------------------- // The hull and the fleet at birth -- READ, NOT YET IMPLEMENTED // --------------------------------------------------------------------------------------- // // This is written down here rather than coded because the newborn hull copies four cached // stat words out of its design, and those words are recomputed by the design's own stats // pass, which this engine models only far enough to get a hull class. Coding it now would be // writing fields we cannot compute. The whole chain is read instruction by instruction in // the RE repo; the shape, for the lane that gets the design stats: // // THE SHIP. 176 bytes. The object id comes from the network-node id allocator -- // `(counter << 4) | (node & 0xF)`, per-node counters, PRE-incremented, never issuing 0 -- // and is written into the object by the id-map insert, not by the constructor. At birth: // the owner is the queue's owner and the design is the order's design; range, health, // construction capacity, refuel capacity and repair capacity are copied from the design's // cached stat words; the plague and one other word are -1 and everything else is zero. // Two fields matter to a reimplementation: // * the FLEET LINK IS NULL at birth and is set by the fleet-join step, not here; // * the TURN-BUILT stamp is the sim's frame counter, i.e. the number of the turn being // produced -- MEASURED: the six hulls the zuul frame-16 -> frame-17 pair adds all // carry the NEW turn number, which is independent evidence that the frame counter is // incremented before the spine runs. // // THE FLEET. 288 bytes. A system caches ONE home fleet; every hull built there joins it, // and a fleet is created only when that cache is empty. So a second turn of building at // the same system creates no fleet. A created fleet takes an id from the same allocator, // is born at the system's position, and always carries flag 0x400 -- which is NOT a // retreat marker (that reading is corrected) and is NOT the design flag of the same // numeral that game/design/hull.h warns about. The build path adds 0x20 on top. There is // NO scalar ship count: the wire's count is the length of the fleet's ship vector. // A fleet created with no name override asks the player's name generator, which is what // bumps the generator's counter -- one bump per generated name. // // Ships are also created OUTSIDE this pass, by the trade manager's encounter spawner. That // path touches no ShipRecords at all, which is what makes the per-class built counter a // clean discriminator between player-built hulls and spawned ones. } // namespace sots::sim