clone

an rts arena for scripts: submit tribe strategies over the web or mcp, run deterministic matches, watch them

main
tribes / packages / engine / README.md
README.md 19.9K source

@tribes/engine

Deterministic v2 simulation engine for Tribes. Implements createWorld: CreateWorld from @tribes/protocol. Tribes are dense populations (1,200 citizens at spawn) simulated as cohorts under per-mille policies — there are no per-citizen entities. Individual entities: settlements, warbands, caravans, settler wagons, threat camps.

All numbers live in src/constants.ts; this file is the human-readable reference for them.

Determinism (binding)

  • All quantities are integers; rates/shares are per-mille (0..1000).
  • Fractional flows use fixed-point accumulators in hashed state: out = (base*rate + acc) div D; acc' = (base*rate + acc) mod D. D is 1000 for simple flows and 1,000,000 for extraction (worker-permille × per-mille rate; documented denominator variant of the accumulator rule). Input-starved refinement clamps acc < 1000 (starved capacity is lost, it does not bank).
  • Allocation across a set uses largest-remainder apportionment (src/apportion.ts), ties broken by ascending index/id.
  • PRNG (splitmix32, src/rng.ts) is consumed only by: mapgen (construction), threat placement (phase 11), and ±5% battle variance (power × (950 + rng(101)) / 1000, attacker drawn before defender), each in fixed iteration order.
  • Slow systems run every SLOW_TICK_INTERVAL = 10 ticks ("slow tick" = every step where (tick+1) % 10 == 0).
  • SimEvents are per-step narration, fog-filtered in viewFor(), and NOT part of stateHash().

Phase order per step()

  1. Actions → policies — tribes ascending id, first ACTIONS_PER_TICK (64) orders each; invalid orders dropped silently.
  2. Territory & census (slow) — per-settlement biome census within its territory radius (Chebyshev).
  3. Labor apportionment — see below.
  4. Extraction — farmers/fishers/woodcutters/miners (+ tool wear; herders tend herds).
  5. Refinement & construction — workshop chains in profession enum order, then builders advance the build queue.
  6. Herds (slow) — growth, fodder, wool, slaughter, horses.
  7. Logistics — caravans & settler wagons move 1 tile/tick (greedy step, slide-around, ocean impassable, rivers fordable); routes & food equalization (slow).
  8. Consumption & needs — food, ale ration, cold-biome clothes; starvation.
  9. Population & morale (slow) — morale drift, births/deaths/emigration, tier upgrades.
  10. Military — warband movement (ascending id), field battles, camp assaults, raids; sieges & supply (slow).
  11. Threats (slow) — camp growth, spawn attempt, camp raids.
  12. Trade, faith & tech (slow) — knowledge/faith accrual, research completion, rite expiry, peddler, offer matching.
  13. Elimination — a tribe with zero settlements AND zero warbands is eliminated (its caravans/wagons/offers/routes/effects vanish).
  14. Sightings, metrics — sighted events (per observer/enemy pair, 200-tick cooldown); tick++; a MetricsFrame is computed when the new tick is a multiple of 10 and on the final tick, then interval flows reset.

Map (64×64 default, seeded; band/blob/vein densities scale with size)

Terrain chars: . plains - f forest - h hills - m mountain - w ocean (impassable) - r river (passable) - s swamp - t steppe - a arctic - v volcano - O ore vein - G gold vein (both hills-class). Layout: ocean border (solid 2, ragged to 4), arctic caps (10 rows) + steppe bands (6 rows) north/south, forest/hills/mountain/swamp blobs, up to 2 volcanoes, 5 lakes, 1–2 meandering rivers, ≤35 ore + ≤8 gold veins on hills/mountains. Cold tiles (clothes upkeep): t, a, m.

Spawns sit on a ring (integer 16-point circle — no trig) at radius 3/8 × size. Fairness (terraformed deterministically): within Chebyshev radius 8 of every founding site: plains ≥ 20, forest ≥ 10, water-adjacent land ≥ 3, hills-or-mountain ≥ 6 with ≥ 1 ore vein.

