5. Balance definitions & constants

Kind-specific server balance lives in server/crates/rules/src/defs.rs; faction availability, buildables, trainables, upgrade ids, and ability carriers live in server/crates/rules/src/faction.rs; terrain movement/cover/concealment hooks live in server/crates/rules/src/terrain.rs and currently return the all-open-ground defaults. config.rs is the thin constants module for timings, tile size, starting resources, supply caps, mining amounts, and other scalar simulation constants; its unit_stats(kind) and building_stats(kind) helpers read the defs table. client/src/config.js mirrors the subset the UI/render/fog needs (costs, supply, sight, sizes, colors, and command-card descriptors). Keep both in sync; run node scripts/check-faction-catalog-parity.mjs to mechanically compare the Rust-authoritative default faction catalog to the client descriptors. The server wiki’s /wiki/stats page is generated from the same Rust definitions and faction catalogs. For changes that affect visible stats, faction availability, upgrades, or ability metadata, run node scripts/check-wiki.mjs; it includes the wiki route/table checks and the client catalog parity check.

server/src/config.rs and server/crates/sim/src/config.rs are compatibility shims for Rust-owned balance exports while call sites are migrated. They should not accumulate server-shell or sim-only implementation constants. Those values belong beside the module that owns the behavior.

Final source-of-truth map and guardrails

Use server/crates/rules/src/defs.rs for unit/building stat records, costs, supply, sight, ranges, footprints, body dimensions, and build/train timing. Use server/crates/rules/src/faction.rs for faction catalogs: buildables, trainables, research rows, ability carriers, command-card descriptors, and ability/upgrade metadata exported by Rust. Use server/crates/rules/src/balance.rs for shared scalar constants such as tick rate, resource-node amounts, support-weapon timings/ranges, upgrade durations, body dimensions, and ability effect scalars. Sim-only behavior constants belong beside the sim module that owns the behavior rather than in the compatibility config shims.

client/src/config.js mirrors only the subset needed by UI, rendering, fog previews, and command cards. Rust-owned mirrored values include gameplay-visible costs, supply, sight, footprints, body dimensions, client-visible timing/range/duration constants, faction legality, upgrade metadata, ability descriptors exported by the Rust faction catalog, ability effect fields exported in the rules dump, and resource starting amounts. Client-owned values include render colors, camera defaults, fog overlay alpha, command-card layout hints, local presentation labels/icons that are not exported by Rust, and resource render labels/sizes.

Run node scripts/check-faction-catalog-parity.mjs after changing Rust-owned values that are mirrored into client/src/config.js. The check runs the Rust rts-rules faction catalog dump, including the clientConfig parity payload, and compares the client mirror for catalogs, stat fields, body dimensions, resource amounts, upgrade metadata, ability compact/order-stage codes, and Rust-owned ability descriptors/effect fields. Run node scripts/check-wiki.mjs as well when a change affects visible stats, faction availability, upgrades, or ability metadata that appears on the generated /wiki/stats page.

ConstantBefore Phase 5After Phase 5Mirror impact
MORTAR_FIRE_TOLERANCE_RADSim-only mortar aim tolerance exported from server/crates/sim/src/config.rs beside mirrored balance constantsSim-local server/crates/sim/src/game/mortar.rs FIRE_TOLERANCE_RAD, owned by mortar firing behaviorNone; it is not mirrored into client/src/config.js and does not change wire shape or balance values

Client mirror boundary inventory

Phase 1 records the current source-of-truth map before later phases add broader mechanical checks. This is an inventory only; it does not change balance, gameplay, or client rendering.

