fix(simulation): address PR #122 review — determinism, correctness, labeling
- HashMap → BTreeMap throughout econ-sim for deterministic iteration (D-010) - Fix cost_factor: multiplicative gate×zone instead of additive (trade.rs) - Extract derive_seed to shared prng.rs, consolidate FNV-1a implementation - Rename run_shock_test → run_no_explosion_check (not D-179 Test 3) - Deduplicate cross-zone FX rate collection in Test 4 - Replace ORDER BY RANDOM() with deterministic ordering + ChaCha8Rng - Make commodity coverage failure a hard error consistent with D-175 - Fix gap-fill off-by-one (4 corps → 3 when coverage = 0) - Correct test report: EconEvent exists, location_type is body/station All four D-179 stability tests still pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+21
-19
@@ -1,6 +1,6 @@
|
||||
//! Database loading — reads economy data from systems.db.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
|
||||
@@ -80,15 +80,15 @@ pub struct GateLink {
|
||||
/// The complete economics dataset loaded from systems.db.
|
||||
pub struct Economy {
|
||||
pub commodities: Vec<Commodity>,
|
||||
pub commodity_map: HashMap<String, Commodity>,
|
||||
pub commodity_map: BTreeMap<String, Commodity>,
|
||||
pub chains: Vec<ProductionChain>,
|
||||
/// Map: output_commodity_id → list of chains that produce it
|
||||
pub chains_by_output: HashMap<String, Vec<ProductionChain>>,
|
||||
pub chains_by_output: BTreeMap<String, Vec<ProductionChain>>,
|
||||
/// Map: system_id → SystemInfo
|
||||
pub systems: HashMap<String, SystemInfo>,
|
||||
pub systems: BTreeMap<String, SystemInfo>,
|
||||
pub corp_presences: Vec<CorpPresence>,
|
||||
/// Map: system_id → list of corp presences
|
||||
pub presences_by_system: HashMap<String, Vec<CorpPresence>>,
|
||||
pub presences_by_system: BTreeMap<String, Vec<CorpPresence>>,
|
||||
/// Bidirectional gate links (transport graph)
|
||||
pub gate_links: Vec<GateLink>,
|
||||
/// Raw corp data for archetype inference: (corp_id, behavioral_archetype?, specialization?)
|
||||
@@ -193,12 +193,12 @@ fn load_chains(conn: &Connection) -> Vec<ProductionChain> {
|
||||
.collect();
|
||||
|
||||
// Build index of chain_id → inputs
|
||||
let mut input_map: HashMap<String, Vec<ChainInput>> = HashMap::new();
|
||||
let mut input_map: BTreeMap<String, Vec<ChainInput>> = BTreeMap::new();
|
||||
for (chain_id, commodity_id, quantity) in all_inputs {
|
||||
input_map
|
||||
.entry(chain_id)
|
||||
.or_default()
|
||||
.push(ChainInput { commodity_id, quantity });
|
||||
input_map.entry(chain_id).or_default().push(ChainInput {
|
||||
commodity_id,
|
||||
quantity,
|
||||
});
|
||||
}
|
||||
|
||||
for chain in &mut chains {
|
||||
@@ -210,7 +210,7 @@ fn load_chains(conn: &Connection) -> Vec<ProductionChain> {
|
||||
chains
|
||||
}
|
||||
|
||||
fn load_systems(conn: &Connection) -> HashMap<String, SystemInfo> {
|
||||
fn load_systems(conn: &Connection) -> BTreeMap<String, SystemInfo> {
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT ss.system_id, ss.proper_name, ss.cultural_corridor,
|
||||
@@ -234,7 +234,9 @@ fn load_systems(conn: &Connection) -> HashMap<String, SystemInfo> {
|
||||
cultural_corridor: row.get(2)?,
|
||||
gate_energy_connected: row.get::<_, Option<i32>>(3)?.unwrap_or(1) != 0,
|
||||
population: row.get(4)?,
|
||||
currency_zone: row.get::<_, Option<String>>(5)?.unwrap_or_else(|| "TRACTUS_PRIMARY".to_string()),
|
||||
currency_zone: row
|
||||
.get::<_, Option<String>>(5)?
|
||||
.unwrap_or_else(|| "TRACTUS_PRIMARY".to_string()),
|
||||
hop_distance: row.get::<_, Option<i64>>(6)?.unwrap_or(5),
|
||||
gate_topology: row.get(7)?,
|
||||
political_zone: row.get(8)?,
|
||||
@@ -246,9 +248,7 @@ fn load_systems(conn: &Connection) -> HashMap<String, SystemInfo> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn load_corp_archetype_data(
|
||||
conn: &Connection,
|
||||
) -> Vec<(String, Option<String>, Option<String>)> {
|
||||
fn load_corp_archetype_data(conn: &Connection) -> Vec<(String, Option<String>, Option<String>)> {
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT corp_id, behavioral_archetype, specialization
|
||||
@@ -315,11 +315,13 @@ fn load_corp_presences(conn: &Connection) -> Vec<CorpPresence> {
|
||||
|
||||
pub fn load_economy(conn: &Connection) -> Economy {
|
||||
let commodities = load_commodities(conn);
|
||||
let commodity_map: HashMap<String, Commodity> =
|
||||
commodities.iter().map(|c| (c.id.clone(), c.clone())).collect();
|
||||
let commodity_map: BTreeMap<String, Commodity> = commodities
|
||||
.iter()
|
||||
.map(|c| (c.id.clone(), c.clone()))
|
||||
.collect();
|
||||
|
||||
let chains = load_chains(conn);
|
||||
let mut chains_by_output: HashMap<String, Vec<ProductionChain>> = HashMap::new();
|
||||
let mut chains_by_output: BTreeMap<String, Vec<ProductionChain>> = BTreeMap::new();
|
||||
for chain in &chains {
|
||||
chains_by_output
|
||||
.entry(chain.output_commodity_id.clone())
|
||||
@@ -330,7 +332,7 @@ pub fn load_economy(conn: &Connection) -> Economy {
|
||||
let systems = load_systems(conn);
|
||||
let corp_presences = load_corp_presences(conn);
|
||||
|
||||
let mut presences_by_system: HashMap<String, Vec<CorpPresence>> = HashMap::new();
|
||||
let mut presences_by_system: BTreeMap<String, Vec<CorpPresence>> = BTreeMap::new();
|
||||
for cp in &corp_presences {
|
||||
presences_by_system
|
||||
.entry(cp.system_id.clone())
|
||||
|
||||
Reference in New Issue
Block a user