Each tribe starts with one settlement: pop 1200 (tier 3, radius 12), morale 600, herds 40, horses 10; buildings hall, 10 house, granary, warehouse, 10 farm, 2 pasture, 2 lumber_camp, mine, smithy, market, shrine, library, barracks; stores grain 2000, meat 100, hides 50, timber 400, stone 250, coin 100, metal 40, leather 40, tools 250, weapons 100, armor 20, clothes 100.

Territory, tiers, storage, housing

  • Tier from pop (never downgrades): 0 camp / 1 village ≥100 / 2 town ≥400 / 3 city ≥1000. Territory radius 4/6/9/12 (Chebyshev square).
  • Store cap per resource: 2000 + 2000×granary + 2000×warehouse (one shared cap — documented simplification; overflow is lost).
  • Housing: hall 300 + 300×tier, house 60. Births stop at 100% occupancy and taper from 80%.

Labor

allocate sets tribe-wide per-mille per profession; allocate with settlementId sets a per-settlement override (persistent; a settlement with any override switches wholly to local apportionment).

  • Tribe pool (settlements without overrides): total pop is apportioned across professions + idle by the tribe's permilles (largest remainder, ties in profession enum order; weight sums > 1000 normalize). Each profession's total is then apportioned across settlements by capacity (ties by ascending settlement id), capped by capacity and remaining pop.
  • Local (overridden settlements): the settlement's pop is apportioned by its effective vector, capped by capacity; the rest is idle.

Capacity = building slots × count, extraction also capped by biome census:

profession slots biome cap
farmer farm 40 plains × 8
fisher dock 30 water tiles × 10
herder pasture 10 —
woodcutter lumber_camp 30 forest × 8
miner mine 30 hills-class × 8
miller / baker mill 20 / bakery 20 —
brewer brewery 15 —
smelter / smith smelter 20 / smithy 20 —
carpenter lumber_camp 10 + hall 5 —
tanner / weaver / tailor 15 each —
scholar library 10 + academy 25 —
priest shrine 10 + church 25 —
builder / hauler uncapped —

Extraction & tools

Rates are ‰ units per worker-tick at full tool coverage: farmer → grain 100 (+200‰ with crop_rotation, +100‰ under a harvest rite), fisher → fish 80, woodcutter → timber 80, miner → stone 60 always, +ore 40 with ≥1 O in territory, +coin 15 with ≥1 G.

Tool rule: extractive professions (farmer/fisher/herder/woodcutter/miner) work at 600‰ yield without tools, 1000‰ with. Coverage = min(stores.tools, extractive workers), assigned in profession enum order. Tools wear 1 per 200 covered worker-ticks (accumulator), consumed from settlement stores.

Refinement (per output unit; ‰ per worker-tick)

worker rate recipe
miller 400 1 grain → 1 flour (gated by craft mill share of grain)
baker 300 1 flour → 1 bread
brewer 200 2 grain → 1 ale (gated by craft brew)
smelter 150 2 ore + 1 timber → 1 metal
smith 100 1 metal + 1 timber → 1 tools/weapons; armor adds 1 leather
carpenter 200 1 timber → 1 plank
tanner 200 1 hide → 1 leather
weaver 150 2 wool → 1 cloth
tailor 100 1 cloth + 1 leather → 1 clothes

The smithy split (craft tools/weapons/armor) apportions output by largest remainder; weapons need weaponsmithing, armor needs plate_armor (locked targets weigh 0; all-zero weights default to tools). Grain gates (craft mill/brew/fodder) are shares of current grain stock per slow phase.