Value/pathRust ownerJS mirror pathCategoryCurrent checkerProposed future checkerClient-only exclusion reasonCompact version impact
TICK_HZ, TICK_MS, TILE_SIZE, simulation timing scalarsserver/crates/rules/src/balance.rs, re-exported by server/src/config.rs and server/crates/sim/src/config.rsclient/src/config.js TICK_HZ, SNAPSHOT_MS, interpolation delay constantsbalance scalarscripts/check-faction-catalog-parity.mjs checks TICK_HZ and client-visible duration constants against the Rust rules dumpExtend the structured dump if another timing scalar becomes client-visibleInterpolation delay is client-only smoothing; TICK_HZ is mirroredNo compact impact unless snapshot cadence or compact fields change
Unit and building costs, supply, sight, footprint/radius, train/build times, and command-card stat rowsserver/crates/rules/src/defs.rs and server/crates/rules/src/balance.rs; faction legality in server/crates/rules/src/faction.rsclient/src/config.js STATS, WORKER_BUILDABLE, FACTION_CATALOGSbalance scalar / faction catalog factscripts/check-faction-catalog-parity.mjs checks catalog legality, costs, supply, sight, ranges, build ticks, building footprints, requirements, train lists, research lists, and non-body unit render radius; node scripts/check-wiki.mjs covers generated wiki stats when runFuture work can move client-only labels/icons into Rust catalogs if they should become authoritativeClient-only labels/icons in STATS are presentation unless the Rust catalog exports them; STATS.size for body-driven vehicles is presentation because the Rust-owned body dimensions are checked separatelyNo compact impact
Vehicle/body dimensionsserver/crates/rules/src/balance.rs *_BODY_* constantsclient/src/config.js TANK_BODY, ANTI_TANK_GUN_BODY, ARTILLERY_BODY, SCOUT_CAR_BODY, COMMAND_CAR_BODYbalance scalarscripts/check-faction-catalog-parity.mjs checks every client body length, width, and clearance against the Rust rules dumpKeep adding new body records to the dump/check when body-driven units are addedNone; client uses these for art, selection, and advisory placement previews, including Tank Trap preview rejection for vehicle-body units, while Rust collision is authoritativeNo compact impact
Ability descriptors, carrier lists, target mode, range, cooldown, cost, queueability, autocast, command-card label/icon/hotkey/titleserver/crates/rules/src/faction.rs plus scalar constants in server/crates/rules/src/balance.rsclient/src/config.js ABILITIES and imported ABILITY idsfaction catalog fact / balance scalarscripts/check-faction-catalog-parity.mjs checks exported command-card descriptors, carriers, target mode, range, cooldown, cost, queueability, autocast, compact codes, and Rust-owned effect fields present on descriptors such as radius, delay, duration, pull multipliers, speed, and damage; protocol parity checks ability compact codesFuture effect fields should be added to the Rust dump and descriptor assertion when they become client-visibleNot UI-only when the field is exported by Rust faction catalogs or balance constants; purely local affordance copy belongs in the documented exclusion listCode changes may affect compact ability/order-stage codes; descriptor-only changes do not
Upgrade descriptors, research building, prerequisites, cost, and research timeserver/crates/rules/src/faction.rs plus server/crates/rules/src/balance.rs upgrade constantsclient/src/config.js UPGRADES and imported UPGRADE idsfaction catalog fact / balance scalarscripts/check-faction-catalog-parity.mjs checks research building, list membership, upgrade costs, research ticks, and prerequisite upgrade idsLabels/icons/descriptions can be moved into Rust catalogs later if they should become authoritativeLabels/icons/descriptions are client-only today unless moved into the Rust catalogNo compact impact unless upgrade ids/codes change
Resource node starting amounts and economy resource namesserver/crates/rules/src/balance.rs STEEL_PATCH_AMOUNT, OIL_GEYSER_AMOUNT; fixed Steel/Oil economy fields in sim/protocolclient/src/config.js RESOURCE_AMOUNTS, KIND.STEEL, KIND.OIL, HUD/resource render helpersbalance scalar / wire DTOscripts/check-faction-catalog-parity.mjs checks node starting amounts; protocol parity checks resource kind codesAdd future client-visible resource amounts to the rules dump/checkResource render labels and sizes are client presentation; amounts affect minimap/tooltips/render assumptionsResource kind/code changes affect protocol/compact; amount changes do not
PLAYER_PALETTEserver/src/lobby/mod.rsclient/src/config.js PLAYER_PALETTEserver-owned presentation data mirrored by clienttests/protocol_parity.mjs source-scrapes the lobby paletteStructured lobby/config dumpNot client-only because server assigns lobby/start colors and sends them on the wireNo compact impact
Terrain, health, selection, placement, and drag colorsNone in Rust; rendering-only choicesclient/src/config.js COLORS except resource identity colors that should stay consistent with Steel/Oil presentationUI-only presentation dataNoneExclusion list in future config parity checkClient owns visual palette; it does not affect simulation, wire DTO shape, or authoritative fogNo compact impact
Fog overlay alphaAuthoritative fog visibility is in sim snapshots; alpha is not a Rust valueclient/src/config.js FOG_EXPLORED_ALPHA, FOG_UNEXPLORED_ALPHAUI-only presentation dataNoneExclusion list in future config parity checkClient owns opacity; Rust owns which tiles/entities are visibleNo compact impact
Camera defaultsNone in Rustclient/src/config.js CAMERAUI-only presentation dataNoneExclusion list in future config parity checkClient-only input/render affordanceNo compact impact
Anti-tank gun field-of-fire previewserver/crates/rules/src/balance.rs ANTI_TANK_GUN_FIELD_OF_FIRE_RAD is authoritative at 45 degrees totalclient/src/config.js ANTI_TANK_GUN_FIELD_OF_FIRE_RADbalance scalar / advisory UI mirrorscripts/check-faction-catalog-parity.mjs checks the client preview against the Rust field-of-fire constantKeep the preview Rust-owned because it represents the authoritative deployed firing arcNot client-only: the client preview represents an authoritative firing arcNo compact impact

