feat(simulation): add economics simulation binary with Layer 1+2 (#806, #807)

Adds tooling/econ-sim — a standalone Rust binary for the Phase 2 economics
simulation:

Layer 1 (Leontief production, #806):
- Deterministic per-run PRNG seeding of corp×site productivity (D-176)
- Fixed-coefficient production chains; scarcity cascades downstream (D-178)
- Per-capita population demand for finals and services
- Gate-energy demand reduction for fusion_fuel at connected nodes (D-186)
- Price adjustment via local tâtonnement

Layer 2 (spatial price equilibrium, #807):
- Damped tâtonnement trade flows along gate links (α=0.03, β=0.4, D-178)
- 8% transport cost per hop damps long-distance arbitrage
- Flows computed from pre-step snapshot; applied atomically
- --stability-check implements D-179 Tests 1 and 2:
  · Test 1: cold-start convergence ±5% at tick 100 → PASS (max 1.05%)
  · Test 2: long-run stability ±2% over ticks 900–999 → PASS (max 0.00%)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-07 13:56:06 +02:00
co-authored by Claude Sonnet 4.6
parent 7b0465a8c6
commit fb87f933f5
1169 changed files with 11201 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
//! CSV output for simulation snapshots.
use std::fs::File;
use std::io::{self, BufWriter, Write};
use std::path::Path;
use crate::model::TickRecord;
/// Write records to CSV. If `path` is None, writes to stdout.
pub fn write_csv(records: &[TickRecord], path: Option<&Path>) -> io::Result<()> {
let header = "node_id,commodity_id,supply,demand,price,tick\n";
match path {
Some(p) => {
let file = File::create(p)?;
let mut w = BufWriter::new(file);
write!(w, "{}", header)?;
for r in records {
writeln!(
w,
"{},{},{:.4},{:.4},{:.4},{}",
r.node_id, r.commodity_id, r.supply, r.demand, r.price, r.tick
)?;
}
w.flush()
}
None => {
let stdout = io::stdout();
let mut w = BufWriter::new(stdout.lock());
write!(w, "{}", header)?;
for r in records {
writeln!(
w,
"{},{},{:.4},{:.4},{:.4},{}",
r.node_id, r.commodity_id, r.supply, r.demand, r.price, r.tick
)?;
}
w.flush()
}
}
}