Consumption, morale

  • Food demand: 2 satiation points per capita per 10 ticks at rations = 1000 (decree 0..2000); per tick (pop×rations + acc) div 50000. Eat order and satiation: bread 3 > fish 2 = meat 2 > grain 1 ("1 food per 10 ticks per capita" holds exactly for fish/meat). Calibration: 40% of pop farming grain feeds the tribe even toolless (480 × 0.1 × 0.6 = 28.8 pts vs 24 needed).
  • Shortfall → starvation: deaths/tick = min(shortfall, max(1, pop/50)), −30 morale, starvation event.
  • ale_ration ‰: 1 ale per capita per 10t at 1000‰; fully served → morale bonus.
  • Cold settlements (on/adjacent t/a/m): clothes wear pop/10000 per tick; shortage → morale penalty.
  • Morale (0..1000) drifts 1/4 of the gap to its target each slow tick. Target = 500 + 100 (≥2 food kinds this interval) + 100 (ale served) + 150 (blessing) − 150 (clothes short) − 300 (starved).

Population (slow)

  • births/10t = pop × 8‰ × housing × morale × surplus (factors ‰; surplus saturates at 50 ticks of stored food).
  • natural deaths 2‰/10t; emigration 10‰/10t while morale < 300.

Herds (slow)

Herders tend 5 herds each (tool-affected). Growth 30‰/10t of tended herds (60‰ for the fodder-fed fraction: fodder decree ‰ of herds, 1 grain each from the craft fodder gate), capped at 20 herds/pasture. Wool 100‰ per tended herd/10t. slaughter decree ‰/10t → 3 meat + 1 hide each. Horses: 20‰/10t growth, capped 10/pasture; cavalry needs 1/soldier.

Construction

build debits materials immediately and enqueues (queue cap 12, count ≤ 5 per order). Builders add 1 point each per tick; one active site per 10 builders (min 1), points split largest-remainder over active sites in queue order. demolish removes one instance instantly, no refund.

building cost (t=timber, p=planks, s=stone) points gate
house 20t 40
granary 40t 80
warehouse 40t 20s 100
farm 30t 60 plains in territory
dock 40t 80 water in territory
pasture 20t 50
lumber_camp 20t 50 forest in territory
mine 30t 20s 100 hills in territory
mill / bakery 30t 10s / 30t 20s 100 milling
brewery 30t 10s 100 brewing
smelter 20t 40s 120 smelting
smithy 30t 20s 120
tannery / weavers_hall / tailor_shop 20t / 30t / 20t 80
market 50t 20s 120
shrine 20t 20s 80
church 60s 40p 200 liturgy
library 50t 20s 120
academy 60s 40p 200 scholarship
barracks 50t 20s 120
walls L1 80t 150
walls L2 150s 250 masonry
walls L3 (castle) 200s 50p 400 castellany
tower 30t 40s 120 walls ≥ 1

The walls building count IS the wall level (max 3). The hall is not buildable (one per settlement, from founding).

Tech & faith

Scholars produce 150‰ knowledge each per 10t; priests 100‰ faith. Research completes on a slow tick when the pool covers base × (4 + techsOwned) / 4 (escalation applied at completion).

tech base cost prereq effect
milling / brewing / smelting 60 / 60 / 80 — unlock mill+bakery / brewery / smelter
weaponsmithing 150 smelting smith may craft weapons
plate_armor 200 weaponsmithing smith may craft armor
masonry 80 — walls L2
castellany 200 masonry walls L3
coinage 100 — peddler rate 250‰ → 400‰
crop_rotation 100 — +200‰ farm yield
horsemanship 100 — cavalry muster
scholarship 120 — academy
liturgy 80 — church
siegecraft 250 weaponsmithing rams (see sieges)

Rites (one-shot faith spends): blessing 20 (own settlement, +150 morale target, 50t) - harvest 25 (own settlement, +100‰ farm, 50t) - warding 40 (tribe-wide, halves threat raid power, 100t) - war 30 (own warband, +100‰ power for its next battle, expires after 200t). Timed effects live in hashed state.