Phase 4 parity exclusions

The structured rules dump intentionally excludes client-owned presentation values that do not have Rust catalog or balance ownership: global terrain and selection colors, fog overlay alpha, camera defaults, command-card layout hints, and resource render labels/sizes. STATS labels and icons remain client-owned until they are exported by a Rust catalog. STATS.size for units with a Rust-owned body record is also presentation-only; the parity check enforces those units through their *_BODY_* length, width, and clearance values instead.

5.0 Faction economy contract

The faction rollout keeps Steel, Oil, and Supply as the global economy contract. Faction catalogs decide which global units, buildings, upgrades, and abilities are legal for a player and define starting Steel/Oil values plus starting entity loadouts, but they still use fixed steel, oil, supplyUsed, and supplyCap fields. Unknown non-empty faction ids do not fall back to the Kriegsia catalog in lower-level economy queries: catalog-gated build/train/research/gather, production-anchor, and supply checks return empty or false. Start-map resource nodes remain Steel and Oil nodes. Score values, replay analysis values, command-card costs, affordability checks, refunds, and supply reservation are intentionally Steel/Oil/Supply-shaped. Catalog existence is not lifecycle admission. server/crates/rules/src/faction.rs may contain playable catalogs, explicit fixture catalogs, and future catalog data, but server/src/lobby/faction_validation.rs decides which ids can enter normal lobby, AI, replay, branch, quickstart, self-play, dev, match-history, and post-match paths. Fixture-only and reserved/future ids must not inherit Kriegsia economy behavior or appear in product selectors just because their catalog rows are dumpable.

Approved direct Steel/Oil/Supply modules for this plan are:

Generic resources are deferred. If a future faction needs a non-Steel/Oil resource, that work must be a separate migration across player state, snapshots, compact transport, replay artifacts, observer analysis, scoring, HUD rows, command-card costs, protocol parity, and prediction/WASM compatibility.

5.1 Target theme and MVP combat loop

The target gameplay direction is a simplified World War II-inspired battlefield with fictional, faction-agnostic sides. This is not a historical simulation. The theme should support readable gameplay, clear unit roles, and strong terrain identity without national or regime-specific iconography.

MVP scope:

Core unit roles:

Terrain rules:

Intended progression:

5.2 Current implementation constants

