@tribes/engine
Deterministic v2 simulation engine for Tribes. Implements createWorld: CreateWorld from @tribes/. 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/; 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.Dis 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 clampsacc < 1000(starved capacity is lost, it does not bank). - Allocation across a set uses largest-remainder apportionment
(
src/), ties broken by ascending index/id.apportion.ts - PRNG (splitmix32,
src/) is consumed only by: mapgen (construction), threat placement (phase 11), and ±5% battle variance (rng.ts power × (950 + rng(101)) /, attacker drawn before defender), each in fixed iteration order.1000 - Slow systems run every
SLOW_TICK_INTERVAL = 10ticks ("slow tick" = every step where(tick+1) % 10 == 0). SimEvents are per-step narration, fog-filtered inviewFor(), and NOT part ofstateHash().
Phase order per step()
- Actions → policies — tribes ascending id, first
ACTIONS_PER_TICK(64) orders each; invalid orders dropped silently. - Territory & census (slow) — per-settlement biome census within its territory radius (Chebyshev).
- Labor apportionment — see below.
- Extraction — farmers/fishers/woodcutters/miners (+ tool wear; herders tend herds).
- Refinement & construction — workshop chains in profession enum order, then builders advance the build queue.
- Herds (slow) — growth, fodder, wool, slaughter, horses.
- Logistics — caravans & settler wagons move 1 tile/tick (greedy step, slide-around, ocean impassable, rivers fordable); routes & food equalization (slow).
- Consumption & needs — food, ale ration, cold-biome clothes; starvation.
- Population & morale (slow) — morale drift, births/deaths/emigration, tier upgrades.
- Military — warband movement (ascending id), field battles, camp assaults, raids; sieges & supply (slow).
- Threats (slow) — camp growth, spawn attempt, camp raids.
- Trade, faith & tech (slow) — knowledge/faith accrual, research completion, rite expiry, peddler, offer matching.
- Elimination — a tribe with zero settlements AND zero warbands is eliminated (its caravans/wagons/offers/routes/effects vanish).
- Sightings, metrics —
sightedevents (per observer/enemy pair, 200-tick cooldown);tick++; aMetricsFrameis 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/. 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/) 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/) 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/, −30 morale,50)) starvationevent. ale_ration‰: 1 ale per capita per 10t at 1000‰; fully served → morale bonus.- Cold settlements (on/adjacent
t/a/m): clothes wearpop/per tick; shortage → morale penalty.10000 - 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) / (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‰/; situational = defender terrain (forest +2, hills +3, mountain/volcano +4, swamp −2) and100 + veterancy/ 100 + situational) wallLevel × 5when defending a settlement. - Battles: both powers get ±5% PRNG variance (attacker first). Higher
wins (tie → defender). With
r = winnerPower×1000/: loser losesloserPower min(500, 150 + r/‰ (min 1), winner5) min(300, max(50, 350000/‰. 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.r)) - 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/. 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,10 militia + wall×100 + towers×50 + garrisons raidevent; 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/ 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 movespermille × (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. Quantityq = 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,repeatoffers persist. - Peddler: an offer with
tribe: nullandwant: '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/ (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;foundsources 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)‰.