Military

  • muster (needs barracks): debits pop (min size 10, ≥50 civilians remain), weapons 1/soldier (hard), armor up to 1/soldier (optional), horses 1/soldier for cavalry (horsemanship). Supply auto-fills up to 3 food/soldier from stores (grain→fish→meat→bread). With siegecraft and ≥20 planks + ≥5 metal at the settlement, the warband gets a ram (siege flag; materials debited).
  • Power = size × (10 + armed(10) + armorRatio‰/100 + veterancy/100 + situational); situational = defender terrain (forest +2, hills +3, mountain/volcano +4, swamp −2) and wallLevel × 5 when defending a settlement.
  • Battles: both powers get ±5% PRNG variance (attacker first). Higher wins (tie → defender). With r = winnerPower×1000/loserPower: loser loses min(500, 150 + r/5)‰ (min 1), winner min(300, max(50, 350000/r))‰. Winner +50 veterancy, loser +20; loser routs toward home (−100 morale). Field battles trigger between warbands within 1 tile when either tribe's stance is hostile; each warband fights at most once per tick, ascending id.
  • Orders: garrison (defend a settlement; adds power incl. wall bonus to its defense) - patrol (intercept hostile warbands/camps within 6 of the anchor) - march {x,y} - raid (settlement or caravan; not allies) - besiege - escort (own caravan; defends it against raiders) - disband (walks to the nearest own settlement, returns pop + gear; supply returns as grain).
  • Raid: within 1 tile, once: vs pop/10 militia + wall×100 + towers×50 + garrisons. Win → steal up to 2 units/soldier (loot order: coin, food, tools, weapons, armor, metal, cloth, clothes, timber, stone; hauled home instantly — abstracted return trip), small pop loss, raid event; at ≥2× defense also destroys one building. Then the warband routs home.
  • Siege (slow ticks, within 1 tile): garrisons must be beaten first (one battle per slow tick vs lowest-id garrison, walls counted). Then walls: damage = power per slow tick, 2000 × level to drop a level; without a ram vs walls ≥ 2 damage is quartered. At walls 0 and no garrison: storm vs militia; win captures the settlement (settlement_captured, ownership transfers, morale 300, overrides cleared).
  • Supply: 1 food/soldier per 10t. Inside own territory warbands auto-refill from that settlement. Outside with empty supply: 1‰ desertion per 10t (accumulator) and −20 morale.

Threats

Camps spawn on slow ticks (PRNG placement, ≤8 tries) in wilderness 10–25 tiles from a settlement, while below min(6, 1 + tick/1500) camps. Kind by terrain: swamp → skeleton_barrow, mountain → ogre_den, volcano after tick 6000 → demon_gate, else goblin_camp. Strength = base (20/30/50/90) + tick/250 + nearbyWealth/5000; +1 per 10t (cap 4× base). Every 100t a camp raids the nearest settlement within 25: power = strength×8 (halved by an active warding rite) vs settlement defense; success steals up to 2×strength units and emits a raid event with by = -1 (threat convention — documented here); ≥2× defense also destroys a building (building_destroyed with by: null). Repelled raids cost the camp 1/4 strength. A warband standing on a camp that wins its battle clears it (threat_cleared, +50 veterancy, coin = 2×strength delivered home); battle events against camps use defender: null.

Logistics & trade

  • Routes (route, ≤24): each slow tick moves permille × (surplus above keep-level 100) from → to, capped by hauler throughput (20 units per hauler at the source per 10t) and dest space. Setting permille 0 deletes the route.
  • Food equalization (default, needs ≥1 hauler and ≥2 settlements): donors above their pop-proportional share of food points push grain/fish/meat/bread to settlements below it, through the donors' hauler budget.
  • Trade offers (≤16): matched on slow ticks, canonical ascending offer id, one match per offer per slow tick. Requirements: complementary resources, non-hostile both ways, counterparty constraints, both tribes have a market (market settlement = lowest-id own settlement with one), price cross giveA×giveB ≥ wantA×wantB. Quantity q = min(wantA, giveB); payment = midpoint price × q. Goods travel as caravans (1 tile/tick) owned by the receiving tribe between the two market settlements; caravans can be raided (caravan_raided; escorts defend). Non-repeat offers are consumed by their first match, repeat offers persist.
  • Peddler: an offer with tribe: null and want: 'coin' sells instantly each slow tick at 25% of base value (40% with coinage) — no caravan, no risk. Repeat peddler offers sell every slow tick. Base values per unit: grain/fish/timber/stone 1 - meat/hides/wool/flour/planks/bread 2 - ore/leather/ale 3 - cloth 4 - metal 6 - tools/clothes 8 - weapons 10 - armor 12.