The current implementation uses the themed unit/building names below. Combat is handled by the shared attack model plus the support-weapon setup/teardown state, tank turret aim gates, and tank hull-facing damage modifiers for anti-tank hits against tank victims. Tanks keep their active movement path while firing on either Move or AttackMove orders; riflemen upgraded with Methamphetamines are permanently charging, keep advancing while firing with normal accuracy, and move at tank speed; other mobile combat units still hold position once a target is in weapon range. Scout cars also fire while moving using an independent rear machine-gun facing. They are unarmored light vehicles and do not receive armored damage reduction, but anti-tank guns do not roll their infantry miss chance against them. Plain Move tanks and scout cars only fire at enemies already in weapon range, while AttackMove tanks and scout cars can chase acquired targets. When they chase an acquired target from outside weapon range, they path to a standoff point inside firing range instead of the target center. Forest-specific rules are future work. The unit, building, and resource-node tables below are the human-readable form of the authoritative rules::defs records.

Unit stats (hp, dmg, range[tiles], cooldown[ticks], speed[px/tick], sight[tiles], cost, supply, buildTicks):

kindhpdmgrangecdspeedsightsteeloilsupbuildTicks
worker4041242.075001396 (~13.2s)
rifleman4554161.685001300 (~10s)
machine_gunner554661.28875102400 (~13s)
mortar_team7540 outer / 100 inner AOE12601.67100503460 (~15s); trained at Gun Works (steelworks kind)
anti_tank_gun4560 deployed / 45 packed14 deployed / 5 packed721.6675253440 (~15s); requires Gun Works (steelworks kind) and Anti-Tank Gun Crews (anti_tank_gun_unlock) researched in R&D Complex
artillery150150 AP inner / 150-10 outer AOE15-60 point fire901.353001005750 (~25s); requires Gun Works (steelworks kind), Anti-Tank Gun Crews (anti_tank_gun_unlock), and Unlock Artillery (artillery_unlock) researched in R&D Complex; tank-sized footprint
scout_car1006562.3510125503480 (~16s)
tank292605722.064251508750 (~25s); requires Vehicle Works (factory kind) and Tank Production (tank_unlock) researched in R&D Complex
command_car2250002.3510150754450 (~15s); requires Vehicle Works (factory kind) and Command Car (command_car_unlock) researched in R&D Complex; no weapon; Scout Car-style movement with a smaller jeep-sized body
ekat3000002.090000; Ekat faction hero; no default attack; regenerates 1 HP/s

Building stats (hp, sight, cost, footprint tiles wxh, buildTicks, extra):

kindplayer-facing namehpsightcostfootbuildTicksnotes
city_centreCity Centre60092003x3550trains worker; +8 supply; players start with one free
zamokZamok600903x30Ekat start building; inert in first playable slice
depotSupply Depot11041002x2300+8 supply
barracksBarracks16561503x2200trains rifleman and machine_gunner; requires a City Centre
training_centreTraining Centre3006100 steel + 50 oil3x2560shared prerequisite before either advanced path; unlocks machine_gunner training at barracks and researches Methamphetamines; requires a City Centre and Barracks
research_complexR&D Complex1656100 steel + 100 oil3x3450research-only building for Anti-Tank Gun Crews, Unlock Artillery, Tank Production, Command Car, and Mortar Autocast; requires a City Centre and Training Centre
factoryVehicle Works3606125 steel + 125 oil3x3749Mobile Warfare path building; trains scout_car immediately, trains tank after Tank Production research, and trains command_car after Command Car research; requires a City Centre and Training Centre
steelworksGun Works3006150 steel + 100 oil3x3599Superior Firepower path building; trains mortar_team immediately and trains Anti-Tank Guns/Artillery after R&D Complex research; requires a City Centre and Training Centre
tank_trapTank Trap150015 steel + 0 oil1x1300engineer-built vehicle obstacle; sparse orthogonal pairs close the single tile between them for vehicle movement only; armored, no trains, no supply, no weapon, no sight/fog reveal, not an elimination building; requires a completed Training Centre

Win: a player is eliminated when they own zero elimination-counting buildings; units and Tank Traps alone do not keep them alive. Last player standing wins; a 1-player match never ends (sandbox/exploration mode). In a 3-4 player match, a connected human who is eliminated receives a one-time gameOver score snapshot immediately while the remaining players keep playing; final match resolution sends gameOver only to players who have not already received one.