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:
2026-06-03 10:47:35 +02:00
co-authored by Claude Opus 4.8
parent 00c8574e11
commit a133b6416e
6 changed files with 351 additions and 0 deletions
@@ -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" } }