Document iteration: 2 (a living document). Date: 2026-10-12. Merges the iteration 2 addendum (owner review of the flow budget + decisions). See the changelog at the end. Later iterations revise decisions, so always read the latest iteration and its changelog before starting.
This is the single repository file holding natural-language specification, architecture, decisions
and progress (governance rule, section 0 item 5). Machine-readable artifacts (the flow-budget
CSV/XLSX, Go source, metrics) live beside it. There must not be separate PROGRESS.md,
DECISIONS.md or report markdown files.
Status tags used throughout - [DECIDED]: stated or accepted by the project owner. - [PROPOSED]: suggested during design discussion, plausible default, not yet confirmed. Implement unless told otherwise, but keep it easy to change. - [OPEN]: explicitly undecided. Do not hard-code; keep the code flexible.
Executing agent: DeepSeek Flash 4.1 [DECIDED]. It should spin up subagents for context management [DECIDED]. The suggested working method below is [PROPOSED] and can be adjusted.
Policy interface, graph structure, config struct, and snapshot format must not be changed by a subagent without reporting it to the main agent, which records the change in the decision log.PROGRESS.md / DECISIONS.md / report files. Keep it a living document: revise decisions in place, bump the iteration, and add a changelog line.The report is served over HTTPS by the shared Caddy instance at https://nn-village-sim.bernie-dev.de/ (see section 0.10). Every work session must close by regenerating that site from this spec and publishing the latest results there, and by updating the fresh-session handover (Appendix D). The public page is the owner's cockpit: current milestone status, flow-budget results, test evidence, and the open questions.
/home/bernie/www/nn-village-sim on the host, mounted into the Caddy
container at /var/www/nn-village-sim; the site config is
/root/caddy/sites/nn-village-sim.caddy (a file_server, encode gzip).python3 budget/build_site.py (it renders this SPEC.md plus the
flow-budget CSV/XLSX into that directory). Reload Caddy only when the site config itself changes:
docker exec caddy caddy reload --config /etc/caddy/Caddyfile.*.bernie-dev.de already points at this host, so no DNS change is needed.A 2D village simulation in which a small population of agents, each controlled by a small neural network (NN), lives in a resource economy that grows in complexity stage by stage. Agents have diverse skills, work on production chains, and are exposed to rare disasters.
The long-term vision is a townscape-style visual UI filled with life by the simulation, rendered later in the Godot engine. The UI is out of scope for now [DECIDED]. The immediate work is the simulation backend, the economy, and a deterministic scripted decision engine that stabilizes it.
Out of scope for now: UI/rendering, procedural town maps, trade/money, a townhall voting system, free-form agent communication, reinforcement learning. See section 13.
rand.New(rand.NewSource(seed))); never the global RNG.time.Now() or wall-clock use in simulation logic; use a fixed tick counter.x*y+z on some architectures. For cross-machine bit-identical results, wrap products in explicit float32(...)/float64(...) conversions, or compare runs on one architecture only.pprof after each new mechanic.Decision: do not settle on procedural town maps yet. Build the simulation on a graph abstraction and use a square 2D grid generator as the first (and for now only) map source.
Graph:
nodes: [{id, x, y}]
edges: [{id, from, to, travel_time}] # time-based cost, not cell counts
sites: [{id, type, node}] # trees, field tiles, well, building lots...
lots: [{id, node, neighbors: [lot_id]}] # explicit adjacency for fire spread
Rules: - Agent logic must not assume four neighbors, uniform edge cost, or integer coordinates. - Agent position is a node or an (edge, progress) pair, also on the grid, so the snapshot format stays stable when the map type changes. - Pathfinding: A using node coordinates for the heuristic. Cache distance fields (flow fields) per target type (nearest tree, well, brewery, ...) and invalidate on map change. - Use a spatial index or incremental counters for "nearest X" queries. Avoid scanning all objects. - A coarse graph is acceptable [PROPOSED]: one node per place (forest patch, field, house lot, junction), edges as roads with travel times. Economic behavior depends on distances, capacities and timing, not fine geometry. - Adjacency between lots is stored explicitly* (needed for fire spread) and is independent of walking edges.
For a later organic townscape look, known open-source references (read for algorithms and reimplement in Go; check licenses):
- Watabou's Medieval Fantasy City Generator, source repo TownGeneratorOS (Haxe; license unclear, check).
- ProbableTrain MapGenerator (TypeScript, tensor-field street networks, GPL-3.0; copying code into a distributed project can impose GPL).
- Literature: Parish and Muller, "Procedural Modeling of Cities"; Chen et al., "Interactive Procedural Street Modeling".
Both reference projects use fine grids for hundreds to thousands of buildings; the simulation graph can be much coarser.
Chain 1: Food and drink 1. A field tile is tilled, sown, grows (world timer), is harvested. 2. Wheat is carried to a silo. 3. Wheat is carried from the silo to the brewery; water is carried from the well to the brewery. 4. Beer is brewed (world timer). 5. Beer is carried to the inn, where it is drunk.
Beer counts as food [DECIDED]. Wheat is not eaten directly. Beer is the village's only food, so this chain is survival-critical.
Chain 2: Housing 1. Wood is chopped (trees regrow, world timer). 2. Logs are carried to a building site. 3. A house is built; agents can sleep in it. 4. Buildings decay over time and need repair (consumes logs).
Crop growth, tree regrowth, beer brewing, building decay. All seeded and deterministic.
Buildings have finite capacity for people and for items: - House: N beds. - Inn: limited seats and service rate; drinking only works while a server is present (see 5.4). - Brewery: one batch at a time (batch size and brew time as parameters) [PROPOSED]. - Silo: finite wheat capacity. - Shelter during storms uses free capacity in any building (see section 11).
The six professions the owner named (farmer, carrier, innkeeper, brewer, wood chopper, builder) map to six task families. Professions are emergent labels, not hard slots [PROPOSED]: every agent can perform every task with a skill coefficient; "brewer" is a label derived from what an agent mostly does. Reason: a hard role system in a small village stalls whenever one role is unfilled.
| Task | Needs | Produces | Skill coefficient |
|---|---|---|---|
| farm | a field tile | stage advance (till, sow, harvest), wheat | farming |
| chop | a tree | logs | strength/axe |
| haul | goods at A | goods at B | carrying (speed, capacity) |
| brew | wheat + water at brewery | beer after timer | brewing |
| build (incl. repair) | logs at site/building | house progress / repair | building |
| serve | inn with beer | makes drinking possible, its output is the service [DECIDED] | serving |
Need actions (not jobs): sleep (house with free bed), eat/drink (inn with beer and an attendant).
Simplifications to keep the network's output count small (about 8) [PROPOSED]:
- farm is one task; the executor performs whichever stage the tile needs.
- haul is one task; the executor picks the most urgent flow (destination demand, source surplus, distance).
- Growth/brewing timers are world processes, not tasks. Agents never idle through them; they pick another task.
type Policy interface {
Decide(obs []float32, mask []bool) (scores []float32) // masked argmax or softmax picks the task
}
Implementations: scripted, random, NN, NN-residual-on-scripted. Anything implementing it can also be a teacher for imitation. Keeping this interface small is how evolution-vs-RL stays undecided.
Each workplace exposes a visible demand/urgency value (brewery lacks water, house needs logs). These are what let agents coordinate and prevent deadlocks. They are also the natural seed for prices if trade is ever added.
Deterministic. Sketch [PROPOSED]: - Need overrides first: if a need is below a threshold, eat/sleep. - Otherwise score each task as urgency x skill coefficient / distance; pick the max; break ties by agent id. - No randomness except via the seeded RNG. Purpose: (a) make the world tunable, (b) baseline, (c) teacher for NNs, (d) fallback. Also build a random-task baseline as a lower bound. A well-tuned world shows a clear gap between random and scripted.
[]float32) plus layer sizes. No NN framework needed: roughly 10^5 to 10^6 weights total, microsecond inference, decisions only on idle.fitness = lifetime-averaged needs satisfaction + beta * village_welfare + gamma * own_contribution.beta is a fixed design knob at first. Do not make it heritable under purely individual selection, because selfishness would drive it to zero.Agents have a lifespan so scripted agents die out naturally. - Stagger founders' initial ages across the lifespan to avoid a synchronized die-off [PROPOSED]. - Vary lifespans per agent (mean +/- ~20%) [PROPOSED]. - Lifespan must be much longer than apprenticeship (at least 10 to 20x) [PROPOSED]. - On death, carried goods are dropped at the location (conservation). Log the cause: age, disaster, starvation.
Selection pressure comes from who is chosen as teacher: random teachers give drift, successful teachers give evolution.
1. Newcomer spawns at the map edge with random skills and walks to the village.
2. It samples K nearby villagers (K about 3 to 5) and picks the one with the best smoothed recent welfare (adjusted for skill); softmax for diversity.
3. Weight copy + Gaussian mutation (simplest; requires identical architectures). The newcomer keeps its own skills.
4. About 10% "innovators" with large mutation or random start.
5. Apprenticeship phase for random-start newcomers: for the first T decisions the agent acts on the teacher's decision (teacher policy evaluated on the newcomer's own observation) or the scripted decision, while its own network trains in the background by behavior cloning. Hand over control once agreement with the teacher exceeds about 90%.
6. Use behavior cloning instead of weight copy when architectures differ (needed as networks grow between stages).
7. A teacher is anything implementing Policy, so scripted agents can be the first generation of teachers.
- Optional visual flavor for the future UI: the newcomer follows its teacher; teaching requires proximity.
- Which immigrant network source is best (random, mutated pretrained, sampled from an evolved pool) is [OPEN]: to be tested as an experiment after everything else is stable. Owner leans toward random newcomers with apprenticeship, accepting slower learning.
Pitfalls to guard against: - Monoculture (single lineage; missing skills like storm response vanish): mutation, innovators, mixing several teachers, a small fresh-lineage immigrant share. - Telephone-game degradation over generations: track agreement with the scripted policy across generations. - Noisy success signal: measure over a window; if best-of-K teachers do not beat random teachers, the signal is uninformative.
In earlier simulations the owner observed populations grow until all resources were depleted instead of reaching an equilibrium. Brakes built into this design: - Building upkeep (houses consume wood; wood regrowth is finite) [DECIDED observation, mechanism PROPOSED]. - Conditional immigration and automatic emigration (9.1). - Density-dependent fire spread (section 11). - Capacity limits (beds, seats, silo, brewery).
The flow budget was done before coding parameters (iteration 1, revised in iteration 2; see
Appendix A and budget/flow_budget.py). The owner's iteration-2 review added requirements the
budget must model [DECIDED: owner]:
work_fraction, slower hauling, real map distances. Do not add goods
or tasks to fix it.The inn is the master population dial for the early stages [DECIDED]. One inn sets the population. A second inn as a later expansion is possible but must not be implemented now.
The flow budget's acceptance criteria (iteration 2) and its results live in Appendix A.
Implement chain by chain. Design the inputs/outputs for all six tasks up front, but validate each chain with the scripted baseline before adding the next.
Policy interface, scripted and random policies.beer_per_agent_day fixed at 1.0; ticks_per_day 600 and work_fraction ~0.5; foraging as a config switch default off; 10–12 staggered founders with ~5 days of beer and a wheat buffer. Revised the flow budget (Appendix A) to add travel time, inn staffing, and a wood/tree model; updated sections 0, 5.5, 9.1, 10. Consolidated all governance into this single file (appendices A–D).The pre-code steady-state accounting required by section 10. Source of truth is
budget/flow_budget.py; outputs are budget/flow_budget.csv, budget/flow_budget.xlsx.
Reproduce with python3 budget/flow_budget.py.
Method. Flows in units per in-game day. Labor = work lines + travel lines. Haul capacity is
derived from travel (carry_cap / weighted-average round-trip), so distances consume labor. A
population is feasible when utilization ≤ 1, feed margin ≥ 0, and beds ≥ P. The paper model
ignores latency and queuing, so it is an upper bound on feasibility and a lower bound on labor
(owner's review, section 10); it is validated against measured M3 runs.
Starting parameters (all [PROPOSED] until measured runs confirm them).
| Parameter | Value | Parameter | Value |
|---|---|---|---|
ticks_per_day |
600 | inn_service_slots × serve_rate_per_server |
2 × 10 = 20 beer/day |
work_fraction |
0.5 | target population (gate) | 12–14 (ref 13) |
beer_per_agent_day |
1.0 | immigration beer gate | > 3 days supply + free beds |
carry_cap |
10 (≈ 50 items/day at these distances) | tree_count_map |
8 (≈ 2× the sustainable need) |
ticks_per_edge / distances |
15 ticks; field 5, tree 7, build 6, well/brewery/silo 4, inn 3 | house_wood_cost, building_decay_days |
20 logs, 20 days |
| founders / buffers | 10–12; ~5 days beer + wheat buffer | foraging yield | 0 (switch off) |
Results at the target population (P = 13, foraging off).
| Quantity | Value | Acceptance target | Met |
|---|---|---|---|
| Labor utilization (demand / supply) | 0.843 (5.48 / 6.50 agent-days) | 75–90% | yes |
| Feed margin (inn cap − demand) / cap | 35% | ≥ 15% | yes |
| Wood demand / regrowth supply | 0.42 (4.0 / 9.6 logs/day) | ≈ half | yes |
| Bed margin | 3 beds | ≥ 0 | yes |
| Inn staffing line | 1.45 agent-days (26% of labor) | explicit | yes |
| Haul labor (travel-derived) | 0.82 agent-days | — | — |
| Foraging on/off comparison | shown (below) | required | yes |
Feasibility by population (foraging off).
| P | utilization | feed margin | bed margin | wood frac | feasible |
|---|---|---|---|---|---|
| 10 | 0.97 | 50% | 2 | 0.31 | yes |
| 11 | 0.91 | 45% | 1 | 0.31 | yes |
| 12 | 0.85 | 40% | 0 | 0.31 | yes |
| 13 | 0.84 | 35% | 3 | 0.42 | yes |
| 14 | 0.80 | 30% | 2 | 0.42 | yes |
| 15 | 0.76 | 25% | 1 | 0.42 | yes |
| 16 | 0.73 | 20% | 0 | 0.42 | yes |
| 18 | 0.85 | 10% | 2 | 0.52 | yes |
| 20 | 0.79 | 0% | 0 | 0.52 | yes |
Foraging switch. Turning foraging on (assumed yield 0.3 beer/agent/day) lowers beer demand, so at P = 13 utilization drops to 0.77 and the feed margin rises to 55%. Default remains off to keep the budget honest (section 5.5).
Findings. - The inn remains the master dial (cap 20 beer/day); the immigration gate at 12–14 keeps the village ~30–35% below the feeding cap, so it never rides the hard limit. - Travel time is what makes labor tight: without it the same parameters leave a large surplus (iteration 1, ~64% idle). With time-based edges the target sits at ~84% utilization, inside the owner's 75–90% band. - Serving is the single largest labor line (~26%), so inn staffing is budgeted explicitly. - Wood is now a visible brake: demand is ~42% of regrowth for a map with 8 trees; the initial four houses also cost a one-off 80 logs. Tree count is an explicit config parameter.
Open questions carried to the owner (do not decide): confirm the exact inn cap (18 vs 20) and
whether work_fraction 0.5 or 0.55 feels right; the paper model must be reconciled with measured M3
runs before any parameter is frozen.
Decisions made during implementation, beyond the spec body. Newest first.
cmd/sweepviz: runs a sweep plus hand-picked extremes (hand-tuned, starved, strained),
captures daily metric series, scores each run (survival + utilization − deaths − emigration),
and writes budget/sweep_series.json with the best/median/worst cases.budget/sweep_charts.py, no JavaScript or chart
library) at /sweep.html: population, labor utilization, beer and wheat over time, plus a
parameter table. Chosen over a JS charting dependency to keep the site and the Go build
dependency-free.-jobs values (verified 1 vs 4).go test.
It found and drove the fix for a carried-item conservation leak (see M6 evidence).decide (SPEC 6.2), not policy
outputs, so Policy stays at 8 outputs.EmigrateUnmetDays; starvation death at twice that.reapAgents; IDs stay unique via nextID, and occupancy is keyed
by agent ID, so removing slice entries is safe.Ledger.Produce so conservation
holds when the brewer consumes it. Flow priority: beer > water > wheat > logs.innAttendedUntil);
the serve task keeps the inn attended for a shift. This implements SPEC 5.3.2 × daily beer demand.farm task is one executor that performs the stage the target tile needs (SPEC 5.4); the tile
stage machine is dormant → tilled → sown → ripe → dormant. Sown (growing) tiles are excluded from
the "needs work" set, so agents pick other tasks during the growth timer.WheatPerHarvest wheat (ledger) and the farmer carries it to the silo; surplus
above carry capacity drops on the ground for a hauler.PlanStage/PlanKind) with an explicit Dest
node; routeTo re-paths only when the destination changes (fixes a stall where agents kept an old
stale path).ForagingYield is the hunger restored per
trip. Calibration is deferred to M3 [PROPOSED].nnvillage, Go 1.22, standard library only (no external dependencies).internal/config, internal/world, internal/sim, internal/policy, cmd/village.
The Policy interface lives in internal/policy so sim depends on it, not vice versa.Edge with two adjacency entries); the spec's
{id, from, to, travel_time} shape is preserved and travel time is symmetric for the grid.labor_utilization and per-task tick counts now; at M0 all ticks are idle.DECISIONS.md, PROGRESS.md, FLOW_BUDGET_REPORT.md,
CLAUDE_HANDOVER.md and the addendum markdown.work_fraction 0.5, haul carry_cap 10 (travel-derived), inn cap 2×10 = 20/day, target
population 12–14, tree_count_map 8. All [PROPOSED], validated at M3.budget/build_site.py.SPEC.md committed first.user.email existed). No global git config changed..gitignore for Go build artifacts, sweep output, profiling.Milestone status (roadmap section 12). Update after every step.
| Milestone | Description | Status |
|---|---|---|
| — | Spec iteration 2 + flow-budget re-run (Appendix A) | done |
| M0 | Skeleton: tick loop, per-run RNG, graph + grid generator, config struct, metrics, determinism + conservation tests, Policy interface, scripted + random policies |
done |
| M1 | Housing chain: chop, haul, build, sleep; regrowth, decay, repair; foraging switch | done |
| M2 | Wheat chain: farm stages, growth timer, silo, haul | done |
| M3 | Beer chain: well, water haul, brewery, inn, serve; beer as food; flow budget checked vs measured | done |
| M4 | Population dynamics: staggered lifespans, immigration/emigration, death causes | done |
| M5 | Disasters: fire (adjacency spread), storm (warning + shelter), scenario harness | done |
| M6 | Sweep tooling: sampling, seeds, early kill, regression suite | done |
| M7 | NN agents (out of scope for this run) | not started |
| M8 | Handover experiments (out of scope for this run) | not started |
| M9 | Snapshot/replay + Godot client (out of scope for this run) | not started |
Current run scope: M0–M6, autonomous, Go stdlib-first. Run complete.
M0 evidence (2026-10-12). Go module nnvillage, Go 1.22, standard library only.
Packages: internal/config (all Appendix A parameters + ForagingYield switch), internal/world
(graph, grid generator, Dijkstra distance fields, A*, sites + explicit lot adjacency),
internal/sim (tick loop, seeded RNG, founders with staggered ages, ledger, metrics + state hash),
internal/policy (Policy interface, scripted + random), cmd/village (CLI).
go build ./..., go vet ./... and go test ./... pass. Two runs at -seed 12345 -ticks 3000
produce byte-identical JSON metrics and identical state hash (determinism test), and the ledger
conserves items (conservation test). At M0 agents are idle by design (executors arrive in M1), so
reported labor utilization is 0.
M1 evidence (2026-10-12). Mutable world state (internal/sim/state.go): trees with regrowth
timers, fields with growth stages, buildings with condition + occupants + repair logs, build sites,
named stores (yard/silo/brewery/inn) and ground piles. Executors (tasks.go) implement chop, haul,
build/repair, sleep and eat with pathfinding and time-based walking; the scripted decision scores
urgency × skill / distance with need overrides. Tree regrowth and building decay are world timers.
The foraging switch is wired end-to-end (default off; forage at the nearest field/forest when
hungry and no beer). Verified: at seed 12345 over 45 days the village holds 5 houses at mean
condition ~0.97 with trees cycling (no wood deadlock), logs are conserved, and two identical runs
produce byte-identical metrics. go test ./... green, including conservation over 6000 ticks and a
foraging on/off test.
M2 evidence (2026-10-12). The farm task advances a field through till → sow → (world-timer growth) → harvest, then carries the wheat to the silo. A growing (sown) field is not offered as work, so agents do not idle through growth timers. Verified: at seed 7 the silo wheat rises 20 → 56 → 122 over 12 days while labor utilization stays in the 0.74–0.93 band. Conservation and determinism tests stay green.
M3 evidence (2026-10-12, + flow-budget check). The beer chain is complete: the well is infinite
(water is created through the ledger on pickup), a general haul task moves wheat silo→brewery, water
well→brewery and beer brewery→inn, the brewer loads a batch (inputs consumed, timer set) and the
timer produces beer, and the innkeeper's serve shift makes drinking possible (SPEC 5.3: no server,
no drinking). Beer is the only food.
Measured vs paper budget (30 days, seeds 1/2/3/42/99): labor utilization 0.86–0.87 against
the paper budget's 0.84 at P=13; serving is the largest labor line, as predicted; population
holds at 11–12 with 5 houses at condition 0.90–0.98; beer/wheat/water cycle without unbounded growth
(demand caps on brewing and farming). go test ./... green, including the beer-chain end-to-end test
and conservation.
M4 evidence (2026-10-12). Population dynamics (population.go): a daily-day boundary update
computes a smoothed days-of-beer-supply EMA; immigration requires free beds and > 3 days of
smoothed supply, with a 3-day bootstrap warm-up so the initial buffer does not pull in newcomers
early. Emigration is automatic after EmigrateUnmetDays of unmet needs; starvation death is a later
fallback; age death at lifespan. Carried goods drop on the ground and beds are freed, so
conservation holds across births/deaths (no births). Verified: seeds 42/7/1/99 all settle at
population 14 (founders 12 + 2 immigrants) with no deaths or emigrations over 60 days, houses at
~0.97 condition and utilization 0.82–0.90; dedicated tests cover immigration, emigration and age
death.
M5 evidence (2026-10-12). disasters.go implements fire and storm, both off by default
(probability 0) so ordinary runs and their determinism baselines are unaffected; the scenario harness
forces them via IgniteRandomHouse/IgniteBuilding/StartStorm (also CLI -fire and -storm).
Fire spreads over the explicit lot adjacency with density-scaled probability, kills occupants, and is
extinguished when agents haul FireWaterToExtinguish water from the well (an emergency task that
preempts normal decisions). Storms emit a warning longer than a typical trip to shelter; agents take
the internal shelter task (not a policy output) into any building with free capacity, and
unsheltered agents/trees die with the configured probability. Tests cover spread, occupant death,
shelter success under a long warning, boosted-frequency rolls, and conservation. M5 decisions:
disasters are opt-in (so they never perturb the tuned equilibrium), and the storm/fire responses are
emergency overrides rather than policy outputs, keeping the Policy vector at 8.
M6 evidence (2026-10-12). internal/sweep samples bounded parameter sets with a low-discrepancy
Kronecker sequence over six world knobs (work fraction, serve rate, beer buffer, tree count, carry
cap, house wood cost), runs each set over multiple seeds one-sim-per-goroutine, early-kills dead
villages, and writes CSV keyed by config hash, seed and state hash. cmd/sweep exposes it:
-runs, -seeds, -days, -jobs, -out. Verified worker determinism: 10 sets × 5 seeds ×
5 days produces byte-identical CSV at -jobs 1 and -jobs 4 (50 rows). A regression suite
(internal/sim/regression_test.go) runs per-stage benchmarks (housing sustains, full economy
survives, halved-yield survives) with survival/utilization/conservation checks. The suite caught a
real conservation leak — abandoning a task while carrying goods destroyed them — which is now fixed
by dropping carried items on clearTask. M6 decisions: sweeps are deterministic by construction
(independent runs, output sorted by run/seed); the regression suite is the guard against behaviour
regressions per stage.
Operational facts a fresh session needs. Updated at the close of every work session (section 0.7).
What this project is. A headless, deterministic 2D village economy simulation in Go, where a small population of agents works a two-chain economy (beer/food and housing), eventually driven by small neural networks. The long-term UI is a Godot townscape, out of scope. The binding process rules are in section 0; the design is the body of this file; the current numbers are in Appendix A.
Repository layout.
nn-village-sim/
├── SPEC.md # this file: spec + architecture + decisions + progress + handover
├── go.mod # module nnvillage, Go 1.22, standard library only
├── cmd/
│ ├── village/main.go # run one sim; -seed -ticks -json -fire -storm
│ ├── sweep/main.go # parameter sweeps; -runs -seeds -days -jobs -out
│ └── sweepviz/main.go # scored runs + daily series -> budget/sweep_series.json
├── internal/
│ ├── config/config.go # all world parameters + Hash()
│ ├── world/ # graph, grid generator, Dijkstra/A*, sites, lots
│ ├── policy/policy.go # Policy interface; scripted + random
│ ├── sim/ # sim loop, state, tasks, needs, population, disasters, metrics, tests
│ └── sweep/sweep.go # sampling, parallel execution, early kill, CSV
└── budget/
├── flow_budget.py # iteration-2 budget generator (writes CSV/XLSX)
├── flow_budget.csv # canonical computed output
├── flow_budget.xlsx # spreadsheet (parameters + derived)
├── sweep_series.json # daily metric series for best/average/worst sweep runs
├── sweep_charts.py # renders sweep_series.json to inline SVG
└── build_site.py # renders SPEC appendices + sweeps to the public site
Reproduce the budget. python3 budget/flow_budget.py. Build-time Python deps: openpyxl,
markdown (installed via pip install --user --break-system-packages). The CSV is canonical.
Public URL. https://nn-village-sim.bernie-dev.de/ (Caddy file_server on the /var/www
mount; config /root/caddy/sites/nn-village-sim.caddy). Regenerate with
python3 budget/build_site.py; reload Caddy only when the site config changes. Every session must
republish here before finishing.
Verify. go test ./... (determinism, item conservation over 6000 ticks, beer-chain end-to-end,
foraging switch, immigration/emigration/age-death, fire/storm scenarios, sweep worker determinism, and
the per-stage regression suite). Also: go vet ./..., gofmt -l . (empty),
go run ./cmd/village -seed 12345 -ticks 3000 -json twice → identical output;
go run ./cmd/sweep -runs 10 -seeds 1,2,3,4,5 -days 5 -jobs 1 vs -jobs 4 → identical CSV.
Regenerate the charted runs with go run ./cmd/sweepviz -runs 6 -seeds 1,2,3 -days 30 -jobs 4
(writes budget/sweep_series.json), then python3 budget/build_site.py.
Do not decide [OPEN] items (section 16); collect them for the owner in this handover. Prefer Go standard library; log any added dependency. Use subagents for focused implementation tasks.
Where the last session left off (run complete). Spec is at iteration 2 and its flow budget passes
all acceptance targets (utilization 84% at P=13). M0–M6 are all complete and tested, Go standard
library only:
- M0 skeleton, M1 housing chain, M2 wheat chain, M3 beer chain (scripted two-chain economy at a stable
equilibrium; measured utilization 0.86 matches the 0.84 budget), M4 population dynamics (village
settles at population 14), M5 disasters (fire + storm, opt-in, scenario harness), M6 sweep tooling +
regression suite (deterministic across workers; the suite caught and fixed a conservation leak).
- Next actions (NOT started, for the owner to prioritise): M7 (NN agents: MLP policy, decision
logging, imitation pretraining, residual policy) and M8 (handover experiments); M9 (snapshot/replay
+ Godot) is a separate project. Nothing beyond M6 was built.
- Open questions for the owner are in section 16 and are unchanged: training method (evolution vs
RL), immigrant network source, pride/village-welfare weights, townhall vote, and final numeric
parameters. Also worth a decision: whether the immigration gate should target 12–14 explicitly or
stay purely margin-based, and calibration of the disaster probabilities.
- Regenerate the public site with python3 budget/build_site.py; run go test ./... before any change.