feat(schema): trait_templates + atlas_body_trait_bias tables + bake (#993)
D-232 architecture-flavor storage mechanism (content is #1005/#1017): - trait_templates: shared catalog of holistic template bundles — two-tier eligibility (hard gates bulk_class/prosperity_bps/ubiquity + soft weight mods), zone_affinity, allow/block tags, visual_bundle. List/map fields are JSON; all numerics integer basis-points (D-010). - atlas_body_trait_bias: sparse per-body hero pins — pin/boost/suppress with basis-point multiplier ranges (boost ≤3×, suppress ≥0.33× never 0). - Baked at import (steps 14/15) from wiki/economics/architecture_trait_ catalog.toml + architecture_trait_bias.toml. FK-validated (body_id → bodies, template_tag → trait_templates), enum + multiplier-range checks, graceful on absent source. Source location provisional pending Q-107; the baked tables are invariant per D-232. Retires the round-2 atlas_body_culture tables. - Catalog ships a 3-template bootstrap seed to prove the bake end-to-end; the full ~35-template catalog is #1005, hero pins #1017. - Stamp sources updated (IMPORT_ECONOMICS_SOURCES + GENERATOR_SOURCES). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -539,6 +539,50 @@ CREATE INDEX IF NOT EXISTS idx_atlas_province_boundaries_body ON atlas_province_
|
||||
CREATE INDEX IF NOT EXISTS idx_atlas_city_positions_body ON atlas_city_positions(body_id);
|
||||
-- END ATLAS INDEX (D-191 §8, #832)
|
||||
|
||||
-- ── Architecture-flavor trait templates (D-232, #993) ──────────────────────
|
||||
-- trait_templates: the shared catalog of holistic architecture-flavor template
|
||||
-- bundles (NOT per-axis traits). Baked from
|
||||
-- wiki/economics/architecture_trait_catalog.toml. List/map fields are JSON text
|
||||
-- (the generator parses). All numerics are integer basis-points (D-010).
|
||||
-- Catalog content is authored in #1005; this table is the mechanism (#993).
|
||||
CREATE TABLE IF NOT EXISTS trait_templates (
|
||||
tag TEXT PRIMARY KEY,
|
||||
label TEXT NOT NULL,
|
||||
cultural_description TEXT,
|
||||
corridor_pool TEXT NOT NULL DEFAULT 'baseline', -- baseline | heritage | cross_corridor
|
||||
geographic_sector TEXT, -- corridor; NULL = shared/cross-corridor
|
||||
bulk_class_gate TEXT, -- JSON list of eligible BulkClass; NULL/[] = any
|
||||
production_ubiquity_gate TEXT, -- JSON list; NULL/[] = any
|
||||
min_prosperity_bps INTEGER NOT NULL DEFAULT 0, -- hard gate (basis points)
|
||||
base_weight INTEGER NOT NULL DEFAULT 10000, -- basis points
|
||||
weight_mods TEXT, -- JSON {dimension: {value: multiplier_bps}}
|
||||
zone_affinity TEXT, -- JSON {DistrictType: weight_bps}
|
||||
allow_tags TEXT, -- JSON list of ObjectTag
|
||||
block_tags TEXT, -- JSON list of ObjectTag
|
||||
era_scope TEXT,
|
||||
visual_bundle TEXT, -- JSON (D-235 bundle + fallback parents)
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_trait_templates_pool ON trait_templates(corridor_pool);
|
||||
CREATE INDEX IF NOT EXISTS idx_trait_templates_sector ON trait_templates(geographic_sector);
|
||||
|
||||
-- atlas_body_trait_bias: sparse per-body bias on the catalog draw (hero bodies
|
||||
-- only, ~30-40). pin = mandatory (counts toward K); boost = weight up (<=3x);
|
||||
-- suppress = weight down (>=0.33x, never 0). Hero-pin content authored in #1017.
|
||||
CREATE TABLE IF NOT EXISTS atlas_body_trait_bias (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
body_id TEXT NOT NULL REFERENCES bodies(body_id) ON DELETE CASCADE,
|
||||
template_tag TEXT NOT NULL REFERENCES trait_templates(tag) ON DELETE CASCADE,
|
||||
bias_kind TEXT NOT NULL, -- pin | boost | suppress
|
||||
weight_multiplier_bps INTEGER, -- boost 10001..30000; suppress 3300..9999; pin NULL
|
||||
note TEXT,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UNIQUE (body_id, template_tag)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_atlas_body_trait_bias_body ON atlas_body_trait_bias(body_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_atlas_body_trait_bias_tag ON atlas_body_trait_bias(template_tag);
|
||||
-- END TRAIT TEMPLATES (D-232, #993)
|
||||
|
||||
-- Indexes
|
||||
-- astronomical_id removed: system_id IS the GJ catalog number
|
||||
CREATE INDEX IF NOT EXISTS idx_star_systems_sector ON star_systems(geographic_sector);
|
||||
|
||||
Binary file not shown.
@@ -56,6 +56,9 @@ GENERATOR_SOURCES: dict[str, list[Path]] = {
|
||||
# with IMPORT_ECONOMICS_SOURCES in tooling/economy-db/import_economics.py.
|
||||
REPO_ROOT / "wiki" / "economics" / "specialization_vocabulary.toml",
|
||||
REPO_ROOT / "wiki" / "economics" / "system_specialization.toml",
|
||||
# D-232 architecture-flavor trait catalog + hero bias (#993).
|
||||
REPO_ROOT / "wiki" / "economics" / "architecture_trait_catalog.toml",
|
||||
REPO_ROOT / "wiki" / "economics" / "architecture_trait_bias.toml",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,10 @@ COMMODITIES_TOML = REPO_ROOT / "wiki" / "economics" / "commodities.toml"
|
||||
CHAINS_TOML = REPO_ROOT / "wiki" / "economics" / "production_chains.toml"
|
||||
SPECIALIZATION_VOCAB_TOML = REPO_ROOT / "wiki" / "economics" / "specialization_vocabulary.toml"
|
||||
SYSTEM_SPECIALIZATION_TOML = REPO_ROOT / "wiki" / "economics" / "system_specialization.toml"
|
||||
# D-232 architecture-flavor trait-template catalog + sparse hero bias (#993).
|
||||
# Source-location is provisional pending Q-107; the baked tables are invariant.
|
||||
ARCHITECTURE_TRAIT_CATALOG_TOML = REPO_ROOT / "wiki" / "economics" / "architecture_trait_catalog.toml"
|
||||
ARCHITECTURE_TRAIT_BIAS_TOML = REPO_ROOT / "wiki" / "economics" / "architecture_trait_bias.toml"
|
||||
SCHEMA_SQL = REPO_ROOT / "server" / "data" / "systems-schema.sql"
|
||||
CORPORATIONS_DIR = REPO_ROOT / "wiki" / "corporations"
|
||||
WIKI_STAR_SYSTEMS = REPO_ROOT / "wiki" / "star-systems"
|
||||
@@ -90,6 +94,8 @@ IMPORT_ECONOMICS_SOURCES: tuple[Path, ...] = (
|
||||
# GENERATOR_SOURCES["import_economics"] in tooling/check-systems-db-stamp.
|
||||
SPECIALIZATION_VOCAB_TOML,
|
||||
SYSTEM_SPECIALIZATION_TOML,
|
||||
ARCHITECTURE_TRAIT_CATALOG_TOML,
|
||||
ARCHITECTURE_TRAIT_BIAS_TOML,
|
||||
)
|
||||
|
||||
|
||||
@@ -371,6 +377,45 @@ CREATE TABLE IF NOT EXISTS atlas_city_positions (
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_atlas_city_positions_body ON atlas_city_positions(body_id);
|
||||
|
||||
-- Architecture-flavor trait templates (D-232, #993). trait_templates = the
|
||||
-- shared catalog (baked from architecture_trait_catalog.toml); atlas_body_trait_bias
|
||||
-- = sparse per-body hero pins (#1017). List/map fields are JSON; numerics are
|
||||
-- integer basis-points (D-010). Retires the round-2 atlas_body_culture tables.
|
||||
DROP TABLE IF EXISTS atlas_body_culture_era;
|
||||
DROP TABLE IF EXISTS atlas_body_culture;
|
||||
CREATE TABLE IF NOT EXISTS trait_templates (
|
||||
tag TEXT PRIMARY KEY,
|
||||
label TEXT NOT NULL,
|
||||
cultural_description TEXT,
|
||||
corridor_pool TEXT NOT NULL DEFAULT 'baseline',
|
||||
geographic_sector TEXT,
|
||||
bulk_class_gate TEXT,
|
||||
production_ubiquity_gate TEXT,
|
||||
min_prosperity_bps INTEGER NOT NULL DEFAULT 0,
|
||||
base_weight INTEGER NOT NULL DEFAULT 10000,
|
||||
weight_mods TEXT,
|
||||
zone_affinity TEXT,
|
||||
allow_tags TEXT,
|
||||
block_tags TEXT,
|
||||
era_scope TEXT,
|
||||
visual_bundle TEXT,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_trait_templates_pool ON trait_templates(corridor_pool);
|
||||
CREATE INDEX IF NOT EXISTS idx_trait_templates_sector ON trait_templates(geographic_sector);
|
||||
CREATE TABLE IF NOT EXISTS atlas_body_trait_bias (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
body_id TEXT NOT NULL REFERENCES bodies(body_id) ON DELETE CASCADE,
|
||||
template_tag TEXT NOT NULL REFERENCES trait_templates(tag) ON DELETE CASCADE,
|
||||
bias_kind TEXT NOT NULL,
|
||||
weight_multiplier_bps INTEGER,
|
||||
note TEXT,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UNIQUE (body_id, template_tag)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_atlas_body_trait_bias_body ON atlas_body_trait_bias(body_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_atlas_body_trait_bias_tag ON atlas_body_trait_bias(template_tag);
|
||||
|
||||
-- Normalize bodies.economic_role to the D-194 canonical 10-value set (#911).
|
||||
-- Idempotent: each UPDATE is a no-op if the old value is already gone.
|
||||
UPDATE bodies SET economic_role = 'agricultural' WHERE economic_role IN ('agriculture', 'mixed-agriculture');
|
||||
@@ -2015,6 +2060,139 @@ def _specialization_checks(conn, vocab, systems, strict):
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Architecture-flavor trait templates (D-232, #993)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_TRAIT_CORRIDOR_POOLS = {"baseline", "heritage", "cross_corridor"}
|
||||
_TRAIT_BIAS_KINDS = {"pin", "boost", "suppress"}
|
||||
# JSON-encoded list/map columns on trait_templates (TOML inline arrays/tables ->
|
||||
# JSON text the generator parses).
|
||||
_TRAIT_JSON_LIST = ("bulk_class_gate", "production_ubiquity_gate", "allow_tags", "block_tags")
|
||||
_TRAIT_JSON_MAP = ("weight_mods", "zone_affinity", "visual_bundle")
|
||||
|
||||
|
||||
def populate_trait_templates(conn: sqlite3.Connection, dry_run: bool) -> int:
|
||||
"""Bake the D-232 architecture-flavor catalog into trait_templates (#993).
|
||||
|
||||
Reads ARCHITECTURE_TRAIT_CATALOG_TOML (`[templates.<tag>]` stanzas) and
|
||||
rebuilds the table. List/map fields are stored as JSON text; numeric
|
||||
eligibility is integer basis-points (D-010). The catalog *content* is
|
||||
authored in #1005 — this baker is the mechanism. Absent source -> 0 rows
|
||||
(the table still exists for the downstream pipeline). Deterministic rebuild:
|
||||
clears trait_templates (cascading atlas_body_trait_bias) first.
|
||||
"""
|
||||
if dry_run:
|
||||
# Still surface row count that WOULD be baked.
|
||||
pass
|
||||
conn.execute("DELETE FROM atlas_body_trait_bias")
|
||||
conn.execute("DELETE FROM trait_templates")
|
||||
if not ARCHITECTURE_TRAIT_CATALOG_TOML.exists():
|
||||
return 0
|
||||
with open(ARCHITECTURE_TRAIT_CATALOG_TOML, "rb") as f:
|
||||
data = tomllib.load(f)
|
||||
templates = data.get("templates", {})
|
||||
errors: list[str] = []
|
||||
rows = []
|
||||
for tag, t in templates.items():
|
||||
pool = t.get("corridor_pool", "baseline")
|
||||
if pool not in _TRAIT_CORRIDOR_POOLS:
|
||||
errors.append(f"trait_templates '{tag}': corridor_pool '{pool}' invalid")
|
||||
if "label" not in t:
|
||||
errors.append(f"trait_templates '{tag}': missing required 'label'")
|
||||
for k in (*_TRAIT_JSON_LIST, *_TRAIT_JSON_MAP):
|
||||
# any provided list/map field must JSON-encode cleanly
|
||||
if k in t:
|
||||
try:
|
||||
json.dumps(t[k])
|
||||
except (TypeError, ValueError):
|
||||
errors.append(f"trait_templates '{tag}': field '{k}' not JSON-serialisable")
|
||||
rows.append((
|
||||
tag, t.get("label", ""), t.get("cultural_description"),
|
||||
pool, t.get("geographic_sector"),
|
||||
json.dumps(t["bulk_class_gate"]) if t.get("bulk_class_gate") else None,
|
||||
json.dumps(t["production_ubiquity_gate"]) if t.get("production_ubiquity_gate") else None,
|
||||
int(t.get("min_prosperity_bps", 0)),
|
||||
int(t.get("base_weight", 10000)),
|
||||
json.dumps(t["weight_mods"]) if t.get("weight_mods") else None,
|
||||
json.dumps(t["zone_affinity"]) if t.get("zone_affinity") else None,
|
||||
json.dumps(t["allow_tags"]) if t.get("allow_tags") else None,
|
||||
json.dumps(t["block_tags"]) if t.get("block_tags") else None,
|
||||
t.get("era_scope"),
|
||||
json.dumps(t["visual_bundle"]) if t.get("visual_bundle") else None,
|
||||
))
|
||||
if errors:
|
||||
print(f" TRAIT TEMPLATE ERRORS ({len(errors)}):")
|
||||
for e in errors:
|
||||
print(f" - {e}")
|
||||
raise _ImportAborted()
|
||||
conn.executemany(
|
||||
"""INSERT INTO trait_templates
|
||||
(tag, label, cultural_description, corridor_pool, geographic_sector,
|
||||
bulk_class_gate, production_ubiquity_gate, min_prosperity_bps,
|
||||
base_weight, weight_mods, zone_affinity, allow_tags, block_tags,
|
||||
era_scope, visual_bundle)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||
rows,
|
||||
)
|
||||
return len(rows)
|
||||
|
||||
|
||||
def populate_atlas_body_trait_bias(conn: sqlite3.Connection, dry_run: bool) -> int:
|
||||
"""Bake the sparse per-body hero pins into atlas_body_trait_bias (#993).
|
||||
|
||||
Reads ARCHITECTURE_TRAIT_BIAS_TOML (`[[bias]]` array). FK-validates body_id
|
||||
against bodies and template_tag against trait_templates (which must be baked
|
||||
first), and validates bias_kind + the basis-point multiplier ranges
|
||||
(boost 10001..30000 = <=3x; suppress 3300..9999 = >=0.33x never 0; pin: no
|
||||
multiplier). Hero-pin *content* is authored in #1017. Absent source -> 0
|
||||
rows. Must run AFTER populate_trait_templates.
|
||||
"""
|
||||
conn.execute("DELETE FROM atlas_body_trait_bias")
|
||||
if not ARCHITECTURE_TRAIT_BIAS_TOML.exists():
|
||||
return 0
|
||||
with open(ARCHITECTURE_TRAIT_BIAS_TOML, "rb") as f:
|
||||
data = tomllib.load(f)
|
||||
entries = data.get("bias", [])
|
||||
body_ids = {r[0] for r in conn.execute("SELECT body_id FROM bodies")}
|
||||
tags = {r[0] for r in conn.execute("SELECT tag FROM trait_templates")}
|
||||
errors: list[str] = []
|
||||
rows = []
|
||||
seen = set()
|
||||
for i, b in enumerate(entries):
|
||||
bid = b.get("body_id")
|
||||
tag = b.get("template_tag")
|
||||
kind = b.get("bias_kind")
|
||||
mult = b.get("weight_multiplier_bps")
|
||||
loc = f"bias[{i}] ({bid}/{tag})"
|
||||
if bid not in body_ids:
|
||||
errors.append(f"{loc}: body_id not in bodies")
|
||||
if tag not in tags:
|
||||
errors.append(f"{loc}: template_tag not in trait_templates")
|
||||
if kind not in _TRAIT_BIAS_KINDS:
|
||||
errors.append(f"{loc}: bias_kind '{kind}' invalid (pin|boost|suppress)")
|
||||
if (bid, tag) in seen:
|
||||
errors.append(f"{loc}: duplicate (body_id, template_tag)")
|
||||
seen.add((bid, tag))
|
||||
if kind == "boost" and not (mult and 10001 <= mult <= 30000):
|
||||
errors.append(f"{loc}: boost weight_multiplier_bps must be 10001..30000 (<=3x), got {mult}")
|
||||
if kind == "suppress" and not (mult and 3300 <= mult <= 9999):
|
||||
errors.append(f"{loc}: suppress weight_multiplier_bps must be 3300..9999 (>=0.33x, never 0), got {mult}")
|
||||
rows.append((bid, tag, kind, mult, b.get("note")))
|
||||
if errors:
|
||||
print(f" TRAIT BIAS ERRORS ({len(errors)}):")
|
||||
for e in errors:
|
||||
print(f" - {e}")
|
||||
raise _ImportAborted()
|
||||
conn.executemany(
|
||||
"""INSERT INTO atlas_body_trait_bias
|
||||
(body_id, template_tag, bias_kind, weight_multiplier_bps, note)
|
||||
VALUES (?,?,?,?,?)""",
|
||||
rows,
|
||||
)
|
||||
return len(rows)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -2193,6 +2371,15 @@ def main():
|
||||
n_updated, n_inserted = populate_atlas_city_names_corps(conn, args.dry_run)
|
||||
print(f" {n_updated} rows updated, {n_inserted} reserved rows inserted")
|
||||
|
||||
# 14. Architecture-flavor trait templates (D-232, #993). Catalog first,
|
||||
# then sparse per-body hero bias (FK -> trait_templates + bodies).
|
||||
print(" [14/15] Baking trait_templates catalog (D-232)...")
|
||||
n_templates = populate_trait_templates(conn, args.dry_run)
|
||||
print(f" {n_templates} trait templates")
|
||||
print(" [15/15] Baking atlas_body_trait_bias hero pins (D-232)...")
|
||||
n_bias = populate_atlas_body_trait_bias(conn, args.dry_run)
|
||||
print(f" {n_bias} body trait-bias rows")
|
||||
|
||||
# Validate structural integrity (FK, chain refs, chain completeness).
|
||||
# These errors indicate broken imported data — do NOT commit.
|
||||
print("\n Validating structural integrity...")
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# ==========================================================================
|
||||
# Architecture-flavor per-body trait bias (D-232) — sparse hero pins.
|
||||
# Source of truth for the `atlas_body_trait_bias` baked table.
|
||||
#
|
||||
# Bias applies ONLY to hero bodies (~30-40 of ~300). Non-hero bodies run the
|
||||
# identical draw with no bias (economics + corridor weight + seed). Bias is
|
||||
# PER-BODY, never per-corridor.
|
||||
#
|
||||
# Three kinds:
|
||||
# pin mandatory template — counts toward K, the body WILL use it.
|
||||
# (no weight_multiplier_bps)
|
||||
# boost non-mandatory weight increase, <= 3x.
|
||||
# weight_multiplier_bps in 10001..30000
|
||||
# suppress weight decrease, >= 0.33x, NEVER 0 (preserves second-playthrough
|
||||
# surprise). weight_multiplier_bps in 3300..9999
|
||||
#
|
||||
# Each entry FK-validates: body_id -> bodies, template_tag -> trait_templates.
|
||||
#
|
||||
# SCOPE NOTE (#993 vs #1017): #993 builds the table + baker. The ~30-40 hero
|
||||
# pins (and the Groombridge GSH clearing_house_landmark, which also needs the
|
||||
# D-222 named-landmark reservation from #957) are authored in #1017. This file
|
||||
# ships empty-but-documented; add `[[bias]]` entries in #1017.
|
||||
#
|
||||
# Example (commented — uncomment + point at a real body/template in #1017):
|
||||
# [[bias]]
|
||||
# body_id = "GJ680d" # a bodies.body_id
|
||||
# template_tag = "terraced_agrarian" # a trait_templates.tag
|
||||
# bias_kind = "boost"
|
||||
# weight_multiplier_bps = 20000 # 2.0x
|
||||
# note = "why this body leans this way"
|
||||
# ==========================================================================
|
||||
|
||||
# (no hero pins yet — #1017)
|
||||
@@ -0,0 +1,84 @@
|
||||
# ==========================================================================
|
||||
# Architecture-flavor trait-template catalog (D-232)
|
||||
# Source of truth for the `trait_templates` baked table (import_economics.py).
|
||||
#
|
||||
# Each template is a HOLISTIC bundle (a coherent relationship between its axes),
|
||||
# never decomposed per-axis. The draw (D-232, three phases) hard-gates the pool
|
||||
# by economics, weights it by modifiers + wiki bias, then seed-draws K templates
|
||||
# per body (K = complexity_tier: Full 5 / Moderate 3 / Minimal 1 / Empty 0).
|
||||
#
|
||||
# Channel separation (D-233): economics decides WHAT a building is; this catalog
|
||||
# decides only HOW it is characterized. `geographic_sector`/corridor is ALWAYS a
|
||||
# soft weight, never a hard gate ("corridors are tendencies, not borders").
|
||||
#
|
||||
# All numeric eligibility is INTEGER BASIS-POINTS (10000 = 1.0x), never f32
|
||||
# (D-010 determinism; save-critical under D-227).
|
||||
#
|
||||
# SCOPE NOTE (#993 vs #1005): this file is the BAKE SOURCE. #993 builds the
|
||||
# table + baker + the bootstrap seed below. The full ~35-template catalog
|
||||
# (40-45 target; baseline + heritage sub-pool per corridor + a shared
|
||||
# cross-corridor pool; CI guardrails >=5 eligible per economic class, no
|
||||
# template >60% pool weight) is authored in #1005 by Miri (cultural/eligibility)
|
||||
# + Araminta (visual_bundle). The seed entries below are minimal bootstraps to
|
||||
# prove the pipeline end-to-end — expand/replace them in #1005.
|
||||
#
|
||||
# Stanza: [templates.<tag>] (tag = stable id, referenced by atlas_body_trait_bias)
|
||||
# Fields:
|
||||
# label human label (required)
|
||||
# cultural_description Miri's cultural meaning
|
||||
# corridor_pool baseline | heritage | cross_corridor
|
||||
# geographic_sector corridor this belongs to (omit for cross_corridor)
|
||||
# bulk_class_gate list of eligible BulkClass (omit = any) — HARD gate
|
||||
# production_ubiquity_gate list of eligible ProductionUbiquity (omit = any) — HARD gate
|
||||
# min_prosperity_bps hard gate (basis points)
|
||||
# base_weight draw weight (basis points; 10000 = 1.0)
|
||||
# weight_mods {dimension = {value = multiplier_bps}} — SOFT weights
|
||||
# dimensions: economic_role | dominant_faction |
|
||||
# founding_age | geographic_sector | morphology_zone
|
||||
# zone_affinity {DistrictType = weight_bps} — district-dominant draw
|
||||
# allow_tags / block_tags ObjectTag allow/block lists
|
||||
# era_scope era applicability (era = maintenance/wear, not tech)
|
||||
# visual_bundle D-235 bundle (wall/roof/facade/street + colors + fallback parents)
|
||||
# ==========================================================================
|
||||
|
||||
# --- BOOTSTRAP SEED (#993) — expand/replace in #1005 -----------------------
|
||||
|
||||
[templates.generic_baseline]
|
||||
label = "Generic Baseline"
|
||||
cultural_description = "The cohesive default look of a settlement with no divergent founding heritage — functional, unremarkable, corridor-neutral. The fallback the draw can always reach."
|
||||
corridor_pool = "baseline"
|
||||
min_prosperity_bps = 0
|
||||
base_weight = 10000
|
||||
zone_affinity = { residential = 10000, commercial = 10000, civic = 10000, industrial = 10000 }
|
||||
allow_tags = ["concrete_wall", "flat_roof", "regular_facade"]
|
||||
block_tags = []
|
||||
era_scope = "any"
|
||||
visual_bundle = { wall = ["concrete_wall"], roof = ["flat_roof"], facade = ["regular_facade"], street = ["paved"], color_register = "neutral_grey", fallback = {} }
|
||||
|
||||
[templates.industrial_utilitarian]
|
||||
label = "Industrial Utilitarian"
|
||||
cultural_description = "Heavy-industry vernacular — large-span sheds, exposed structure, infrastructure-as-aesthetic. Reads as a place that makes things at scale."
|
||||
corridor_pool = "baseline"
|
||||
bulk_class_gate = ["BulkSolid", "BulkLiquid"]
|
||||
min_prosperity_bps = 0
|
||||
base_weight = 12000
|
||||
weight_mods = { economic_role = { mining = 18000, manufacturing = 16000 }, dominant_faction = { syndic_dominant = 13000 } }
|
||||
zone_affinity = { industrial = 18000, commercial = 8000 }
|
||||
allow_tags = ["steel_frame", "corrugated_roof", "gantry"]
|
||||
block_tags = ["ornamental_cornice"]
|
||||
era_scope = "any"
|
||||
visual_bundle = { wall = ["steel_frame", "brick_infill"], roof = ["corrugated_roof"], facade = ["industrial_glazing"], street = ["heavy_haul"], color_register = "oxide_and_steel", fallback = { steel_frame = "generic_wall" } }
|
||||
|
||||
[templates.terraced_agrarian]
|
||||
label = "Terraced Agrarian"
|
||||
cultural_description = "A farming-community register — low, clustered dwellings around shared harvest infrastructure, oriented to the land rather than the street. Common where agriculture founds the settlement."
|
||||
corridor_pool = "baseline"
|
||||
bulk_class_gate = ["Perishable", "BulkSolid"]
|
||||
min_prosperity_bps = 0
|
||||
base_weight = 11000
|
||||
weight_mods = { economic_role = { agricultural = 18000 }, morphology_zone = { lowland_plain = 13000 } }
|
||||
zone_affinity = { residential = 14000, civic = 9000 }
|
||||
allow_tags = ["rendered_wall", "pitched_roof", "courtyard"]
|
||||
block_tags = ["glass_curtain_wall"]
|
||||
era_scope = "any"
|
||||
visual_bundle = { wall = ["rendered_wall"], roof = ["pitched_roof", "clay_tile"], facade = ["shuttered_window"], street = ["packed_earth", "cobble"], color_register = "warm_earth", fallback = { rendered_wall = "generic_wall" } }
|
||||
Reference in New Issue
Block a user