Planet-down cascade workshop (3 rounds, 5 agents): layer-by-layer generation from empty world through population overlay, city planning, and street rendering. Includes consultant review by Troblum. Also commits: pre-Sprint-35 DB backup, Claude Code team-mode tmux test log (team-test.md). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
603 lines
31 KiB
Markdown
603 lines
31 KiB
Markdown
---
|
||
title: "Round 2 — Burnelli-Sheldon: Economics Algorithm Proposals"
|
||
description: "Full specification of the three-component district mix model, attractor-matching compatibility matrix, latent settlement generalization under fully generative placement, and TerritorialStatus threshold convergence."
|
||
type: workshop
|
||
status: active
|
||
workshop: planet-down-cascade
|
||
agent: burnelli-sheldon
|
||
round: 2
|
||
created: 2026-05-01
|
||
---
|
||
|
||
# Round 2 — Burnelli-Sheldon: Economics Algorithm Proposals
|
||
|
||
This document provides concrete algorithm specifications for the four economics assignments
|
||
from the Round 1 notes. No positions are hedged — these are proposals for convergence.
|
||
|
||
---
|
||
|
||
## 1. Three-Component District Mix Model — Full Specification
|
||
|
||
### 1.1 Component 1: Population Tier Guarantees
|
||
|
||
The population tier guarantees establish a **mandatory district set** that is allocated
|
||
before the economic role weight table distributes remaining slots. These are derived from
|
||
the sub-settlement hierarchy in the brief, mapped to the D-C4 district count formula.
|
||
|
||
**Guaranteed district set by population tier:**
|
||
|
||
| Sub-settlement tier | Population | Guaranteed types | Notes |
|
||
|--------------------|-----------|-----------------|-------|
|
||
| Waypoint | < 50 | 1× **Mixed** | All functions fused into one district |
|
||
| Outpost | 50–999 | **Residential** + **Mixed** | Two functional zones |
|
||
| Town | 1,000–9,999 | **Residential** + **Commercial** | Entertainment present inside Commercial character |
|
||
| City | 10,000–99,999 | **Residential** + **Commercial** + **Entertainment** | Dedicated entertainment justified by population |
|
||
| Large City | 100,000+ | **Residential** + **Commercial** + **Entertainment** + **Administrative** | Bureaucratic complexity requires dedicated admin |
|
||
|
||
**BS-Q1 resolution (9,999-person Town without dedicated Entertainment):** Confirmed
|
||
plausible. A Town's Commercial district contains the entertainment function — the pub,
|
||
the social hall, the gambling den — as a character property of that district. When the
|
||
settlement grows to City tier (10,000+), specialization makes a dedicated Entertainment
|
||
district economically viable. The function always exists; the dedicated district is the
|
||
marker of scale.
|
||
|
||
**WorldTier exception — domed/cave settlements:** Always 1 district regardless of
|
||
population. Single district type = Mixed, carrying all functions.
|
||
|
||
**Capital city bonus:** +1 district slot (as per D-C4 rule) distributed by the weight
|
||
table after guarantees are placed.
|
||
|
||
**When D-C4 count is less than the guaranteed set size:**
|
||
|
||
D-C4 can produce fewer total district slots than the guaranteed set requires. When this
|
||
occurs, the overflow guaranteed types collapse into Mixed:
|
||
|
||
```
|
||
if D_total < len(guaranteed_set):
|
||
collapse_to_mixed():
|
||
1 district if D_total == 1 → type = Mixed (all functions)
|
||
2 districts if D_total == 2 → Residential + Mixed (remaining functions)
|
||
```
|
||
|
||
This correctly handles outposts with D-C4 = 1 that the formula would label Passage-tier.
|
||
|
||
### 1.2 Component 2: Economic Role Multiplier Table
|
||
|
||
The weight table distributes the **remaining district slots** after the guaranteed set
|
||
is placed. It also applies to the guaranteed set as a character modifier for *which
|
||
variant* of each type the settlement gets — but it does not remove guaranteed types.
|
||
|
||
**No weight is ever zero.** Minimum value is 3 (not 0.2 as I wrote in Round 1 — I
|
||
converted to integer weights summing to 100 per row for implementation clarity). This
|
||
means every district type has some non-zero probability of appearing in any settlement's
|
||
non-guaranteed slots, while the character differences between economic roles remain
|
||
pronounced.
|
||
|
||
**Full weight table (integer weights, each row sums to 100):**
|
||
|
||
| economic_role | Res | Com | Ind | Adm | Log | Ent | Mix | Trn | Spe |
|
||
|--------------|-----|-----|-----|-----|-----|-----|-----|-----|-----|
|
||
| manufacturing | 18 | 8 | 30 | 6 | 18 | 5 | 8 | 5 | 2 |
|
||
| agricultural | 22 | 12 | 5 | 10 | 20 | 5 | 15 | 5 | 6 |
|
||
| extraction | 16 | 7 | 28 | 4 | 24 | 5 | 9 | 5 | 2 |
|
||
| transit | 10 | 18 | 5 | 5 | 20 | 12 | 14 | 14 | 2 |
|
||
| research | 16 | 6 | 5 | 14 | 5 | 7 | 10 | 3 | 34 |
|
||
| commercial | 14 | 30 | 5 | 6 | 10 | 14 | 14 | 5 | 2 |
|
||
| service_mixed | 20 | 18 | 5 | 10 | 6 | 14 | 18 | 7 | 2 |
|
||
| mining | 16 | 7 | 22 | 3 | 28 | 6 | 9 | 7 | 2 |
|
||
| frontier | 26 | 10 | 10 | 5 | 18 | 8 | 16 | 5 | 2 |
|
||
| energy | 10 | 3 | 18 | 6 | 24 | 3 | 6 | 6 | 24 |
|
||
|
||
**How to read this table:**
|
||
|
||
The mining row has Log = 28, Ind = 22 as dominant weights. After placing guaranteed
|
||
Residential + Commercial (at Town) or + Entertainment (at City), remaining slots almost
|
||
always produce Industrial and Logistics districts. Entertainment weight = 6 means
|
||
occasionally a second Entertainment appears in a large mining city — a second drinking
|
||
district for a city that grew big enough to support it. This is correct.
|
||
|
||
The energy row has Spe = 24, Log = 24 as dominant. Energy installations need Specialized
|
||
facilities (reactors, control centers, research wings) and Logistics (heavy transport for
|
||
fuel and output). Entertainment = 3 and Commercial = 3 are minimal but non-zero — large
|
||
energy cities have a company canteen and a gym, which earns each a district representation
|
||
at high enough population.
|
||
|
||
**BS-Q3 resolution (energy Entertainment weight):** At Outpost/Town scale (1-2 districts),
|
||
energy settlements don't get a dedicated Entertainment slot from the weight table —
|
||
Entertainment is present inside the Mixed/Commercial district instead. At City scale
|
||
(10,000+), the guaranteed Entertainment takes effect, producing a dedicated district with
|
||
rough-canteen character. The weight = 3 only applies to *additional* Entertainment
|
||
districts beyond the guarantee, which are rare. This is the correct behavior.
|
||
|
||
**Political archetype modifiers stacked on top:**
|
||
These remain as described in the brief. They're additive modifiers to the weight table
|
||
row before normalization:
|
||
|
||
```
|
||
CompanyTown: Adm -= 10, Ind += 10, Log += 5, Res += 5 (production-heavy, thin governance)
|
||
AdminCapital: Adm += 20, Ent += 5, Spe += 5, Ind -= 15 (governance-heavy, civic services)
|
||
FreePort: Com += 15, Trn += 10, Mix += 5, Adm -= 15 (commerce-heavy, light governance)
|
||
Contested: no modifier (both political forces fight over what to build; neither wins)
|
||
OrganicGrowth: Mix += 15, Res += 10, Ind -= 10, Adm -= 5 (unplanned, service-led growth)
|
||
```
|
||
|
||
### 1.3 Component 3: Settlement Age Modifier
|
||
|
||
**Where founding_age_years comes from:**
|
||
|
||
For named cities: `bodies.founding_age_years` (INTEGER, already in the bodies schema).
|
||
For sub-settlements without a direct bodies row, derive from parent body age:
|
||
|
||
| Sub-settlement type | Derived age |
|
||
|--------------------|-----------:|
|
||
| Mining camp | `max(5, parent_body.founding_age_years - 20)` |
|
||
| Trade waypoint | `max(5, parent_body.founding_age_years / 3)` |
|
||
| Agricultural node | `max(5, parent_body.founding_age_years - 10)` |
|
||
| Shadow node | 10 (always nascent — informal, lacks permanence by definition) |
|
||
|
||
**BS-Q2 resolution:** Age modifier applies only at Backwater tier and above. Waypoint and
|
||
Passage settlements are too structurally simple to differentiate by age; they get
|
||
nascent character regardless of founding date.
|
||
|
||
**Age bracket classification:**
|
||
|
||
| founding_age_years | Age bracket | Name |
|
||
|-------------------|-------------|------|
|
||
| 0–49 | nascent | Raw production focus |
|
||
| 50–149 | young | Functional |
|
||
| 150–349 | established | Differentiated |
|
||
| 350+ | mature | Layered |
|
||
|
||
**What age bracket affects:**
|
||
|
||
Age modifies the **character class** of each district. This propagates to three
|
||
`DistrictSkeleton` fields:
|
||
- `perimeter_treatment`: nascent → Open; young → Fenced; established → Walled; mature → variable (can be any)
|
||
- `density_pct` of commercial/entertainment blocks: nascent = 0.4–0.6; young = 0.5–0.7; established = 0.6–0.8; mature = 0.6–0.9 (wide range — old cities have both dense cores and thin outskirts)
|
||
- Content system character selector: nascent entertainment = "bare supply function"; mature entertainment = "institutional legacy"
|
||
|
||
**What age bracket does NOT affect:** District count. District type. Prosperity baseline.
|
||
These are determined by Components 1 and 2 alone.
|
||
|
||
**Age × WorldTier cap interaction:**
|
||
|
||
```
|
||
effective_age_bracket =
|
||
if world_tier in (Waypoint, Passage): nascent # always
|
||
elif founding_age_years < 50: nascent
|
||
elif founding_age_years < 150: young
|
||
elif founding_age_years < 350: established
|
||
else: mature
|
||
```
|
||
|
||
A 400-year-old Waypoint is still nascent. A 10-year-old Epicenter city is still nascent.
|
||
The cap ensures structural simplicity wins over chronological age for small settlements.
|
||
|
||
### 1.4 The Combined Algorithm — Full Formula
|
||
|
||
```
|
||
Algorithm: compute_district_distribution(city, body, system, terrain)
|
||
|
||
Inputs (all from systems.db or session DB Layer 1-2 output):
|
||
city.population, city.world_tier, city.political_archetype
|
||
body.economic_role, body.founding_age_years, body.settlement_pattern
|
||
system.economic_tier, system.distribution_index
|
||
terrain.topographic_gradient_direction # from Layer 1 output
|
||
seed = child_seed(world_seed, city_id) # SeedChain
|
||
|
||
Returns: Vec<DistrictSpec>
|
||
where DistrictSpec = { type: DistrictType, prosperity_baseline: f32,
|
||
character_class: CharacterClass, density_pct: f32,
|
||
perimeter_treatment: PerimeterTreatment }
|
||
|
||
──────────────────────────────────────────────────────────────
|
||
|
||
Step 1: Total district count
|
||
D_raw = max(1, floor(city.population / 50_000)) # log-scaled for pop > 500k
|
||
D_total = min(D_raw, WorldTier_cap[city.world_tier])
|
||
if city.is_capital: D_total = min(D_total + 1, WorldTier_cap[city.world_tier])
|
||
if body.settlement_pattern in (domed, cave): D_total = 1
|
||
|
||
Step 2: Mandatory districts from population tier
|
||
mandatory_types = guaranteed_set(city.population, city.world_tier)
|
||
# Apply D_total cap: if len(mandatory_types) > D_total, collapse to Mixed
|
||
mandatory = place_guaranteed_types(mandatory_types, D_total)
|
||
remaining_slots = D_total - len(mandatory)
|
||
# NOTE: computation is purely local — NO queries about neighboring cities.
|
||
# Each settlement is computed independently from its own fields and seed.
|
||
|
||
Step 3: Fill remaining slots from economic role weight table
|
||
if remaining_slots > 0:
|
||
weights = ROLE_WEIGHT_TABLE[body.economic_role] # 9-element, all > 0
|
||
weights = apply_archetype_modifiers(weights, city.political_archetype)
|
||
weights = renormalize_to_100(weights)
|
||
# Sample without replacement — can produce duplicate types (second Ind, second Log)
|
||
additional_types = seeded_weighted_sample(weights, remaining_slots, seed)
|
||
all_types = mandatory + additional_types
|
||
else:
|
||
all_types = mandatory
|
||
|
||
Step 4: Assign age character class
|
||
age_bracket = effective_age_bracket(body.founding_age_years, city.world_tier)
|
||
for d in all_types:
|
||
d.character_class = AGE_CHARACTER_TABLE[d.type][age_bracket]
|
||
d.density_pct = age_density_range(d.type, d.character_class)[seeded_float(seed)]
|
||
d.perimeter_treatment = age_perimeter(d.type, age_bracket)
|
||
|
||
Step 5: Assign prosperity gradients
|
||
base_prosperity = economic_tier / 5.0 + ROLE_PROSPERITY_MODIFIER[body.economic_role]
|
||
gradient_direction = terrain.topographic_gradient_direction # high point = high prosperity
|
||
gradient_magnitude = DISTRIBUTION_INDEX_SCALE[system.distribution_index]
|
||
# Paula's topographic modifier is additive to the base, direction set by terrain:
|
||
# hilltop district (top 30% elevation): +0.05
|
||
# flood-adjacent district (bottom 20% vs. sea level): -0.05
|
||
for (i, d) in enumerate(all_types):
|
||
positional_rank = rank_in_gradient_direction(d.grid_position, gradient_direction, len(all_types))
|
||
topo_modifier = topo_offset(d, terrain)
|
||
d.prosperity_baseline = clamp(
|
||
base_prosperity + (positional_rank - 0.5) × gradient_magnitude + topo_modifier,
|
||
0.05, 0.95
|
||
)
|
||
|
||
──────────────────────────────────────────────────────────────
|
||
|
||
ROLE_PROSPERITY_MODIFIER (from L3-Q5 brief formula):
|
||
extraction: -0.10
|
||
research: +0.15
|
||
service_mixed: +0.10
|
||
frontier: -0.20
|
||
(all others: 0.00)
|
||
|
||
DISTRIBUTION_INDEX_SCALE:
|
||
"stratified": 0.7 (steep gradient — 0.7 × positional rank ± 0.35 spread)
|
||
"moderate": 0.2 (shallow gradient — ≈ Gaussian around base, σ = 0.1)
|
||
```
|
||
|
||
**Self-contained confirmation:** The algorithm references only local city/body/system
|
||
fields. There are no lookups into neighboring city data, no distance calculations to
|
||
adjacent settlements, no regional density queries. Each city generates its district
|
||
distribution independently from its seed and the fields above. This satisfies the
|
||
SeedChain isolation property from D-C10.
|
||
|
||
---
|
||
|
||
## 2. Attractor-Matching: Full Compatibility Matrix
|
||
|
||
### 2.1 The Compatibility Matrix
|
||
|
||
Scale 0–10 where 0 = incompatible/excluded and 10 = preferred/ideal match.
|
||
Geographic attractor types: the seven tags from the brief (Section 1, Layer 1 minimum
|
||
viable) plus two additional tags identified by Paula (CoastalLowland, RiverValley) that
|
||
emerged in Round 1 discussion.
|
||
|
||
| economic_role | RiverConfl | CoastalHarbor | MtnPass | ArablePlain | ResourceConc | Defensible | NaturalBarrier | CoastalLowland | RiverValley |
|
||
|--------------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||
| agricultural | 8 | 5 | 2 | 10 | 1 | 2 | 0 | 7 | 8 |
|
||
| extraction | 3 | 3 | 5 | 2 | 10 | 3 | 1 | 2 | 3 |
|
||
| mining | 2 | 2 | 6 | 1 | 10 | 4 | 1 | 1 | 2 |
|
||
| transit | 9 | 9 | 10 | 3 | 1 | 3 | 0 | 5 | 7 |
|
||
| commercial | 8 | 9 | 6 | 4 | 2 | 2 | 0 | 6 | 7 |
|
||
| research | 4 | 4 | 4 | 4 | 6 | 8 | 3 | 3 | 4 |
|
||
| manufacturing | 7 | 6 | 3 | 5 | 5 | 2 | 0 | 6 | 6 |
|
||
| service_mixed | 6 | 6 | 4 | 6 | 2 | 3 | 0 | 6 | 6 |
|
||
| frontier | 5 | 5 | 5 | 4 | 5 | 6 | 3 | 4 | 4 |
|
||
| energy | 4 | 5 | 3 | 2 | 8 | 4 | 2 | 4 | 3 |
|
||
|
||
### 2.2 Forbidden Combinations (score = 0)
|
||
|
||
These are not just low-probability — they are implausible enough that the generator
|
||
should flag them for review rather than place silently:
|
||
|
||
| Economic role | Forbidden attractor | Reason |
|
||
|--------------|--------------------|---------|
|
||
| agricultural | NaturalBarrier | No farming in impassable terrain |
|
||
| transit | NaturalBarrier | Transit hubs do not sit at physical barriers — they use gaps in them (MtnPass) |
|
||
| commercial | NaturalBarrier | Commerce requires accessible flow of goods and people |
|
||
| manufacturing | NaturalBarrier | Heavy industry requires accessible logistics |
|
||
| service_mixed | NaturalBarrier | Services require customers who can reach them |
|
||
|
||
**Note on NaturalBarrier:** This attractor type is a settlement exclusion zone for
|
||
most roles. The only roles with non-zero scores there are extraction (1), mining (1),
|
||
research (3), frontier (3), energy (2) — all roles whose economic activities specifically
|
||
exploit inaccessibility or operate in remote terrain.
|
||
|
||
### 2.3 Preferred Combinations (score = 9–10)
|
||
|
||
These are the "inevitable placements" Ozzie flagged — a player seeing a transit hub at
|
||
a mountain pass should feel the placement was obvious:
|
||
|
||
| Economic role | Preferred attractor | Score | Narrative |
|
||
|--------------|--------------------|----|-----------|
|
||
| transit | MountainPass | 10 | The pass is the reason the city exists |
|
||
| transit | CoastalHarbor | 9 | Port = transit nexus |
|
||
| transit | RiverConfluence | 9 | River junction = historic trade/ferry crossing |
|
||
| agricultural | ArablePlain | 10 | The land is the reason for the city |
|
||
| extraction | ResourceConcentration | 10 | The deposit is the reason for the city |
|
||
| mining | ResourceConcentration | 10 | Same |
|
||
| commercial | CoastalHarbor | 9 | Ports = commercial centers historically |
|
||
|
||
### 2.4 Full Attractor-Matching Algorithm (reconciled from four Round 1 proposals)
|
||
|
||
The four Round 1 proposals (Gestalt, Tyre, Burnelli-Sheldon, Paula) are structurally
|
||
equivalent; they differ in ordering and handling edge cases. This is the convergence:
|
||
|
||
```
|
||
Algorithm: attractor_matching(cities, attractors, body, systems_db, seed)
|
||
|
||
──────────────────────────────────────────────────────────────
|
||
|
||
Phase 0: Build score matrix and validate HQ constraints
|
||
For each city_i and attractor_j:
|
||
S[i,j] = compat_score(city_i.economic_role, attractor_j.type)
|
||
× quality_bonus(attractor_j)
|
||
× population_weight(city_i.population)
|
||
|
||
where:
|
||
quality_bonus = 1.2 if attractor_j is highest-quality of its type on this body
|
||
population_weight = 1.0 + min(log10(city_i.population / 1000) × 0.10, 0.30)
|
||
|
||
HQ hard constraints (corp cross-references in systems.db):
|
||
for each corp with headquarters_body = this body:
|
||
matching_city = city with matching name in atlas_city_names
|
||
compatible_attractors = attractors where compat_score ≥ 6
|
||
if no compatible_attractor exists:
|
||
FLAG for lead review: "[city_name] has no compatible attractor for [corp_role]"
|
||
assign to best available attractor (highest score, even below 6)
|
||
else:
|
||
force S[matching_city, incompatible_attractor] = 0 for all incompatible
|
||
|
||
──────────────────────────────────────────────────────────────
|
||
|
||
Phase 1: Priority ordering
|
||
Sort cities into tiers:
|
||
Tier A: cities with HQ corp cross-reference (locked, must-place)
|
||
Tier B: remaining named cities, sorted by population descending
|
||
Tier C: unnamed cities (population budget, sorted by population descending)
|
||
|
||
──────────────────────────────────────────────────────────────
|
||
|
||
Phase 2: Assign Tier A cities (hard constraints first — Paula's ordering)
|
||
For each corp in extraction-order (most geographically constrained roles first):
|
||
economic role constraint order: extraction → mining → energy → agricultural
|
||
→ manufacturing → transit → commercial → research
|
||
→ service_mixed → frontier
|
||
Assign each Tier A city to its highest-scoring available compatible attractor.
|
||
Mark attractor as consumed.
|
||
|
||
──────────────────────────────────────────────────────────────
|
||
|
||
Phase 3: Assign Tier B and Tier C cities (Hungarian algorithm)
|
||
Build sub-matrix S' for unassigned cities × remaining attractors.
|
||
Solve maximum-weight bipartite matching.
|
||
O(N³) where N ≤ 30: negligible compute.
|
||
|
||
Derive FoundingOrientation from assigned attractor_type:
|
||
RiverConfluence → RiverCrossing
|
||
CoastalHarbor → PortFacing
|
||
MountainPass → PassControl
|
||
ArablePlain → AgriculturalExpanse
|
||
ResourceConc. → ExtractionTether
|
||
Defensible → DefenseFacing
|
||
NaturalBarrier → (fortress pattern within MountainPass or Defensible)
|
||
CoastalLowland → AgriculturalExpanse (coastal variant)
|
||
RiverValley → RiverCrossing (valley variant)
|
||
|
||
──────────────────────────────────────────────────────────────
|
||
|
||
Phase 4: Overflow handling
|
||
If N_cities > M_attractors after Phase 3:
|
||
For each unmatched city (sorted by population descending):
|
||
Place synthetic attractor:
|
||
position = seed_derived offset from nearest NameLocked settlement
|
||
distance ∈ [15%, 40%] × body_scale
|
||
biased toward nearest road corridor (if road graph available)
|
||
type = nearest_attractor.type
|
||
(inherits the dominant attractor character of the region)
|
||
geographically_triggered = false
|
||
FoundingOrientation = AdminFacing (Paula's OrganicGrowth resolution)
|
||
|
||
──────────────────────────────────────────────────────────────
|
||
|
||
Phase 5: Produce SettlementRecord per city
|
||
settlement_class = NameLocked if Tier A
|
||
= PopulationBudget if Tier B/C, geographically_triggered = true
|
||
= OrganicGrowth if overflow, geographically_triggered = false
|
||
```
|
||
|
||
**Ozzie's constraint enforced:** Any placement that triggers a compat_score = 0
|
||
(forbidden combination) is blocked at Phase 0. The generator cannot silently place a
|
||
fishing port in the mountains — it either finds a compatible attractor or flags for lead
|
||
review. This satisfies the "feels inevitable, not arbitrary" requirement.
|
||
|
||
---
|
||
|
||
## 3. Latent Settlements Under Fully Generative Placement
|
||
|
||
### 3.1 What Changed
|
||
|
||
Under the old model, wiki-authored cities were always active. Sub-settlements were latent.
|
||
Under Amendment 3, ALL settlements are generator-placed. The latent concept must generalize.
|
||
|
||
The key economic insight: **latency tracks economic justification, not authoring method.**
|
||
A settlement is latent if its continued existence depends on economic conditions that can
|
||
change. A settlement is non-latent if it exists for structural reasons that don't change
|
||
with economics (a mountain pass town exists because the pass exists; that doesn't change).
|
||
|
||
### 3.2 Settlement Classes and Active Status Logic
|
||
|
||
Introduce a `SettlementClass` enum on all generated settlement records:
|
||
|
||
```rust
|
||
pub enum SettlementClass {
|
||
/// Placed by HQ corp cross-reference. The corp's HQ exists as long as the
|
||
/// corp exists. Active if corp health_metric > 0.0 (any presence).
|
||
NameLocked,
|
||
|
||
/// Placed by population budget — geographic attractor exists but name is
|
||
/// generator-assigned. Active if nearest NameLocked settlement is economically
|
||
/// healthy (its sponsoring corp health > 0.4). Becomes ghost if the anchor corp
|
||
/// collapses and no other corp moves in.
|
||
PopulationBudget,
|
||
|
||
/// Sub-settlements placed by economic trigger conditions:
|
||
/// mining camps, trade waypoints, agricultural nodes.
|
||
/// Active if triggering corp health_metric > 0.4.
|
||
EconomicTriggered,
|
||
|
||
/// Overflow settlements placed at Province centroids without geographic attractor.
|
||
/// Active if provincial avg corp health > 0.5 (needs regional economic density).
|
||
OrganicGrowth,
|
||
}
|
||
```
|
||
|
||
**Why NameLocked is not always-active:** If the corp dissolves entirely
|
||
(`corp_lifecycle_events.event_type = Dissolved`), the HQ city loses its economic
|
||
anchor. The city doesn't vanish spatially (the streets and buildings remain), but
|
||
it becomes a ghost: dark, unmaintained, repopulated by scavengers and squatters.
|
||
The physical city persists; the economic life drains out. This is the only case where
|
||
a NameLocked settlement becomes ghost.
|
||
|
||
**Why this is stronger than the old latent concept:** The old model had named cities
|
||
as unconditionally active and sub-settlements as optionally latent. The new model
|
||
correctly identifies that a city's activity is always economically contingent — it
|
||
just has different trigger conditions by class. The full active/ghost spectrum applies
|
||
to all settlement types.
|
||
|
||
### 3.3 Schema Addition
|
||
|
||
Add `settlement_class` and `geographically_triggered` to the generated settlement
|
||
records in `BodyWorldState`:
|
||
|
||
```rust
|
||
pub struct GeneratedSettlement {
|
||
pub city_id: String,
|
||
pub position: (f32, f32), // body-local normalized coords
|
||
pub founding_orientation: FoundingOrientation,
|
||
pub settlement_class: SettlementClass,
|
||
pub geographically_triggered: bool, // Paula's NEW-Q2 disambiguation flag
|
||
pub activating_corp: Option<String>, // corp_id for EconomicTriggered
|
||
pub activating_condition: Option<String>, // description for OrganicGrowth
|
||
}
|
||
```
|
||
|
||
`geographically_triggered = false` is the OrganicGrowth disambiguation flag Paula
|
||
identified. It answers "was this settlement placed because of a geographic attractor
|
||
or because of population pressure alone?" FoundingOrientation = AdminFacing when false.
|
||
|
||
### 3.4 The Latent Settlement Table in Systems.db
|
||
|
||
My Round 1 recommendation was option (b): store latent settlement positions in a
|
||
systems.db Phase 3 output table. Under Amendment 1 (three-tier execution), this shifts:
|
||
latent settlement positions are runtime background generation output stored in session DB
|
||
(as part of `BodyWorldState`), not systems.db.
|
||
|
||
The economic SIM still reads from systems.db for its aggregate calculations
|
||
(corp_presence, corp_financial_state). It does not need the spatial settlement positions
|
||
to compute economics — it just needs to emit health_metric updates that the renderer
|
||
reads to determine active/ghost status per settlement.
|
||
|
||
**The handoff:** The sim emits `corp_health_changed(corp_id, new_metric)` events.
|
||
The Layer 3-4 generator, when processing a city, queries: is the activating corp's
|
||
health_metric above or below the threshold for this settlement class? This query is
|
||
a single lookup into the economic sim's current state. No full re-generation is needed.
|
||
|
||
---
|
||
|
||
## 4. TerritorialStatus Economic Thresholds — Convergence Proposal
|
||
|
||
The Round 1 notes show three proposals (Paula, Tyre, Burnelli-Sheldon). This section
|
||
proposes convergence thresholds that satisfy all three agents' requirements.
|
||
|
||
**Note on Province dimensions:** Tyre's ARCH-1 through ARCH-4 blockers include
|
||
confirmation of Province scale. The thresholds below use Province-relative quantities
|
||
(fraction of Province cells) to remain valid regardless of absolute Province dimensions.
|
||
When ARCH-1 is resolved, the fractions convert to concrete cell counts.
|
||
|
||
### 4.1 Convergence Table
|
||
|
||
| TerritorialStatus | Threshold | Economic signal |
|
||
|------------------|-----------|----------------|
|
||
| **CoreTerritory** | ≥ 2 named settlements (City or Town) within Province boundaries AND road_coverage_fraction ≥ 0.55 across Province cells AND ≥ 1 active corp (health_metric ≥ 0.5) with presence in Province | Economically integrated, actively maintained |
|
||
| **FrontierTerritory** | ≥ 1 settlement (any tier) in Province AND (road_coverage_fraction < 0.55 OR road_quality < 0.5 at Province boundary edges) | Settled but underdeveloped; economic reach doesn't fill the Province |
|
||
| **ExtractiveZone** | ≥ 1 ResourceConcentration geographic feature tag in Province AND ≥ 1 corp_presence with extraction/mining primary_operation commodity AND road corridor from resource site to nearest City (even if narrow) | Productive but not balanced; the corridor is the territory |
|
||
| **ContestZone** | ≥ 2 settlements from different political_archetype types within Province AND overlapping MaintenanceAuthority on shared road segments (or road networks crossing Province internal boundary without single authority) | Competing claims with no resolution |
|
||
| **WildernessBuffer** | 0 settlements placed + 0 roads + no ResourceConcentration tag | Untouched; no economic vector yet |
|
||
| **AbandonedZone** | ≥ 1 settlement with `placed_at_generation = true` in Province AND 0 active settlements at current sim evaluation (all occupying corps have health_metric < 0.2) | Was settled; economic basis collapsed |
|
||
|
||
### 4.2 What Changed vs. Round 1 (My Position)
|
||
|
||
- **CoreTerritory:** Added "≥ 1 active corp with health_metric ≥ 0.5" as a third condition.
|
||
My Round 1 threshold was purely structural (settlement count + road maintenance). Paula's
|
||
version required the economic health signal. She's right — a ghost city Province with
|
||
intact roads but zero active corps is not CoreTerritory; it's AbandonedZone.
|
||
|
||
- **FrontierTerritory:** Adopted Paula's road quality threshold at Province edges. My Round 1
|
||
version was vague about what "road density < 0.5" meant. Road quality < 0.5 at Province
|
||
boundary is more testable.
|
||
|
||
- **ContestZone:** Adopted Tyre's "two CoreTerritory zones from different political archetypes
|
||
overlapping" framing as the primary test. My Round 1 threshold (competing road networks)
|
||
is now a diagnostic signal rather than the definition.
|
||
|
||
- **AbandonedZone:** Added Paula's `placed_at_generation = true` flag as the necessary
|
||
condition to distinguish "abandoned" from "never settled." My Round 1 version would
|
||
mis-classify a WildernessBuffer Province where a corp briefly established presence and
|
||
then left; the flag provides the correct disambiguation.
|
||
|
||
### 4.3 Runtime Derivation
|
||
|
||
TerritorialStatus can be re-derived at runtime from the economic simulation's state
|
||
without re-running the Layer 1-2 generator. The inputs are:
|
||
|
||
- `settlement_active_state` per settlement (from SettlementClass + corp health query)
|
||
- `road_coverage_fraction` per Province (from Layer 2 session cache — does not change)
|
||
- `corp_presence` + `corp_financial_state.health_metric` (from economic sim)
|
||
|
||
This means TerritorialStatus is a *render parameter* like `prosperity_current` — it
|
||
reflects the current economic state and updates when the sim changes, but it does not
|
||
trigger a layout regeneration. The Province boundaries don't move; only the status label
|
||
changes.
|
||
|
||
---
|
||
|
||
## 5. Open Positions and Requests for Other Agents
|
||
|
||
### 5.1 BS-Q1 — Final Resolution (no further input needed)
|
||
|
||
A 9,999-person Town without a dedicated Entertainment district is economically correct.
|
||
Entertainment is functional inside the Commercial district at Town scale. Gestalt
|
||
validated this in Round 1 notes (no objection to the threshold).
|
||
|
||
### 5.2 Topographic Modifier to prosperity_baseline (Paula + Burnelli-Sheldon)
|
||
|
||
Paula proposed a topographic modifier (+0.05 hilltop, -0.05 flood-adjacent). This is
|
||
included in the formula at Step 5 of §1.4 as an additive offset on the base gradient.
|
||
No incompatibility with my gradient direction/magnitude split. Confirmed compatible.
|
||
|
||
### 5.3 Gestalt: Character table feedback needed
|
||
|
||
The age character classes in §1.3 (AGE_CHARACTER_TABLE) propagate to `DistrictSkeleton`
|
||
fields (perimeter_treatment, density_pct). I need Gestalt to confirm whether these
|
||
fields are sufficient to express the character differences, or whether additional fields
|
||
on `DistrictSkeleton` are needed. Specifically: does a "nascent entertainment district"
|
||
vs. a "mature entertainment district" need a named field, or does density_pct + prosperity
|
||
alone carry it?
|
||
|
||
### 5.4 Tyre: ARCH-3 schema question
|
||
|
||
The attractor-matching algorithm requires `atlas_city_names` with fields:
|
||
`body_id`, `city_name`, `economic_role`, `population`, `hq_for_corp_id` (nullable).
|
||
Does this replace or extend `atlas_cities`? My recommendation: extend with additional
|
||
columns. `atlas_cities` currently stores authored pixel positions (which become obsolete
|
||
under Amendment 3) plus name + population + kind (which are still needed). The new
|
||
columns are: `hq_for_corp_id` (nullable FK to corporations) and `assigned_attractor_id`
|
||
(nullable, written by Layer 2 generator to session DB, not systems.db).
|
||
|
||
### 5.5 Threshold for "abandoned body" condition on NameLocked settlements
|
||
|
||
My SettlementClass model has NameLocked cities becoming ghost when their founding corp
|
||
dissolves. I need confirmation from Paula: does corp dissolution in the economics sim
|
||
produce a `corp_lifecycle_events.event_type = Dissolved` record? The rendering system
|
||
needs a way to read this state. Alternatively: `corp_financial_state.health_metric = 0.0`
|
||
as a proxy. Which is the correct signal to read?
|