Appendix A — Methods Appendix
This appendix details the experimental protocol: how determinism is preserved, what noise is injected and why, and how to reproduce each vignette from the shipped scripts.
A.1 Vignette / pilot-script mapping
The body refers to vignettes by the V labels; the pilot scripts that produced the numbers use the D labels — internal pilot identifiers carried over from the BDPD\(^1\) development cycle (Brunelli 2026), where D1–D5 indexed successive single-arena governance pilots. The mapping reflects script-development order (\(V \leftrightarrow D\)), not the narrative order chosen for this paper. Concretely: \(V1 = D1\), \(V2 = D3\), \(V3 = D4\), \(V4 = D5\):
| Body label | Script (scripts/pilot_v10_d*_*.mjs) |
Vignette |
|---|---|---|
| V1 | pilot_v10_d1_cliff_polycentric.mjs |
Collapse isolation across 10 arenas |
| V2 | pilot_v10_d3_yoon_voluntary.mjs |
Reflexive vs voluntary sanctioner (ladder) |
| V3 | pilot_v10_d4_treaty_enforcer.mjs |
World-level treaty enforcer |
| V4 | pilot_v10_d5_exclusion_sweep.mjs |
Junk-arena exclusion |
| V5 (app.) | (card-game tabletop) | Forest of Humbaba — described in Appendix C |
| V6 (app.) | pilot_v10_seed_sweep.mjs |
Noise / seed robustness — see Section A.3 |
The cost table Table A.3 and the seed-robustness ledger Table A.1 both carry a V<i> (D<j>) annotation tying each body vignette to the pilot script that produced its numbers. The ledger additionally lists pilot D2 (graduated sanctioning), a BDPD\(^1\) pilot reused here as a built-in robustness probe that maps to no vignette in this paper.
A.2 Observability noise patch
The built-in substrate is fully deterministic given the specification. The logistic engine’s step method consumes a random-number generator only when useHiddenReserve = true; all mini-pilots in this paper use useHiddenReserve = false. The observability layer consumes the RNG only when at least one variable has noise > 0; the platform default is noise: 0 for every variable. Built-in heuristic agents consult only the observation and their own private state, with no internal RNG. The seed therefore reaches the run but has no behavioural channel through which to leave a trace.
We patched five pilot scripts (pilot_v10_d{1..5}_*.mjs) to set observability.commonsStock.noise = 0.03 per arena. The patch is opt-in to the research scripts: the platform default remains noise: 0 so that production use of the API and UI is unchanged. The 3% noise budget is small enough to leave every qualitative headline visible at \(N = 5\) and large enough to surface seed sensitivity where it exists.
A.3 V6 — Seed-robustness ledger (built-in)
Table A.1 summarises the qualitative-vs-quantitative split across the five built-in mini-pilots.
The headline metric column tags each pilot’s qualitative signal — the discrete or ordinal claim the vignette body actually rests on (commons preserved yes/no, sanction-ladder ordering, number of enforcer fires) — by how reliably that signal reproduces across the five seeds. A metric is flat when every seed produces the same value: the headline number in the vignette body is identical for \(s = 17, 23, 29, 31, 37\). A metric is mild when seeds differ by a small fraction — typically a single unit on a count, or a percentage point or two on a fraction. A metric is sensitive when seed-to-seed variation is large enough that any single seed cannot represent the full distribution, so the vignette body deliberately avoids basing claims on the exact number. The classification is conservative by design: a metric reported as “flat” here must be flat in the underlying data, never just mild.
The quantitative caveat column reports the observed value range, across the same five seeds, for a secondary metric that was sensitive — the depth of the commons stock at \(T_{\max}\), the wealth tail of a specific agent, the precise count of off-arena sanctions. Even when a pilot’s headline is rock-solid (V1: “0/5 cliff arena collapse, flat”) the numerical depth often is not (V1: arena0_finalStock ranges from \(4.0\) to \(9.8\) across seeds). Readers who want to use those numbers downstream — for calibration of a follow-up campaign, for parameter recovery, or for cross-paper meta-analysis — should plan around the range shown rather than around any single seed’s value. This qualitative-vs-quantitative split is the central methodological caveat of the paper’s \(N = 5\) design and is taken up more carefully in Section D.1.
| Vignette (script) | Headline metric (flat / mild / sensitive) | Quantitative caveat |
|---|---|---|
| V1 (D1) | survivors_count 9/9 (flat); arena0 collapse 5/5 (flat) | arena0_finalStock 4.0–9.8 (sensitive) |
| n/a (D2) | grad survA 12, viol 5, sanct 5 (all flat) | grad_finalA 7.2–8.3 (sensitive) |
| V2 (D3) | reflex/voluntary50 violations 16, 15 (flat) | vol50r2_sanct 12–13 (mild) |
| V3 (D4) | enforcer fires 1.0, shock 18.14 (flat) | off_finalA 5.9–8.4 (sensitive) |
| V4 (D5) | excludes 3, junk purity 1.00 (flat) | on_finalM1 38.2–39.2 (mild) |
The pattern is consistent across the substrate: institutional events (collapse yes/no, sanction-fires count, exclude count) are noise-robust; depth-of-cliff metrics for arenas at or below the threshold \(\theta\) are noise-sensitive.
The full per-seed values live in data/pilot/v10_seed_sweep/aggregate.json.
A.4 Agent SDK
Pre-LLM approaches to multi-agent commons used deep reinforcement learning, where agents learned appropriation strategies through trial and error alone (Pérolat et al. 2017). All LLM players in V2–V4 are Python subprocesses launched by the BDPD Agent SDK (agents/bdpd_agent.py). The SDK provides:
- A
@tooldecorator that auto-generates OpenAI-compatible JSON Schema from Python type hints and docstrings; - A tool-call loop that marshals model responses, dispatches tool invocations via an
on_tool_callcallback, and re-injects results into the conversation; - Two engine-agnostic starter tools shipped with the platform:
query_history(read past turns) andcompute_trend(moving-average slope on any metric).
The SDK targets any OpenAI-compatible chat-completion endpoint; all LLM results in this paper use DeepSeek’s implementation.
A.5 LLM cost calibration
The three LLM N=5 sweeps reported in Chapter 3 used DeepSeek-flash with the parameters listed in Table A.2.
| Parameter | Value |
|---|---|
| Model | deepseek-v4-flash (no chain-of-thought) |
| Temperature | 0.4 |
| Max tokens / response | 256 |
| Max history per agent | 32 turns |
| Prompt template | Platform default with archetype nudge |
| Archetype tags | conservative (cooperators) and aggressive (defector) |
Thinking mode. DeepSeek v4-flash and v4-pro support an optional thinking parameter that surfaces chain-of-thought reasoning alongside the final answer. An empirical probe on the BDPD substrate (examples/agents/probing_test.py) found that thinking improves performance only on metacognitive tasks (recognising ambiguity in partial-information settings) and provides no benefit — or slight degradation — on numerical harvest decisions. At current pricing, a thinking-enabled flash run costs \(\approx 2\times\) a non-thinking run; a v4-pro thinking run costs \(\approx 40\times\) the flash baseline. All results in this paper use thinking: { type: "disabled" }.
The total token usage and realised cost are in Table A.3.
| Pilot | Calls | Input tokens | Output tokens | Upper $ (list) | Realised $ |
|---|---|---|---|---|---|
| V2 (D3) | 2 015 | 17.7 M | 304 k | 5.11 | ~0.30 |
| V3 (D4) | 3 030 | 23.6 M | 406 k | 6.81 | ~0.40 |
| V4 (D5) | 3 284 | 25.4 M | 509 k | 7.41 | ~0.45 |
| Total | 8 329 | 66.7 M | 1.22 M | 19.33 | ~1.15 |
The realised cost is approximately 6% of the list-price upper bound. The discrepancy is the DeepSeek prompt-cache rebate: the platform prompt’s system and tool-schema preamble — the bulk of every input — caches with high hit rate across calls within a pilot run.
Two caveats. First, the 6% multiplier is a snapshot of DeepSeek’s pricing at our run date; provider rebate policies change and a campaign-scale follow-up should re-calibrate. Second, the realised cost was computed from the per-agent trace files (data/pilot/v10_*_llm_n5/traces/seed_*/*.jsonl) by summing the usage.prompt_tokens and usage.completion_tokens fields and applying the list price as if no cache rebate applied, then comparing the result to the user-reported actual billed amount for a single-seed smoke. The ~6% ratio is consistent across the three pilots, but readers extending this work should not rely on it without an independent check. (The Calls column is the trace-level API-request count, including tool round-trips, and so exceeds the per-decision tally reported in each pilot’s aggregate.json.)
A.6 Reproducibility
To reproduce V2–V4 with a fresh DeepSeek key:
# V2 — D3 voluntary sanctioner ladder
DEEPSEEK_API_KEY=sk-... \
node scripts/pilot_v10_d3_llm_n5.mjs
# V3 — D4 treaty enforcer
DEEPSEEK_API_KEY=sk-... \
node scripts/pilot_v10_d4_llm_n5.mjs
# V4 — D5 exclusion + junk arena
DEEPSEEK_API_KEY=sk-... \
node scripts/pilot_v10_d5_llm_n5.mjsFor V1 and V6 (built-in only, no API key needed):
# V1 / V6 — seed-robustness sweep across D1–D5
node scripts/sweep_v10_seed.mjsEach script accepts PILOT_SEEDS=17,23,29,31,37 (LLM) or PILOT_SEED=17 (built-in) to override the default sweep range; output JSON artefacts land under data/pilot/v10_*/seed_<s>/. The aggregate files at data/pilot/v10_*/aggregate.json carry the per-cell mean, standard deviation, min, and max used in the vignette tables.
A.7 Perturbation surface inventory
The platform exposes a set of engine-agnostic perturbation types. Adding a new engine does not require extending any perturbation. Table A.4 lists each type with the paper that first exercises it.
engine/<kind>.js.
| Perturbation type | Trigger | Effect | First exercised |
|---|---|---|---|
capacity_shock |
turn \(\geq T\) | sets commons capacity \(K\) to a ceiling value | BDPD\(^0\) (P-sweeps) |
regen_shock |
turn \(\geq T\) | shifts regeneration rate \(r\) | BDPD\(^0\) (P-sweeps) |
observability_flip |
turn \(\geq T\) | toggles observation noise on/off mid-run | BDPD\(^0\) (appendix) |
threshold_shift |
turn \(\geq T\) | moves the collapse threshold \(\theta\) | BDPD\(^0\) (appendix) |
sanction |
pact_violation |
deducts a fixed amount from violator wealth | BDPD\(^1\) (C1) |
sanction_graduated |
pact_violation |
deducts a ladder-indexed amount per ordinal | BDPD\(^1\) (D2, D3) |
exclude |
pact_violation |
migrates violator to a designated junk arena | BDPD\(^2\) (V4) |
capital_shock |
governance trigger | scales capital \(C\) by a factor | BDPD\(^3\) (OFAT sweep) |
wealth_shock |
governance trigger | scales player wealth by a factor | BDPD\(^3\) (lever) |
A.8 Mini-triage parameters
The conclusions sketch a mini-triage — one cell per road — used to bound the three campaign candidates before committing to a full sweep. Table A.5 fixes the cell parameters so the triage is reproducible from the same toolchain as the main sweeps. Each cell is a single seed (\(N = 1\)), chosen to confirm or deny that the road’s candidate effect is present at all on the current substrate.
| Cell | Anchor | \(T_{\max}\) | Substrate | Agents | Seed |
|---|---|---|---|---|---|
| A.0 — Seneca cascade | S2 (3 linked arenas, 1 emitter + 2 receivers, no governance) | 30 | Seneca; Link.type='pollution', target \(\geq\) 8 turns between emit and cliff |
built-in conservative + 1 aggressive | 17 |
| B.0 — Junk contagion | V4/D5 LLM, same Link/exclude config |
60 | logistic, same as V4 | DeepSeek-flash; 1 defector + 2 cooperators per arena, 3 mains + 1 junk | 17 |
| C.0 — Treaty asymptote | V3/D4 LLM, same treaty + enforcer | 100 | logistic, same as V3 | DeepSeek-flash; 1 defector + 4 cooperators; treaty cap_8 |
17 |
Read-out rule for each cell:
- A.0 passes if (i) the downstream cliff is reached between turns 8 and 25 (cascade is detectable but not instantaneous), and (ii) the upstream emitter’s \(P\) coordinate crosses a flaggable threshold at least 4 turns before the first downstream cliff.
- B.0 passes if the junk-purity timeseries either plateaus between turn 30 and turn 60 (population equilibrium reached, mean of last 20 turns within \(\pm 0.05\) of the V4 mean) or drops below 0.3 (contagion takeover). An intermediate, still-decaying trajectory is a fail — the campaign would need longer horizons before B can be read.
- C.0 passes if the enforcer-fire rate per 10 turns, averaged over turns 70–100, is within \(\pm 30\)% of the same rate averaged over turns 40–60 (asymptote reached); fails if the late-run rate is still > 1.5\(\times\) the mid-run rate (limit-testing has not converged).
All three cells use seed 17 because it is the project’s pilot anchor (the seed under which D1–D5 were first read). The total triage budget is under $5 and one development day (\(\approx\) 4 h for Cell A’s Link.type='pollution' patch, \(\approx\) 2 h to run all three cells, \(\approx\) 2 h to read the outcomes against the rules above).