feat(simulation): build Tier-3 corporation generation pipeline (#800)
Adds server/src/bin/generate_corporations and tooling/generate-corporations wrapper. Generates ~5,000 Tier-3 corp instances from Tier-1/2 template archetypes with seeded name generation (FNV-1a + corridor-weighted PRNG). Writes wiki markdown stubs for each generated corporation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Generated
+54
-3
@@ -226,7 +226,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"toml_edit",
|
||||
"toml_edit 0.23.10+spec-1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1221,6 +1221,15 @@ dependencies = [
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.34+deprecated"
|
||||
@@ -1236,7 +1245,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "settled-reach-server"
|
||||
version = "0.1.31"
|
||||
version = "0.1.32"
|
||||
dependencies = [
|
||||
"bevy_app",
|
||||
"bevy_ecs",
|
||||
@@ -1254,6 +1263,7 @@ dependencies = [
|
||||
"serde_yaml",
|
||||
"sysinfo",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
@@ -1378,6 +1388,27 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime 0.6.11",
|
||||
"toml_edit 0.22.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.5+spec-1.1.0"
|
||||
@@ -1387,6 +1418,20 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime 0.6.11",
|
||||
"toml_write",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.23.10+spec-1.0.0"
|
||||
@@ -1394,7 +1439,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"toml_datetime 0.7.5+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
]
|
||||
@@ -1408,6 +1453,12 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
|
||||
@@ -22,6 +22,7 @@ crossbeam-channel = "0.5"
|
||||
sysinfo = "0.35"
|
||||
serde_json = "1"
|
||||
rusqlite = { version = "0.32", features = ["bundled"] }
|
||||
toml = "0.8"
|
||||
|
||||
[features]
|
||||
default = ["gauntlet"]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,214 @@
|
||||
//! Deterministic name generation for Tier-3 corporations.
|
||||
//!
|
||||
//! Names are composed from culture-specific pools matching the Settled Reach's
|
||||
//! geographic sectors. Each sector has dominant cultural influences derived
|
||||
//! from lore (wiki settlements, founding cultures, corridor identities).
|
||||
//!
|
||||
//! Pattern: `{surname/word} {business_suffix}` where surname draws from
|
||||
//! the sector's cultural pool and suffix from the lore category.
|
||||
|
||||
use rand::prelude::*;
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Surname pools by sector (drawn from founding cultures in wiki canon)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Core systems: cosmopolitan mix — the Reach's center of gravity.
|
||||
const CORE_NAMES: &[&str] = &[
|
||||
"Alvarez", "Benoit", "Carvalho", "Durand", "Eriksen", "Fournier", "Gao",
|
||||
"Hartmann", "Ishida", "Johansson", "Kirchner", "Lemaire", "Moreau",
|
||||
"Nakamura", "Olsson", "Pelletier", "Richter", "Saito", "Torres",
|
||||
"Ueda", "Vasquez", "Werner", "Xu", "Yamada", "Zhou", "Andersen",
|
||||
"Beaumont", "Costa", "Delacroix", "Engel", "Fujita", "Gutierrez",
|
||||
"Hayashi", "Ibarra", "Jensen", "Klein", "Laurent", "Mercier",
|
||||
"Novak", "Ortiz", "Park", "Reuter", "Suzuki", "Takahashi",
|
||||
"Ulrich", "Valentin", "Wagner", "Xie", "Yilmaz", "Zhang",
|
||||
];
|
||||
|
||||
/// North reach: Nordic, Scottish, northern European — Calloway heritage.
|
||||
const NORTH_REACH_NAMES: &[&str] = &[
|
||||
"Andersson", "Bjornsson", "Calloway", "Dalsgaard", "Eklund", "Falk",
|
||||
"Grimstad", "Hedlund", "Ivarsson", "Jonasson", "Kirkpatrick", "Lindqvist",
|
||||
"MacLeod", "Nordstrom", "Olafsson", "Pettersson", "Rehn", "Strandberg",
|
||||
"Thorsen", "Ulvskog", "Vikstrom", "Wahlberg", "Aberg", "Berglund",
|
||||
"Carlsen", "Dalgaard", "Engstrom", "Forsell", "Gustafsson", "Halvorsen",
|
||||
"Ingvarsson", "Jansson", "Knudsen", "Lundin", "MacPherson", "Nylund",
|
||||
"Ostergaard", "Palsson", "Rasmussen", "Sjoberg", "Toft", "Ulfsson",
|
||||
"Vestergaard", "Wiklund", "Aasen", "Brannstrom", "Dahl", "Eide",
|
||||
"Friberg", "Gren",
|
||||
];
|
||||
|
||||
/// South reach: Eastern European, East Asian industrial — Stalownia corridor.
|
||||
const SOUTH_REACH_NAMES: &[&str] = &[
|
||||
"Adamski", "Baranov", "Chernov", "Dubois", "Egorov", "Filipov",
|
||||
"Gromov", "Horvat", "Ivanova", "Jankovic", "Kowalski", "Lazarev",
|
||||
"Morozov", "Novikov", "Ostrowski", "Petrov", "Reznik", "Sokolov",
|
||||
"Tkachenko", "Uvarov", "Volkov", "Wojcik", "Yakimov", "Zheng",
|
||||
"Babic", "Chernyshev", "Dragunov", "Fedorov", "Grushevsky", "Havel",
|
||||
"Ito", "Jovanovic", "Katsaros", "Lebedev", "Mazur", "Nemec",
|
||||
"Ochoa", "Popov", "Radic", "Smirnov", "Tanaka", "Urasawa",
|
||||
"Vasiliev", "Watanabe", "Xiang", "Yegorov", "Zaytsev", "Borysko",
|
||||
"Chen", "Dimitrov",
|
||||
];
|
||||
|
||||
/// West reach: German, Central European — Compact territory, Westphalian influence.
|
||||
const WEST_REACH_NAMES: &[&str] = &[
|
||||
"Albrecht", "Baumann", "Christensen", "Dietrich", "Eisenberg", "Fischer",
|
||||
"Gruber", "Hoffmann", "Ingolstadt", "Jaeger", "Kessler", "Lehmann",
|
||||
"Mueller", "Neumann", "Obermann", "Pfeiffer", "Quandt", "Roth",
|
||||
"Schaefer", "Thiel", "Urban", "Vogt", "Weidenfeld", "Ziegler",
|
||||
"Becker", "Claussen", "Dorfmann", "Eberhardt", "Fleischer", "Gerstner",
|
||||
"Haber", "Imhof", "Jung", "Kraemer", "Linden", "Metzger",
|
||||
"Niedermann", "Opitz", "Preuss", "Raabe", "Steinbach", "Trautmann",
|
||||
"Unger", "Vollmer", "Winterberg", "Zahn", "Auerbach", "Bruckner",
|
||||
"Dahlem", "Eckhardt",
|
||||
];
|
||||
|
||||
/// East reach: Filipino, Korean, maritime Asian — distinctive identity.
|
||||
const EAST_REACH_NAMES: &[&str] = &[
|
||||
"Aquino", "Bautista", "Cruz", "Dalisay", "Espiritu", "Flores",
|
||||
"Garcia", "Hernandez", "Ilagan", "Jeon", "Kim", "Lim",
|
||||
"Magalang", "Navarro", "Ocampo", "Park", "Quijano", "Reyes",
|
||||
"Santos", "Tan", "Uy", "Villanueva", "Wong", "Yoo",
|
||||
"Aguilar", "Buenaventura", "Castillo", "Dizon", "Enriquez", "Fernandez",
|
||||
"Gonzales", "Hwang", "Ignacio", "Jeong", "Kwon", "Lee",
|
||||
"Marasigan", "Nakamura", "Oh", "Perez", "Ramos", "Son",
|
||||
"Tolentino", "Umali", "Valdez", "Yun", "Zamora", "Baek",
|
||||
"Choi", "Dela Cruz",
|
||||
];
|
||||
|
||||
/// Deep frontier: mixed backgrounds from all settler waves — no dominant culture.
|
||||
const FRONTIER_NAMES: &[&str] = &[
|
||||
"Adeyemi", "Bergstrom", "Chandra", "Duval", "Emeka", "Fonseca",
|
||||
"Gupta", "Hassan", "Ibrahim", "Jansson", "Kovac", "Liu",
|
||||
"Martinez", "Nkosi", "Okafor", "Patel", "Quinn", "Rodriguez",
|
||||
"Sousa", "Thorne", "Uddin", "Varga", "Wu", "Xiong",
|
||||
"Yoshida", "Zhao", "Abara", "Beaumont", "Cardenas", "Doyle",
|
||||
"Ekwueme", "Ferreira", "Gomes", "Henriksen", "Idris", "Juma",
|
||||
"Kato", "Larsen", "Morales", "Ndlovu", "Osei", "Petrov",
|
||||
"Ruiz", "Singh", "Tavares", "Uchida", "Volkov", "Wang",
|
||||
"Yang", "Zaman",
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Business suffix pools by lore category
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const EXTRACTION_SUFFIXES: &[&str] = &[
|
||||
"Mining Co.", "Extraction", "Resources", "Minerals", "Mining",
|
||||
"Quarry Works", "Deep Drill", "Ore Works", "Claims", "Mining & Salvage",
|
||||
"Prospecting", "Dig Co.", "Rock Works", "Shaft Mining", "Surface Mining",
|
||||
];
|
||||
|
||||
const AGRICULTURE_SUFFIXES: &[&str] = &[
|
||||
"Farms", "Agricultural Co.", "Growers", "Harvest", "Provisions",
|
||||
"Ranchers", "Fisheries", "Food Co.", "Plantations", "Cultivators",
|
||||
"Produce", "Orchard", "Dairy", "Stockfeed", "Processing",
|
||||
];
|
||||
|
||||
const MANUFACTURING_SUFFIXES: &[&str] = &[
|
||||
"Manufacturing", "Works", "Industries", "Fabrication", "Engineering",
|
||||
"Precision", "Assembly", "Components", "Foundry", "Machine Works",
|
||||
"Systems", "Technical", "Metalworks", "Forging", "Production",
|
||||
];
|
||||
|
||||
const TRADE_LOGISTICS_SUFFIXES: &[&str] = &[
|
||||
"Freight", "Logistics", "Shipping", "Transport", "Haulage",
|
||||
"Cargo", "Transit", "Distribution", "Forwarding", "Express",
|
||||
"Lines", "Carriers", "Fleet", "Couriers", "Supply Co.",
|
||||
];
|
||||
|
||||
const SERVICES_SUFFIXES: &[&str] = &[
|
||||
"Services", "Associates", "Consulting", "Partners", "Group",
|
||||
"Holdings", "Clinic", "Bureau", "Agency", "Office",
|
||||
"Practice", "Solutions", "Advisors", "Trust", "Institute",
|
||||
];
|
||||
|
||||
const INTELLIGENCE_SUFFIXES: &[&str] = &[
|
||||
"Analytics", "Intelligence", "Data Services", "Information", "Research",
|
||||
"Advisory", "Insights", "Consulting", "Networks", "Analysis",
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Name generation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn names_for_sector(sector: &str) -> &'static [&'static str] {
|
||||
match sector {
|
||||
"core" => CORE_NAMES,
|
||||
"north_reach" => NORTH_REACH_NAMES,
|
||||
"south_reach" => SOUTH_REACH_NAMES,
|
||||
"west_reach" => WEST_REACH_NAMES,
|
||||
"east_reach" => EAST_REACH_NAMES,
|
||||
"deep_frontier" => FRONTIER_NAMES,
|
||||
_ => CORE_NAMES,
|
||||
}
|
||||
}
|
||||
|
||||
fn suffixes_for_category(category: &str) -> &'static [&'static str] {
|
||||
match category {
|
||||
"extraction" => EXTRACTION_SUFFIXES,
|
||||
"agriculture" => AGRICULTURE_SUFFIXES,
|
||||
"manufacturing" => MANUFACTURING_SUFFIXES,
|
||||
"trade_logistics" => TRADE_LOGISTICS_SUFFIXES,
|
||||
"services" => SERVICES_SUFFIXES,
|
||||
"intelligence" => INTELLIGENCE_SUFFIXES,
|
||||
_ => SERVICES_SUFFIXES,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a plausible business name for the given sector and lore category.
|
||||
/// Deterministic for a given RNG state.
|
||||
pub fn generate_name(rng: &mut ChaCha8Rng, sector: &str, category: &str) -> String {
|
||||
let names = names_for_sector(sector);
|
||||
let suffixes = suffixes_for_category(category);
|
||||
|
||||
let surname = names[rng.random_range(0..names.len())];
|
||||
let suffix = suffixes[rng.random_range(0..suffixes.len())];
|
||||
|
||||
// 20% chance of double-barrel name (Surname & Surname Suffix)
|
||||
if rng.random::<f64>() < 0.20 {
|
||||
let surname2 = names[rng.random_range(0..names.len())];
|
||||
if surname != surname2 {
|
||||
return format!("{} & {} {}", surname, surname2, suffix);
|
||||
}
|
||||
}
|
||||
|
||||
// 15% chance of "Surname's Suffix" or "Surname Bros. Suffix"
|
||||
if rng.random::<f64>() < 0.15 {
|
||||
return format!("{} Bros. {}", surname, suffix);
|
||||
}
|
||||
|
||||
format!("{} {}", surname, suffix)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand::SeedableRng;
|
||||
|
||||
#[test]
|
||||
fn deterministic_names() {
|
||||
let mut rng1 = ChaCha8Rng::seed_from_u64(42);
|
||||
let mut rng2 = ChaCha8Rng::seed_from_u64(42);
|
||||
|
||||
for _ in 0..100 {
|
||||
let a = generate_name(&mut rng1, "core", "extraction");
|
||||
let b = generate_name(&mut rng2, "core", "extraction");
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn names_not_empty() {
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(1);
|
||||
for sector in &["core", "north_reach", "south_reach", "west_reach", "east_reach", "deep_frontier"] {
|
||||
for cat in &["extraction", "agriculture", "manufacturing", "trade_logistics", "services", "intelligence"] {
|
||||
let name = generate_name(&mut rng, sector, cat);
|
||||
assert!(!name.is_empty(), "Empty name for {}/{}", sector, cat);
|
||||
assert!(name.contains(' '), "No space in name: {}", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generate Tier-3 corporations for the Settled Reach economy.
|
||||
#
|
||||
# Usage:
|
||||
# tooling/generate-corporations
|
||||
# tooling/generate-corporations --seed 42 --min-corps 5000
|
||||
# tooling/generate-corporations --output path/to/output.toml
|
||||
#
|
||||
# Builds on first run if binary doesn't exist.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
BIN="$ROOT_DIR/server/target/debug/generate_corporations"
|
||||
|
||||
# Build if needed
|
||||
if [ ! -f "$BIN" ]; then
|
||||
echo "Building generate_corporations..." >&2
|
||||
(cd "$ROOT_DIR/server" && cargo build --bin generate_corporations 2>&1 | tail -3) >&2
|
||||
fi
|
||||
|
||||
exec "$BIN" "$@"
|
||||
Reference in New Issue
Block a user