A House Divided A House DividedDocumentation
Changelog
Game Design/World & Simulation

Defence Procurement

Last updated 2026-08-20

Overview#

Defence procurement lets a country's ministry award contracts to domestic corporations to build materiel (arsenal components) for its military. The subsystem is a set of independent guardrails around what used to be an almost unbounded transfer channel from the national defence appropriation into corporate cash, the defence price-band drain incident. Each guardrail is a small, pure module so the award route, the delivery sweep, and the client-side award form can all agree on the same numbers.

Location: src/lib/military/

Key files:

Background: the Lockheed Drain#

A delivery credits the supplier pricePerLot - productionCost per lot. On the live world (turn 219) a US air lot was struck at 383,748,809 and cost 1,091 to build under the pre-fix cost model, a margin of 99.9997%. Because the price band's top was the GDP anchor (five orders of magnitude above the true build cost) and the cost model was an unrelated flat commodity-recipe figure, a defence contract was very close to a pure transfer of national appropriation into one corporation's cash balance. A minister with a stake in the supplier directly benefited. This is documented and tracked as the known price-band-drain issue; the guardrails below are the mitigation, not a full clawback (no retroactive clawback by owner decision).

Contract Award Limits (defenceContractLimits.ts)#

Contracts are budgeted in quarter-year tranches, not against an unlimited future stream.

Turn Spend Cap (defenceTurnSpendCap.ts)#

The award side limits the total a supplier may be promised in a window; this limits the rate at which that promise may settle, closing the throughput gap that let a single contract (turn 214, Soviet Union) deliver 5 lots for 4.69bn in one turn, an entire quarterly procurement window emptied in one tick.

Lot Economics and Price Band (defenceLotEconomics.ts)#

Cost is derived from price, not the other way round, inverting the old model is the core of ticket #1134.

Live Price Ratios (defencePriceRatios.ts)#

Lot costing reads the same world commodity price ratio (globalPrice / basePrice) the corporation turn's own input-cost calculation uses, so a lot's cost floor and a plant's actual input bill move together. Deliberately the world ratio, not a country's reachable-market ratio, the award form is a client component that quotes a price band before any of that scope exists, and a world ratio is the same number for both sides of the negotiation. Never cached at module scope (a stale ratio would survive a world reset into the next era).

Self-Dealing (defenceSelfDealing.ts)#

The concentration cap stops one supplier taking the whole tranche; it says nothing about who owns that supplier. resolveSelfDealing() detects when the awarding minister has a stake in the receiving corporation:

The answer to self-dealing is disclosure plus a political price, not another cap:

Fill Eligibility (defenceFillEligibility.ts)#

resolveFillEligibility() is the single shared answer to "can this plant fill this order", used by the award picker, the award route, the CEO's accept, and the delivery sweep, previously each carried its own version and disagreed (ticket cluster #1076/#1083/#1087/#1099/#1108/#1127). AWARDABLE MUST MEAN DELIVERABLE.

Refusal reasons (FillIneligibilityReason):

Reason Meaning
foreign_supplier Plant is not in the buying country
currency_mismatch Corp is not paid in the buying country's currency (canSupply() infers the currency from the corp's own country, never defaults to USD, a prior USD default hid every non-US domestic plant, ticket #1087)
no_materiel_line Plant's strategy supplies no arsenal component
retooled_off_component Plant has re-tooled off the component its contract was frozen on
no_output Plant produces nothing this turn, still eligible (a plant between production runs is a legitimate contract target; the reason rides along as a UI warning, not a refusal)
no_factories_assigned No production lines assigned to the order

State ownership is not a fill-eligibility factor: a National Corporation banks in its own country's currency like any other supplier; the only thing it changes is who clicks Accept (no player CEO).

Procurement Gate (procurementGate.ts)#

isDefenceProcurementPaused(db) reads gameState.defenceProcurementPaused (singleton _id: "current", not filtered on isActive). When true:

This is a kill switch for a procurement-drain exploit, not a subsystem teardown.