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.

Table A.1: Built-in seed-robustness ledger (N=5, noise=0.03).
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 @tool decorator 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_call callback, and re-injects results into the conversation;
  • Two engine-agnostic starter tools shipped with the platform: query_history (read past turns) and compute_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.

Table A.2: DeepSeek-flash parameters for V2–V4 LLM replications.
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.

Table A.3: Token usage and realised vs list-price cost for the LLM N=5 sweeps.
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.mjs

For V1 and V6 (built-in only, no API key needed):

# V1 / V6 — seed-robustness sweep across D1–D5
node scripts/sweep_v10_seed.mjs

Each 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.

Table A.4: Platform perturbation surface. All types are engine-agnostic: they operate on arena-level or player-level state without touching 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.

Table A.5: Mini-triage cells: one seed per road, fixed parameters, total budget under $5.
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).