Military
The military simulation extension, canwu-military, is an engagement engine. It resolves fights between typed forces at a place your world owns, under a combat rulebook your game installs and a step resolver; what it writes is records and signals that your game applies. It owns no doctrine, unit catalogue, terrain table, weather, control or occupation state, store, or report text. Read this page when you decide whether to drive your combat with it, or how to fit your own combat model into it.
In brief: every number that decides a fight is a parameter of your rulebook. Your game places forces and supplies their condition each day. The engine steps the fight, records every per-step quantity, and closes it into an engagement_summary record; a fallen place is a fact in that summary, never an occupation the engine creates.
Crates and ownership
Section titled “Crates and ownership”| Crate | What it owns | Published |
|---|---|---|
canwu-military |
MilitaryPlugin<R>, the StepResolver trait and ParametricStepResolver, eight record kinds under canwu.military (rulebook, registry, force, engagement, engagement_steps, siege, crossing, engagement_summary), the MilitaryCommandV1 command, and the engagement_report and engagement_outcome knowledge schemas |
Published on crates.io |
canwu-military-reference-content |
synthetic_rulebook_v1(), one explicitly synthetic rulebook |
Published on crates.io |
canwu-military-reference |
demo_engagement_scenario(), which installs that rulebook and six forces on the reference world, and the military_starter example |
Repository only |
| Concern | Who owns it |
|---|---|
| A force as combat knows it: owner, controllers, location, components with present and lost men, side terms, battle state | canwu-military (the force record); your game registers, revises, and places it |
| Everything else about a force: pay, autonomy, readiness, doctrine, commander, stores | Your game, passed in as condition inputs each engagement-day |
| The engagement process: open, forecast, steps, break, pursuit, close, summary | canwu-military, on your rulebook and a resolver |
| Sieges and contested crossings | canwu-military; the site’s walls, supply, naval control, and a gate opened by your own rule are your inputs |
| Movement, contact, detection, hostility | Your game and canwu-movement or canwu-knowledge; the engine checks only that forces stand at the site |
| Control, occupation, casualties as people, stores of goods | Your game, from the summary and the outcome signal |
Core model
Section titled “Core model”- Combat rulebook (
CombatRulebookV1, 战斗规则册): step profiles, per-kind rules for the six engagement kinds (Field,Assault,Sortie,Relief,FerryFight,BridgeheadFight), forecast terms and bands, the four lines, cohesion, morale, break, pursuit, terrain, night, fortification, siege and crossing rules, and the limits of every collection the engine keeps. It is hashed; the scenario installs it, or aSystemissuer sendsInstallRulebook. A run holds one. - Force (
ForceStateV1): a force stands at a place your world owns (LocationV1::At { site }); your game places it withPlaceForcewhen it arrives. Its components carry the values your game compiles from its unit profiles; its side terms carry cohesion cap, morale break, battle command, assessment, credibility, and traits. - Engagement (
EngagementStateV1, 交战): two sides at a site, each with its forces, its report holders, its latest condition input (ConditionInputV1, 战斗条件输入), the battle’s morale shock, cohesion, and losses; a pre-battle forecast (ForecastV1, 战前预判) from the rulebook’s terms and ratio bands. - Engagement step (
StepV1, 战斗步): one resolution of the step formula with every per-step quantity, the inputs’ digests, the draws, and the resolver’s identity. - Siege (
SiegeStateV1, 围城) and contested crossing (CrossingStateV1, 渡河作战): day-grained and wave-grained processes that open their own engagements.
How it runs
Section titled “How it runs”View diagram source
flowchart TB
Cmd["MilitaryCommandV1<br/>canonical command ingress"] --> Admit["Admission<br/>static checks, holders"]
Admit --> Apply["military_apply_v1 packet"]
Wake["Wake system on your cadence,<br/>the trusted Step command,<br/>or your timed step"] --> StepPacket["Step packet"]
Apply --> Settle["Phase 7: settle-engagements-v1<br/>apply commands, then step"]
StepPacket --> Settle
Settle --> Records["force, engagement, steps,<br/>summary, siege, crossing"]
Settle --> Signal["Optional outcome signal<br/>to your plugin"]
Records --> Publish["Phase 13: reports<br/>to each side's holders"]
- Your game sends a command; admission checks its shape and its report holders and queues an apply packet for the next boundary at the same instant.
- A fight steps only in a boundary that admits a step packet. With
with_step_cadence(Some(cadence))(daily by default) the wake system schedules one whenever work is due, stepped in the next boundary at the same instant; withwith_step_cadence(None)your game sendsStep { up_to }after its inputs, stepped in the boundary that applies them. Either way your game may instead queue a timed step (timed_step_ingress(due_at)) for a boundary it settles anyway, such as the next daily one: that boundary steps, and stepping adds no boundary of its own. Commands that boundary admits apply in the next cut, after the step; if a day’s orders must apply before the step, sendStepwith them, or have them admitted in a boundary before the timed step is due, so their apply packets land in the stepping boundary ahead of the step. - In phase 7 the settle system applies the boundary’s commands in admission order, then steps every engagement, siege day, and crossing wave due by then.
- Each step builds the resolver’s input from the sides’ components, their latest inputs, the environment, the kind’s rule, and the site; calls the resolver; splits casualties over components by largest remainder (each component takes the whole part of its proportional share, and the places left over go to the largest fractional remainders); applies the break rule and pursuit; and appends the step.
- A side that withdraws or routs closes the engagement; its
max_minuteswithout a break (the kind’s, or a shorter time your game named when it opened the fight) closes itStalled. The close writes a create-onlyengagement_summaryrecord and, if you asked for one, sends the outcome signal. - In phase 13 the report system publishes each side’s holder-relative report within the knowledge budget.
The step and the resolver
Section titled “The step and the resolver”The resolver is a type: MilitaryPlugin<R: StepResolver>. ParametricStepResolver computes four lines per side (fire, shock, melee, and defense: one integer term each) from the components, terrain, night, fortification, and supply, and a per-step rate each side inflicts; every per-step quantity (inflicted share, cohesion loss, morale shock) scales that rate once by the profile’s step_scale_bp (in basis points, hundredths of a percent), so a daily profile can compress many short steps into one. A game with its own formula implements StepResolver once and may ask for keyed draws through StepDraws. Activation refuses a rulebook that names another resolver than the plugin runs.
The engine owns everything around the call: it clamps the outcome to the rulebook’s ranges, applies the break rule (a defender behind closed walls of the rulebook’s grade never breaks), applies pursuit on a rout, and decides whether the fight continues. Step profiles let one fight run on a daily step while another is promoted to half-hour steps (ReviseStepProfile).
Sieges and crossings
Section titled “Sieges and crossings”A siege day computes investment from the besiegers’ men and any naval control, emplaces guns and then bombards the walls toward the grade’s breach threshold, and opens an assault when ordered or, with storm_on: BreachOrGate, the day a breach or a gate opens. A sortie opens when ordered and the defenders’ cooldown has passed; a relief is an engagement you open against the besiegers. Defenders breaking at the wall make the siege fall and disperse the defenders still there; besiegers routed lift it. A second force joins a besieged site with JoinSiege, and enters on its side any assault or sortie under way; ReviseSiege { release } lets a force on either side that is not fighting leave, so you can place it elsewhere. A siege whose last defender leaves falls Abandoned at its next siege day, and LiftSiege ends one whose last besieger leaves.
A crossing sends one wave per wave interval. An interceptor whose control is high enough catches a wave by threshold or by a keyed draw; a caught wave fights a ferry fight and turns back; a landed wave may fight a bridgehead fight against the defenders on the far bank, which the order names and ReviseCrossing { defenders } revises when a force reaches that bank later. Your game moves the force when the crossing completes.
When a command fails
Section titled “When a command fails”A command that fails admission is a rejected command attempt. A command that fails in phase 7 (an unknown or engaged force, a stale expected_host_revision, an issuer who controls no force, a limit, an unknown posture or profile) is recorded with a canwu.military.command_rejected.v1 event whose summary begins with the error code. A step the engine cannot take (an oversized step, a failing resolver, too many draws) holds its engagement with canwu.military.engagement_held.v1 until a SupplyInput, ReviseStepProfile, or OrderAction { Abandon }. None of these fails the boundary.
Knowledge and visibility
Section titled “Knowledge and visibility”Each side names its report holders: people, governments, or the force records themselves. Holders are checked when the command is admitted and again when a report is published, so a holder who has died is skipped. Progress reports (engagement_report) supersede the side’s last; closing reports (engagement_outcome) are kept. A fight, siege, or crossing whose sides name no holder queues no report, so a game that writes its own reports from the records pays the report system nothing. A side sees its own losses exactly and the enemy’s as bands from the rulebook’s cuts.
Randomness, persistence, and replay
Section titled “Randomness, persistence, and replay”Two random streams, crossing-interception and step-contingency, are drawn by keyed address with the admitted step packet as evidence, so a live, sealed, restored, and replayed run draw alike. Every command that changes a record names that record’s expected_host_revision, which only host commands advance: a step in the same instant never makes your command stale. The wake cadence, the signal target, and the publication reservation are part of the plugin descriptor, so a run restores and replays only with the same choices.
What your application supplies
Section titled “What your application supplies”- The rulebook, compiled from your content.
- Forces and their revisions (muster, desertion, wounded returns) and their placement on arrival.
- Engagements, sieges, and crossings opened from your own contact rules, with each side’s report holders.
- One condition input per side and one environment input a day, derived from your supply, pay, command, and weather records, cited as evidence.
- Your own rules the engine does not decide: betrayal (
ReviseSiege { gate_open_steps }), storming doctrine, rations, naval patrols, promotion of a fight. - What follows a fight: control, occupation, population, stores, and player text, from the summary or the outcome signal.
Limits and budgets
Section titled “Limits and budgets”Every collection the engine keeps has a cap in the rulebook’s limits: forces, components per force, forces and holders per side, open engagements, sieges, and crossings, step bytes, draws per step, and the report queue. A boundary without a step packet does no stepping work; a stepping boundary costs in proportion to the open engagements and the forces in them. A closed fight leaves about 2 KB of records for the life of the run. The engine has two sides per engagement, no automatic contact, and banded enemy figures in its reports.
Try it
Section titled “Try it”cargo run -p canwu-military-reference --example military_starterThe walkthrough Run the engagement engine explains what the starter does at each step. For resource-backed supply, run cargo run -p canwu-economy-reference --example grain_loop and read the grain, production, and military supply case. For stream declarations and draw evidence, see randomness.
Further reading
Section titled “Further reading”- Settlement system: the fourteen phases, staged writes, and rollback
- Event system: admission order and zero-delay packets
- Extend with plugins: writing your own resolver, or a plugin that replaces this one
- Military engagements design: the contract this extension implements, with its implementation notes
- Military engagement revisions: the timed step, siege joins and releases, revised crossing defenders, and shorter fights added in 0.21.0