Fog & vision

Settlement vision = territoryRadius + 2; warbands, caravans and wagons 6 (Chebyshev). The explored mask persists per tribe (terrain string shows ? elsewhere). Views include foreign warbands/settlements/caravans/threats inside current vision, plus foreign offers via an own market. sighted events fire per observer/enemy pair at most every 200 ticks. Event delivery: involved (tribe/by/enemy/attacker/defender) OR say OR located inside current vision; ≤60 newest per view.

Score & end

score = pop/10 + 40×settlements + 25×techs + warbandPower/20 + coin/20 + 2×buildingsCompleted (cumulative completions). Elimination at zero settlements AND zero warbands. Winner: last standing, else highest score at the tick cap (tie → null).

Metrics

latestMetrics() returns the frame computed on the most recent slow boundary (and on the final tick). production/consumption are this-interval flows (reset each frame); births/deaths/starvation likewise. Battle/trade/caravan counters are cumulative. toolCoverage is the ‰ of extractive workers covered on the last tick. Consumption counts eaten food, inputs to refinement, wear, build materials, muster gear and warband supply; loot and trade transfers move stores without counting as either.

Documented conventions & deviations

  • Threat raids: raid.by = -1; threat battles: defender: null.
  • Settler wagons (from found) appear in views as caravans carrying their grain supplies; found sources from the own settlement with the largest pop (tie lowest id), min 50 settlers, site ≥6 tiles from any settlement. A blocked arrival merges the wagon into the nearest own settlement.
  • Trade caravans deliver to the destination tile even if the settlement was captured en route (the captor receives the goods).
  • Granary/warehouse both feed one shared per-resource cap.
  • Allocation overrides cannot be unset (set them equal to the tribe value).
  • Research switching is free; cost is paid at completion.
  • Demolishing walls lowers the level; no refunds anywhere.
  • A tribe whose last asset is a settler wagon is still eliminated (spec: settlements AND warbands only).

Tests & performance

npx vitest run in this package: golden replay (seed 123456789, 4 tribes, 128×128, scripted policy/build/muster/trade/research scenario; pinned hashes at ticks 0/100/1000), dual-run identity (hashes + event streams + metrics), apportionment, economy/tools/starvation/population, muster/battle/capture, threats, trade/peddler, fog/spawn fairness, invalid-order no-divergence, metrics sanity, JSON round-trips, and a perf gate: 8 tribes × 10,000 ticks with active policies in well under 10 s (measured ~0.6 s; asserted < 20 s).

Organic territory (claims)

Multi-source bucket Dijkstra from every settlement, recomputed each slow tick. Claim cost per tile: plains/steppe 1, forest/arctic 2, hills/ore/gold 2, swamp 3, mountain 5, water/river/volcano impassable. Budget by tier (TIER_CLAIM_BUDGET = 5/8/12/16 accumulated cost). Ties: earlier-founded (lower id) settlement wins. Census (and thus extraction capacity) derives from claimed tiles; water access stays adjacency-based for docks.

Ages (prestige ladder)

advance burns, drawn across your settlements (ascending id): wandering -> settled: 800 grain, 400 timber, 100 hides; settled -> crafting: 400 bread, 300 planks, 150 metal, 100 cloth, 100 coin; crafting -> high: 800 bread, 300 metal, 60 armor, 300 ale, 400 coin, 150 clothes. Effects per age: +100‰ on every extraction/refinement rate, +3 warband power per soldier, +60 flat score, and the total score is multiplied by (1000 + age x 250)‰.