docs(meta): add Sprint 33 economics test plan
Test plan covering #806, #807, #808, #809 acceptance criteria aligned with D-179 stability tests and Phase 2 economics deliverable requirements. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,691 @@
|
||||
# Test Plan: Sprint 33 — Pecunia (Economics Simulation)
|
||||
|
||||
- **Sprint:** 33
|
||||
- **Date:** 2026-04-07
|
||||
- **Author:** Hoshe (QA)
|
||||
- **Branch:** `sprint-33/server`
|
||||
- **Tickets:** #813, #805, #806, #807, #808, #809
|
||||
- **Key spec:** `decisions/economics.md` (D-171–D-187), D-179 (stability acceptance criteria)
|
||||
|
||||
---
|
||||
|
||||
## How to Use This Document
|
||||
|
||||
Verification queries are written for `tooling/db/sqlite-query`. Stability tests run via
|
||||
`tooling/econ-sim --stability-check` once #807 lands. All SQL queries assume a fully-imported
|
||||
`server/data/systems.db` (after running `make economy-db`).
|
||||
|
||||
**Pass/fail convention:** Each test has an **Expected** clause. A test fails if the output
|
||||
deviates from Expected in any measurable way. Failures from #807 and later that involve
|
||||
oscillation or divergence indicate a broken model — tune α/β before calling it a feature (D-179).
|
||||
|
||||
---
|
||||
|
||||
## Pre-Flight: Baseline Data Sanity
|
||||
|
||||
Run these before testing any ticket. If they fail, the DB state is corrupted and ticket-level
|
||||
tests are meaningless.
|
||||
|
||||
```sql
|
||||
-- BF-1: Commodity count must be 36 (D-184)
|
||||
SELECT COUNT(*) FROM commodities;
|
||||
-- Expected: 36
|
||||
|
||||
-- BF-2: Commodity tier breakdown must match D-184 (9/10/9/5/3)
|
||||
SELECT tier, COUNT(*) FROM commodities GROUP BY tier ORDER BY tier;
|
||||
-- Expected:
|
||||
-- intermediate 10
|
||||
-- raw 9
|
||||
-- final 9
|
||||
-- service_professional 5
|
||||
-- service_luxury 3
|
||||
|
||||
-- BF-3: Production chain count must be 21 (D-184)
|
||||
SELECT COUNT(*) FROM production_chains;
|
||||
-- Expected: 21
|
||||
|
||||
-- BF-4: Chain input count must be 40 (count inputs from production_chains.toml)
|
||||
SELECT COUNT(*) FROM chain_inputs;
|
||||
-- Expected: 40
|
||||
|
||||
-- BF-5: Gate links must be bidirectional (every from→to has a matching to→from)
|
||||
SELECT COUNT(*) FROM gate_links gl
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM gate_links rev
|
||||
WHERE rev.from_system_id = gl.to_system_id
|
||||
AND rev.to_system_id = gl.from_system_id
|
||||
);
|
||||
-- Expected: 0
|
||||
|
||||
-- BF-6: All chain inputs reference valid commodities
|
||||
SELECT COUNT(*) FROM chain_inputs ci
|
||||
LEFT JOIN commodities c ON ci.input_commodity_id = c.commodity_id
|
||||
WHERE c.commodity_id IS NULL;
|
||||
-- Expected: 0
|
||||
|
||||
-- BF-7: All chain outputs reference valid commodities
|
||||
SELECT COUNT(*) FROM production_chains pc
|
||||
LEFT JOIN commodities c ON pc.output_commodity_id = c.commodity_id
|
||||
WHERE c.commodity_id IS NULL;
|
||||
-- Expected: 0
|
||||
```
|
||||
|
||||
**Known discrepancy to verify:** The `commodities.toml` section header reads
|
||||
`# PROFESSIONAL SERVICES (7)` but only 5 entries follow. The total must be 36 (matching
|
||||
D-184: 9+10+9+5+3). Flag if the count is 38.
|
||||
|
||||
---
|
||||
|
||||
## #813 — Energy-over-gate Schema Extension
|
||||
|
||||
**Spec ref:** D-186
|
||||
**Assigned to:** Tyre
|
||||
**Status:** in_progress
|
||||
|
||||
### What was changed
|
||||
|
||||
- `gate_energy_connected INTEGER DEFAULT 1` column added to `star_systems`
|
||||
(system-level, not per body/station — all nodes in a system inherit the system's setting
|
||||
via a join. Gate Corp energy is a system-wide commercial contract, not a per-node toggle.)
|
||||
- `MARK_PRIMARY` zones default to `false` (0)
|
||||
- All other zones default to `true` (1)
|
||||
- Migration is idempotent (safe to re-run via COLUMN_MIGRATIONS)
|
||||
- `set_gate_energy()` runs as step 6, after `set_currency_zones()` step 5 (correct ordering)
|
||||
|
||||
### Verification queries
|
||||
|
||||
```sql
|
||||
-- 813-1: Column exists on star_systems table
|
||||
PRAGMA table_info(star_systems);
|
||||
-- Expected: row with name='gate_energy_connected' and type='INTEGER'
|
||||
|
||||
-- 813-2: MARK_PRIMARY systems have gate_energy_connected = 0
|
||||
SELECT COUNT(*) FROM star_systems
|
||||
WHERE currency_zone = 'MARK_PRIMARY'
|
||||
AND gate_energy_connected != 0;
|
||||
-- Expected: 0
|
||||
|
||||
-- 813-3: TRACTUS_PRIMARY systems have gate_energy_connected = 1
|
||||
SELECT COUNT(*) FROM star_systems
|
||||
WHERE currency_zone = 'TRACTUS_PRIMARY'
|
||||
AND gate_energy_connected != 1;
|
||||
-- Expected: 0
|
||||
|
||||
-- 813-4: MIXED systems have gate_energy_connected = 1
|
||||
SELECT COUNT(*) FROM star_systems
|
||||
WHERE currency_zone = 'MIXED'
|
||||
AND gate_energy_connected != 1;
|
||||
-- Expected: 0
|
||||
|
||||
-- 813-5: gate_energy_connected is never NULL
|
||||
SELECT COUNT(*) FROM star_systems WHERE gate_energy_connected IS NULL;
|
||||
-- Expected: 0
|
||||
|
||||
-- 813-6: At least one MARK_PRIMARY system exists (validates zone data is present)
|
||||
SELECT COUNT(*) FROM star_systems WHERE currency_zone = 'MARK_PRIMARY';
|
||||
-- Expected: > 0 (requires #820 copy work to be merged first; skip if copy branch not merged)
|
||||
|
||||
-- 813-7: Sim binary can read gate_energy via join (integration spot-check)
|
||||
-- The sim must join bodies/stations to star_systems to get gate_energy_connected.
|
||||
-- Verify the join is correct:
|
||||
SELECT b.body_id, ss.gate_energy_connected
|
||||
FROM bodies b
|
||||
JOIN star_systems ss ON b.system_id = ss.system_id
|
||||
WHERE b.inhabited = 1
|
||||
LIMIT 5;
|
||||
-- Expected: 5 rows with gate_energy_connected = 0 or 1 (not NULL)
|
||||
```
|
||||
|
||||
### Edge cases
|
||||
|
||||
**813-E1: Idempotent migration**
|
||||
Run `make economy-db` twice on the same DB. Second run must not raise an error, and query
|
||||
813-1 through 813-7 must still pass.
|
||||
|
||||
**813-E2: Systems with NULL currency_zone**
|
||||
If any star system has `currency_zone IS NULL`, the migration logic must treat it as
|
||||
`TRACTUS_PRIMARY` (default to `true`). Verify no bodies end up with `gate_energy_connected = 0`
|
||||
due to a NULL zone.
|
||||
|
||||
```sql
|
||||
SELECT COUNT(*) FROM star_systems WHERE currency_zone IS NULL;
|
||||
-- Expected: 0 (import pipeline sets default; but verify regardless)
|
||||
```
|
||||
|
||||
**813-E3: Demand reduction is NOT implemented here**
|
||||
Confirm the `~0.3× fusion_fuel` utility demand reduction is absent from the schema-only ticket.
|
||||
The demand model lives in the sim binary (#806). Verify:
|
||||
- No column named `utility_demand_modifier` or similar on star_systems
|
||||
- No new columns beyond `gate_energy_connected` on star_systems
|
||||
|
||||
### Regression markers
|
||||
|
||||
- `tooling/economy-db/import_economics.py` migration block must still be idempotent
|
||||
- Existing BF-1 through BF-7 must still pass after #813 migration
|
||||
|
||||
---
|
||||
|
||||
## #805 — Corporation Pipeline and Validation
|
||||
|
||||
**Spec ref:** D-175, D-182
|
||||
**Assigned to:** Dudley
|
||||
**Status:** in_progress
|
||||
|
||||
### What was changed
|
||||
|
||||
- `import_economics.py` (or a new companion script) reads `wiki/corporations/` markdown files
|
||||
- Populates `corp_presence` table from authored location data
|
||||
- Validates wiki corp names ↔ DB `corporations.proper_name` sync (D-182 sync constraint)
|
||||
- Coverage rules: 3+ corps per major commodity type, 1+ per inhabited system >100K pop
|
||||
- Chain completeness validation: every intermediate commodity has ≥1 producing chain
|
||||
- Coverage failures exit non-zero (D-175 phase gate)
|
||||
|
||||
### Verification queries
|
||||
|
||||
```sql
|
||||
-- 805-1: corp_presence is no longer empty after pipeline run
|
||||
SELECT COUNT(*) FROM corp_presence;
|
||||
-- Expected: > 0
|
||||
|
||||
-- 805-2: All corp_presence rows reference valid corp_id
|
||||
SELECT COUNT(*) FROM corp_presence cp
|
||||
LEFT JOIN corporations c ON cp.corp_id = c.corp_id
|
||||
WHERE c.corp_id IS NULL;
|
||||
-- Expected: 0
|
||||
|
||||
-- 805-3: All corp_presence rows reference valid location_id
|
||||
-- (either a body_id or station_id — location_type determines which table)
|
||||
SELECT COUNT(*) FROM corp_presence WHERE location_type = 'body'
|
||||
AND location_id NOT IN (SELECT body_id FROM bodies);
|
||||
-- Expected: 0
|
||||
SELECT COUNT(*) FROM corp_presence WHERE location_type = 'station'
|
||||
AND location_id NOT IN (SELECT station_id FROM stations);
|
||||
-- Expected: 0
|
||||
|
||||
-- 805-4: Chain completeness — every intermediate must have a producing chain
|
||||
SELECT c.commodity_id, c.name
|
||||
FROM commodities c
|
||||
WHERE c.tier = 'intermediate'
|
||||
AND c.commodity_id NOT IN (
|
||||
SELECT output_commodity_id FROM production_chains
|
||||
);
|
||||
-- Expected: 0 rows (all 10 intermediates have a producing chain)
|
||||
|
||||
-- 805-5: Chain completeness — every final good must have a producing chain
|
||||
SELECT c.commodity_id, c.name
|
||||
FROM commodities c
|
||||
WHERE c.tier = 'final'
|
||||
AND c.commodity_id NOT IN (
|
||||
SELECT output_commodity_id FROM production_chains
|
||||
);
|
||||
-- Expected: 0 rows (all 9 finals have a producing chain)
|
||||
|
||||
-- 805-6: Services have NO producing chains (they are demand sinks, not outputs)
|
||||
SELECT c.commodity_id, c.name
|
||||
FROM commodities c
|
||||
WHERE c.tier IN ('service_professional', 'service_luxury')
|
||||
AND c.commodity_id IN (SELECT output_commodity_id FROM production_chains);
|
||||
-- Expected: 0 rows
|
||||
|
||||
-- 805-7: Raw materials have NO producing chains (they are inputs, not outputs)
|
||||
SELECT c.commodity_id, c.name
|
||||
FROM commodities c
|
||||
WHERE c.tier = 'raw'
|
||||
AND c.commodity_id IN (SELECT output_commodity_id FROM production_chains);
|
||||
-- Expected: 0 rows (fusion_fuel is intermediate, not raw — verify separately)
|
||||
|
||||
-- 805-8: Coverage rule — corporations per major commodity type (D-175: 3+ per major type)
|
||||
-- "Major commodity type" = intermediates and finals with demand_model = 'market'
|
||||
-- This requires corp_presence.primary_operation to reference a commodity_id; adjust
|
||||
-- query if the schema uses a different field. Flag if the field is absent.
|
||||
SELECT commodity_id, COUNT(DISTINCT cp.corp_id) AS corp_count
|
||||
FROM corp_presence cp
|
||||
JOIN corporations c ON cp.corp_id = c.corp_id
|
||||
WHERE cp.primary_operation IS NOT NULL
|
||||
GROUP BY cp.primary_operation
|
||||
HAVING corp_count < 3;
|
||||
-- Expected: 0 rows (every commodity with corp presence has 3+ corps)
|
||||
|
||||
-- 805-9: Coverage rule — inhabited systems > 100K pop have at least one corp
|
||||
SELECT ss.system_id, ss.proper_name
|
||||
FROM star_systems ss
|
||||
JOIN system_economy se ON ss.system_id = se.system_id
|
||||
WHERE se.population > 100000
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM corp_presence cp
|
||||
JOIN bodies b ON cp.location_id = b.body_id AND cp.location_type = 'body'
|
||||
WHERE b.system_id = ss.system_id
|
||||
UNION
|
||||
SELECT 1 FROM corp_presence cp
|
||||
JOIN stations s ON cp.location_id = s.station_id AND cp.location_type = 'station'
|
||||
WHERE s.system_id = ss.system_id
|
||||
);
|
||||
-- Expected: 0 rows
|
||||
```
|
||||
|
||||
### Exit code tests
|
||||
|
||||
Run the pipeline with intentional violations and verify non-zero exit:
|
||||
|
||||
**805-E1: Wiki name mismatch causes hard error**
|
||||
Temporarily rename a corporation in the DB to something the wiki doesn't know, re-run pipeline.
|
||||
Expected: non-zero exit with clear error message identifying the mismatch.
|
||||
|
||||
**805-E2: Missing commodity coverage causes hard error**
|
||||
If coverage drops below 3 corps for any major commodity type, pipeline must exit non-zero.
|
||||
Expected: non-zero exit with specific commodity identified.
|
||||
|
||||
**805-E3: Coverage failure for underpopulated system causes hard error**
|
||||
If an inhabited system with >100K pop has zero corp presence, pipeline must exit non-zero.
|
||||
Expected: non-zero exit with system_id identified.
|
||||
|
||||
**805-E4: Dry-run still works**
|
||||
`python3 tooling/economy-db/import_economics.py --dry-run` must:
|
||||
- Not write to corp_presence
|
||||
- Run all validations and report but not halt on coverage gaps (dry-run output is informational)
|
||||
- Exit 0 (dry-run is for inspection, not a gate)
|
||||
|
||||
Wait — check this with Dudley. If dry-run is meant to be a gate too, this should exit non-zero
|
||||
on validation failure. The existing pipeline exits 0 on dry-run. Confirm expected behavior
|
||||
before locking this test.
|
||||
|
||||
### Regression markers
|
||||
|
||||
- BF-1 through BF-7 still pass (new pipeline must not corrupt commodity/chain data)
|
||||
- `corp_presence` table's FK constraint still enforced (BF-6 analog for corps)
|
||||
- Existing `gate_links` bidirectionality (BF-5) unaffected
|
||||
|
||||
---
|
||||
|
||||
## #806 — Skeleton Economy_sim Binary
|
||||
|
||||
**Spec ref:** D-176, D-177, D-178
|
||||
**Assigned to:** Dudley
|
||||
**Status:** backlog (blocked on #805)
|
||||
|
||||
### What was changed
|
||||
|
||||
- New Rust binary at `tooling/econ-sim/`
|
||||
- Reads systems.db: gate_links, commodities, production_chains, chain_inputs, corp_presence
|
||||
- Seeds per-corp-site productivity (5 dimensions, PRNG, log-normal distribution)
|
||||
- Layer 1 Leontief only (no inter-system trade, no currency)
|
||||
- Outputs per-node CSV: node_id, commodity_id, supply, demand, price, tick
|
||||
- `--stability-check` flag compiles (stub, not yet meaningful)
|
||||
|
||||
### Build verification
|
||||
|
||||
```bash
|
||||
cd tooling/econ-sim && cargo build
|
||||
# Expected: exits 0, no compile errors
|
||||
|
||||
tooling/econ-sim --help
|
||||
# Expected: help text with --db, --stability-check, and --output flags visible
|
||||
```
|
||||
|
||||
### CSV output verification
|
||||
|
||||
```bash
|
||||
tooling/econ-sim --db server/data/systems.db --output /tmp/econ_out.csv
|
||||
```
|
||||
|
||||
**806-1:** CSV file is created at the specified path
|
||||
**806-2:** CSV header contains: `node_id,commodity_id,supply,demand,price,tick`
|
||||
**806-3:** Row count is `active_nodes × 36` (760 active nodes × 36 commodities = ~27,360 rows)
|
||||
- Acceptable range: ±10% of 27,360 (active node count may differ slightly from spec estimate)
|
||||
**806-4:** No `price` values are negative or zero for commodity types with non-zero base_price
|
||||
**806-5:** `tick` column is 0 for initial seeding output (first tick)
|
||||
|
||||
### Productivity seeding verification
|
||||
|
||||
**806-6: Standard node range (D-176)**
|
||||
```python
|
||||
# Pseudocode — inspect CSV output
|
||||
import csv, math
|
||||
prices = [float(row['price']) for row in csv.DictReader(open('/tmp/econ_out.csv'))]
|
||||
# For standard commodities, productivity multiplier range is 0.4–1.8x
|
||||
# Price variation relative to base_price should reflect this range
|
||||
base_price_by_id = { ... } # from commodities table
|
||||
multipliers = [price / base_price_by_id[row['commodity_id']] for row in rows]
|
||||
assert all(0.3 <= m <= 2.0 for m in multipliers), "multiplier out of expected range"
|
||||
# Actual range check: most values should fall within 0.4–1.8x (log-normal tails permitted)
|
||||
```
|
||||
|
||||
**806-7: Monopoly-source node range (D-176)**
|
||||
Nodes producing `lattice_grade_material` (production_ubiquity = 'monopolistic') must show
|
||||
tighter multiplier range: 0.7–1.4×. Verify variance is lower than standard nodes.
|
||||
|
||||
**806-8: D-177 constraints — what must NOT vary**
|
||||
Verify the binary never seeds or varies:
|
||||
- Location of production (the set of nodes producing each commodity is fixed from DB data,
|
||||
not random)
|
||||
- `lattice_grade_material` productivity: must stay within 0.7–1.4× (monopolistic ceiling)
|
||||
- Absence of seeded "starting disruptions" (no negative productivity, no corps with zero
|
||||
initial output as a seeded state)
|
||||
|
||||
**806-9: Corridor correlation (D-176 ~0.6)**
|
||||
Nodes in the same geographic corridor should have correlated productivity across runs with
|
||||
similar PRNG seeds. Spot-check: run binary twice with seeds differing by 1; nodes in same
|
||||
corridor should show ~0.6 Pearson correlation on their multipliers.
|
||||
|
||||
### Stub stability check
|
||||
|
||||
```bash
|
||||
tooling/econ-sim --stability-check
|
||||
# Expected: exits with some non-panic output, even if it's "stability tests not yet implemented"
|
||||
# Must NOT crash or segfault
|
||||
```
|
||||
|
||||
### Regression markers
|
||||
|
||||
- Atlas binary still builds: `cargo build --bin atlas`
|
||||
- D-177 lore constraints respected (see 806-8)
|
||||
|
||||
---
|
||||
|
||||
## #807 — Trade Flows and Stability Testing
|
||||
|
||||
**Spec ref:** D-178, D-179
|
||||
**Assigned to:** Dudley
|
||||
**Status:** backlog (blocked on #806)
|
||||
|
||||
**This is the most critical ticket. D-179 defines the exit condition for Phase 2.**
|
||||
|
||||
### Tâtonnement parameters
|
||||
|
||||
Verify from source code:
|
||||
- α = 0.03 (price adjustment speed)
|
||||
- β = 0.4 (damping coefficient)
|
||||
|
||||
If these are configurable via CLI flags, document the defaults. If hardcoded, grep for them:
|
||||
```bash
|
||||
grep -r "0\.03" tooling/econ-sim/src/
|
||||
grep -r "0\.4" tooling/econ-sim/src/
|
||||
```
|
||||
|
||||
### Floyd-Warshall startup performance
|
||||
|
||||
```bash
|
||||
time tooling/econ-sim --stability-check 2>&1 | head -5
|
||||
# Expected: FW initialization completes in < 2s (D-178 spec: ~0.5s, allow 4x margin)
|
||||
# Flag if > 5s: likely iterating over all 3700 nodes instead of the ~760 active subgraph
|
||||
```
|
||||
|
||||
### Market node tiering (D-178)
|
||||
|
||||
**807-1:** Active node count is approximately 760 (inhabited bodies + all stations)
|
||||
|
||||
```sql
|
||||
-- Count active market nodes per D-178 definition
|
||||
SELECT COUNT(*) FROM (
|
||||
SELECT body_id AS node_id FROM bodies WHERE inhabited = 1
|
||||
UNION ALL
|
||||
SELECT station_id FROM stations
|
||||
);
|
||||
-- Expected: ~760 (accept 700–820 as the spec estimate may not match actual DB state)
|
||||
```
|
||||
|
||||
**807-2:** Passive producer count is approximately 240
|
||||
|
||||
```sql
|
||||
SELECT COUNT(*) FROM bodies WHERE inhabited = 0 AND population > 0;
|
||||
-- Expected: ~240 (bodies with economic activity but no market function)
|
||||
-- Adjust query based on how the sim defines "passive producer"
|
||||
```
|
||||
|
||||
### Transport cost model
|
||||
|
||||
Verify in source or via output that:
|
||||
**807-3:** Gate edges cost 5–12% per hop (inter-system)
|
||||
**807-4:** Orbital edges cost 1–3% (intra-system)
|
||||
**807-5:** Transport costs are applied to commodity prices, not abstracted away
|
||||
|
||||
### D-179 Stability Tests
|
||||
|
||||
```bash
|
||||
tooling/econ-sim --stability-check
|
||||
```
|
||||
|
||||
All four tests are run by this flag (Tests 1–2 in #807, Tests 3–4 in #808). After #807:
|
||||
|
||||
**Test 1: Cold-start convergence (D-179)**
|
||||
- Simulate 100 game-days from cold start
|
||||
- Measure price deviation from equilibrium at tick 100
|
||||
- **Pass criterion:** All active commodity prices within ±5% of equilibrium
|
||||
- **Fail indicators:** oscillation, monotonic drift, any price < 0
|
||||
|
||||
**Test 2: Long-run stability (D-179)**
|
||||
- Simulate 1,000 game-days with zero external events
|
||||
- Measure maximum price drift from tick-0 equilibrium
|
||||
- **Pass criterion:** Zero drift > ±2% over the full 1,000-tick run
|
||||
- **Fail indicators:** slow drift accumulation, oscillation amplitude > 2%, any negative price
|
||||
|
||||
### Stockpile buffer test
|
||||
|
||||
**807-6:** Single-tick supply removal does not cause price explosion
|
||||
```
|
||||
procedure:
|
||||
1. Run sim to equilibrium (100 ticks)
|
||||
2. Inject a single tick of zero supply for one commodity at one node
|
||||
3. Observe price at that node for next 5 ticks
|
||||
Expected: price rises but does not exceed 10× base_price
|
||||
Fail: price goes to infinity, NaN, or negative
|
||||
```
|
||||
|
||||
### Regression markers
|
||||
|
||||
- Test 1 and Test 2 must pass with `--stability-check` before #808 begins
|
||||
- If either test fails: do NOT mark #807 done, do NOT proceed to #808
|
||||
- α/β must be documented (in source comments or README) so future tuning is traceable
|
||||
|
||||
---
|
||||
|
||||
## #808 — Currency Zones and Exchange Rates
|
||||
|
||||
**Spec ref:** D-171, D-172, D-174, D-181, D-186
|
||||
**Assigned to:** Dudley
|
||||
**Status:** backlog (blocked on #807)
|
||||
|
||||
### Currency zone model
|
||||
|
||||
**808-1:** Tractus↔Mark friction = ~3%
|
||||
Verify in cross-zone trade: cost of a commodity transiting from a TRACTUS_PRIMARY to a
|
||||
MARK_PRIMARY node is ~3% higher than same-zone transit at equal hop distance.
|
||||
|
||||
**808-2:** Zero friction within MARK_PRIMARY zones
|
||||
Two nodes both in MARK_PRIMARY zones trading with each other incur no currency conversion cost
|
||||
beyond the standard transport cost.
|
||||
|
||||
**808-3:** Sol is NOT a zone flag
|
||||
```sql
|
||||
SELECT COUNT(*) FROM star_systems WHERE currency_zone = 'SOL_PRIMARY';
|
||||
-- Expected: 0 (Sol is shadow economy only, D-171)
|
||||
```
|
||||
|
||||
**808-4:** Exchange rate is driven by trade balance, not hardcoded
|
||||
The Tractus/Mark exchange rate must change between runs (or across ticks as trade flows change).
|
||||
Hardcoded rates are a test failure.
|
||||
|
||||
### Signal vocabulary (D-181)
|
||||
|
||||
All 7 signals must be present in sim output per active node:
|
||||
|
||||
**808-5:**
|
||||
```
|
||||
1. price_current — present in output
|
||||
2. price_trend — present in output (direction + rate)
|
||||
3. trade_flow_volume — present in output
|
||||
4. corporate_presence — present in output
|
||||
5. stockpile_weeks — present in output
|
||||
6. production_vs_baseline — present in output
|
||||
7. official_coverage_ratio — present in output (derived from shadow_economy_intensity)
|
||||
```
|
||||
|
||||
Edge case: For `official_coverage_ratio`, verify nodes with no shadow economy intensity
|
||||
(TRACTUS_PRIMARY core systems) produce `official_coverage_ratio = 1.0` (formal economy
|
||||
covers 100% of activity), not NULL.
|
||||
|
||||
### gate_energy_connected demand reduction (D-186)
|
||||
|
||||
**808-6:** Nodes with `gate_energy_connected = true` show `fusion_fuel` demand ~0.3× baseline
|
||||
- Run sim on a TRACTUS_PRIMARY system (gate_energy_connected = true)
|
||||
- Run sim on a MARK_PRIMARY system (gate_energy_connected = false)
|
||||
- Compare `fusion_fuel` demand signal: on-grid node demand must be ~30% of off-grid
|
||||
|
||||
**808-7:** Industrial chain inputs are NOT reduced (D-186)
|
||||
- `smelt_ore` still requires `fusion_fuel` at 0.3 coefficient regardless of gate energy
|
||||
- `alloy_fabrication` still requires `fusion_fuel` at 0.2 coefficient
|
||||
- `electronics_fabrication` still requires `fusion_fuel` at 0.2 coefficient
|
||||
|
||||
### D-179 Tests 3–4
|
||||
|
||||
**Test 3: Shock response (D-179)**
|
||||
- Apply a single supply shock to one commodity at one node
|
||||
- **Pass criteria:**
|
||||
- Cascade propagates to dependent commodities (Leontief input scarcity visible)
|
||||
- Recovery to within 10% of pre-shock price within 200 ticks
|
||||
- No price explosions (no value > 100× base_price)
|
||||
- No negative prices
|
||||
- **Fail indicators:** runaway cascade, no recovery, shock isolated (no cascade = broken Leontief)
|
||||
|
||||
**Test 4: Cross-zone trade balance (D-179)**
|
||||
- Increase trade volume across a TRACTUS_PRIMARY / MARK_PRIMARY boundary
|
||||
- **Pass criteria:**
|
||||
- Exchange rate adjusts in response (Tractus/Mark ratio changes)
|
||||
- Rate re-stabilizes within 50 ticks
|
||||
- Friction cost is visible (cross-zone goods 3% more expensive than same-zone equivalent)
|
||||
- **Fail indicators:** no rate adjustment, infinite oscillation, rate diverges
|
||||
|
||||
All four D-179 tests must pass before #809 begins.
|
||||
|
||||
### Regression markers
|
||||
|
||||
- Tests 1 and 2 from #807 must still pass with currency layer active
|
||||
- Tractus prices are still the numeraire (no price expressed in Mark or Sol units)
|
||||
|
||||
---
|
||||
|
||||
## #809 — Corporate Agent Behavior
|
||||
|
||||
**Spec ref:** D-175, D-178, D-180, D-181
|
||||
**Assigned to:** Dudley
|
||||
**Status:** backlog (blocked on #808, #799, #800)
|
||||
|
||||
### Corporate data loading
|
||||
|
||||
**809-1:** Corporations are loaded from DB, not hardcoded
|
||||
```bash
|
||||
grep -r "hardcoded\|\"Gate Corporation\"\|\"Vethara\"" tooling/econ-sim/src/
|
||||
# Expected: corporation names should appear only in test fixtures or SQL queries,
|
||||
# not as string literals in behavioral logic
|
||||
```
|
||||
|
||||
**809-2:** Behavioral archetype template is read from TOML
|
||||
```bash
|
||||
ls wiki/economics/archetypes/behavioral.toml
|
||||
# Expected: file exists (created by copy team per sprint briefing)
|
||||
```
|
||||
|
||||
**809-3:** Each archetype is instantiated per corporation from corp_presence
|
||||
```sql
|
||||
-- Every corporation with corp_presence rows has a behavioral_archetype in DB
|
||||
SELECT COUNT(*) FROM corp_presence cp
|
||||
JOIN corporations c ON cp.corp_id = c.corp_id
|
||||
WHERE c.behavioral_archetype IS NULL;
|
||||
-- Expected: 0 (all corps with presence have an archetype assigned)
|
||||
```
|
||||
|
||||
### Six behavioral archetypes (D-175)
|
||||
|
||||
**809-4:** All 6 archetypes are implemented
|
||||
```bash
|
||||
grep -r "Monopolist\|Distributor\|Producer\|Specialist\|Cooperative\|Intermediary" \
|
||||
tooling/econ-sim/src/
|
||||
# Expected: all 6 appear in behavioral logic, not just data loading
|
||||
```
|
||||
|
||||
**809-5:** Archetypes produce distinguishably different behavior
|
||||
Run stability check with only Monopolist corps vs. only Cooperative corps in a test system.
|
||||
Price signals should differ between the two runs. If all archetypes produce identical output,
|
||||
the behavioral differentiation is not implemented.
|
||||
|
||||
### EconEvent stub (D-180)
|
||||
|
||||
**809-6:** EconEvent struct compiles with all required fields
|
||||
```bash
|
||||
grep -r "EconEvent" tooling/econ-sim/src/
|
||||
# Expected: struct definition with: target, effect, duration, visibility fields
|
||||
```
|
||||
|
||||
**809-7:** Visibility variants are defined
|
||||
```bash
|
||||
grep -r "Global\|Proximate\|Disclosed\|Hidden" tooling/econ-sim/src/
|
||||
# Expected: all 4 visibility variants present in the EconEvent type
|
||||
```
|
||||
|
||||
**809-8:** Event handler is a no-op (not exercised in Phase 2)
|
||||
Any call to `handle_event(EconEvent { ... })` should produce no observable simulation change.
|
||||
The port must compile and accept events without crashing.
|
||||
|
||||
### Signal completeness (D-181)
|
||||
|
||||
**809-9:** All 7 signals produced per active node with agents active
|
||||
Repeat 808-5 checks with corporate agents running. Agent behavior must not suppress or break
|
||||
signal production.
|
||||
|
||||
**809-10:** `production_vs_baseline` reflects agent output vs seeded baseline
|
||||
A Monopolist corp restricting supply should show `production_vs_baseline < 1.0`.
|
||||
A Cooperative corp operating at full capacity should show `production_vs_baseline ≈ 1.0`.
|
||||
|
||||
### D-179 Full Test Suite with Agents Active
|
||||
|
||||
**This is the Phase 2 exit condition.**
|
||||
|
||||
```bash
|
||||
tooling/econ-sim --stability-check
|
||||
```
|
||||
|
||||
**809-11:** All four stability tests pass with corporate agents active:
|
||||
- Test 1: Cold-start convergence ±5% within 100 game-days
|
||||
- Test 2: Long-run stability ±2% over 1,000 game-days
|
||||
- Test 3: Shock response, recovery within 200 ticks, no explosions or negatives
|
||||
- Test 4: Cross-zone balance re-stabilizes within 50 ticks
|
||||
|
||||
If agents CAUSE instability that wasn't present in #808, the agent behavioral parameters need
|
||||
tuning — this is a model bug, not a design decision. Investigate price-setting behavior before
|
||||
concluding the architecture is wrong.
|
||||
|
||||
### Regression markers
|
||||
|
||||
- All prior D-179 tests still pass
|
||||
- EconEvent handler does not crash on any valid input permutation
|
||||
- `behavioral.toml` is a required file — binary must error on missing file with a clear message
|
||||
|
||||
---
|
||||
|
||||
## Checklist: Verification Order
|
||||
|
||||
| Order | Ticket | Gate condition | Who verifies |
|
||||
|-------|--------|----------------|--------------|
|
||||
| 1 | Pre-flight BF-1–7 | DB baseline valid | Hoshe, post #804 |
|
||||
| 2 | #813 | Schema correct, defaults correct | Hoshe, when Tyre delivers |
|
||||
| 3 | #805 | corp_presence populated, coverage valid, exits non-zero on failure | Hoshe, when Dudley delivers |
|
||||
| 4 | #806 | Binary builds, CSV output correct, seeding in range | Hoshe, when Dudley delivers |
|
||||
| 5 | #807 | Tests 1+2 pass `--stability-check` | Hoshe, when Dudley delivers |
|
||||
| 6 | #808 | Tests 3+4 pass, all 7 signals present | Hoshe, when Dudley delivers |
|
||||
| 7 | #809 | All 4 D-179 tests pass with agents active | Hoshe, when Dudley delivers |
|
||||
|
||||
**Phase 2 is complete only when step 7 passes.** Steps 5 through 7 are the formal exit gate
|
||||
per D-179 and D-183.
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Quick Reference — D-179 Stability Criteria
|
||||
|
||||
| Test | Condition | Pass threshold | Run at |
|
||||
|------|-----------|---------------|--------|
|
||||
| 1 | Cold-start convergence | ±5% of equilibrium within 100 game-days | #807 |
|
||||
| 2 | Long-run stability | ±2% drift over 1,000 game-days, zero events | #807 |
|
||||
| 3 | Shock response | Recovery within 200 ticks, no explosions, no negatives | #808 |
|
||||
| 4 | Cross-zone trade balance | Re-stabilizes within 50 ticks | #808 |
|
||||
|
||||
All four must pass simultaneously with corporate agents active (#809) for Phase 2 sign-off.
|
||||
Reference in New Issue
Block a user