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:
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user