Compare commits
56
Commits
@@ -44,7 +44,7 @@ Named after Qatux, the Raiel with perfect memory who helped Paula Myo by recalli
|
||||
- **Work in dedicated round files:** All new rounds happen in `docs/discussions/round-NN-topic.md` from the start. DISCUSSION.md is retired for new content.
|
||||
- **Update the discussion index ONLY when closing:** After a round is formally closed, update `docs/discussions/README.md` with the round entry (number, topic, decisions produced, file link).
|
||||
- **Update briefings:** After a round produces new decisions, update the relevant agent briefing files in `docs/briefings/`.
|
||||
- **Re-index documents:** After archiving or updating documents, re-index them in Qdrant via `db/connectors/qdrant-index <path>`.
|
||||
- **Re-index documents:** After archiving or updating documents, re-index them in Qdrant via `tooling/db/qdrant-index <path>`.
|
||||
|
||||
## Team workflow (mandatory)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Local Services
|
||||
|
||||
Endpoints are also preconfigured in `db/connectors/config.json`.
|
||||
Endpoints are also preconfigured in `tooling/db/config.json`.
|
||||
|
||||
- **Gitea:** `http://git.schweitz.internal` (login: `schweitz`)
|
||||
- **Qdrant:** `http://tower-of-joy:6333/`
|
||||
|
||||
@@ -17,11 +17,15 @@ docs/
|
||||
workshops/ # Workshop briefs and outputs
|
||||
db/
|
||||
schema.sql # Database schema
|
||||
connectors/ # Connector scripts for SQLite and Qdrant
|
||||
connectors/ # Symlink → tooling/db/ (backwards compat, remove after Sprint 22)
|
||||
tooling/
|
||||
db/ # Connector scripts for SQLite, Qdrant, and audio
|
||||
config.json # Endpoint configuration
|
||||
ticket # Ticket CLI
|
||||
sprint # Sprint lifecycle CLI
|
||||
sqlite_connector.py # SQLite mini MCP
|
||||
qdrant_connector.py # Qdrant + ollama mini MCP
|
||||
audio_connector.py # Stable Audio Open connector
|
||||
.claude/
|
||||
agents/ # Agent personality files
|
||||
skills/ # Skill definitions
|
||||
|
||||
@@ -25,6 +25,23 @@
|
||||
"Bash(git ls-tree *)",
|
||||
"Bash(git rev-parse --show-toplevel)",
|
||||
|
||||
"Bash(tooling/db/ticket *)",
|
||||
"Bash(tooling/db/sprint *)",
|
||||
"Bash(tooling/db/sqlite-query *)",
|
||||
"Bash(tooling/db/sqlite-exec *)",
|
||||
"Bash(tooling/db/qdrant-search *)",
|
||||
"Bash(tooling/db/qdrant-index *)",
|
||||
"Bash(tooling/db/qdrant-health)",
|
||||
"Bash(tooling/db/qdrant-count)",
|
||||
"Bash(tooling/db/sqlite-init)",
|
||||
"Bash(tooling/db/decisions-sync)",
|
||||
"Bash(tooling/db/decision *)",
|
||||
|
||||
"Bash(tooling/db/audio-generate *)",
|
||||
"Bash(tooling/db/audio-health)",
|
||||
"Bash(tooling/db/audio-post *)",
|
||||
"Bash(tooling/db/audio-batch *)",
|
||||
|
||||
"Bash(db/connectors/ticket *)",
|
||||
"Bash(db/connectors/sprint *)",
|
||||
"Bash(db/connectors/sqlite-query *)",
|
||||
@@ -35,10 +52,12 @@
|
||||
"Bash(db/connectors/qdrant-count)",
|
||||
"Bash(db/connectors/sqlite-init)",
|
||||
"Bash(db/connectors/decisions-sync)",
|
||||
"Bash(db/connectors/decision *)",
|
||||
|
||||
"Bash(db/connectors/audio-generate *)",
|
||||
"Bash(db/connectors/audio-health)",
|
||||
"Bash(db/connectors/audio-post *)",
|
||||
"Bash(db/connectors/audio-batch *)",
|
||||
|
||||
"Bash(make *)",
|
||||
"Bash(make)",
|
||||
|
||||
@@ -13,7 +13,7 @@ description: >
|
||||
# Audio Generation — The Settled Reach
|
||||
|
||||
Generate sonically consistent audio assets using the Stable Audio Open API via
|
||||
wrapper scripts at `db/connectors/audio-*`.
|
||||
wrapper scripts at `tooling/db/audio-*`.
|
||||
|
||||
Asset descriptions, filenames, bus routing, and design intent are documented in
|
||||
`docs/assets/audio/`. This skill provides the prompt system, generation
|
||||
@@ -25,21 +25,21 @@ workflow, and quality validation.
|
||||
|
||||
```bash
|
||||
# Check API health
|
||||
db/connectors/audio-health
|
||||
tooling/db/audio-health
|
||||
|
||||
# Generate a single asset (WAV only)
|
||||
db/connectors/audio-generate "prompt text" \
|
||||
tooling/db/audio-generate "prompt text" \
|
||||
--duration 10 --steps 100 --cfg 7 \
|
||||
--output path/to/output.wav
|
||||
|
||||
# Generate + post-process in one command (WAV → trim → normalize → OGG)
|
||||
db/connectors/audio-generate "prompt text" \
|
||||
tooling/db/audio-generate "prompt text" \
|
||||
--duration 10 --steps 100 --cfg 7 \
|
||||
--output path/to/gen/intermediate.wav \
|
||||
--output-ogg client/assets/audio/final.ogg
|
||||
|
||||
# Batch-generate from a manifest (preferred for multiple assets)
|
||||
db/connectors/audio-batch docs/assets/audio/batch-s10-327.json
|
||||
tooling/db/audio-batch docs/assets/audio/batch-s10-327.json
|
||||
```
|
||||
|
||||
### Parameters
|
||||
@@ -138,16 +138,16 @@ AMB-001, SFX-002, UI-005). This couples the manifest to the asset inventory.
|
||||
|
||||
```bash
|
||||
# Full run
|
||||
db/connectors/audio-batch docs/assets/audio/batch-s10-327.json
|
||||
tooling/db/audio-batch docs/assets/audio/batch-s10-327.json
|
||||
|
||||
# Dry run — preview what would be generated
|
||||
db/connectors/audio-batch docs/assets/audio/batch-s10-327.json --dry-run
|
||||
tooling/db/audio-batch docs/assets/audio/batch-s10-327.json --dry-run
|
||||
|
||||
# Generate only specific assets
|
||||
db/connectors/audio-batch docs/assets/audio/batch-s10-327.json --only AMB-001,AMB-002
|
||||
tooling/db/audio-batch docs/assets/audio/batch-s10-327.json --only AMB-001,AMB-002
|
||||
|
||||
# Skip assets that already have OGG files
|
||||
db/connectors/audio-batch docs/assets/audio/batch-s10-327.json --skip-existing
|
||||
tooling/db/audio-batch docs/assets/audio/batch-s10-327.json --skip-existing
|
||||
```
|
||||
|
||||
### 3. Update asset docs with prompts
|
||||
@@ -190,8 +190,8 @@ For one-off generation or iteration on a specific asset:
|
||||
2. Read `references/sonic-palette.md` for the sonic family prefix.
|
||||
3. Read `references/category-templates.md` for the matching template.
|
||||
4. Assemble the full prompt.
|
||||
5. Run `db/connectors/audio-health` to verify the API is up.
|
||||
6. Run `db/connectors/audio-generate` with `--post` or `--output-ogg` to
|
||||
5. Run `tooling/db/audio-health` to verify the API is up.
|
||||
6. Run `tooling/db/audio-generate` with `--post` or `--output-ogg` to
|
||||
generate and post-process in one step.
|
||||
7. Verify the output (file size, duration).
|
||||
8. Update the asset status and prompt in `docs/assets/audio/{category}.md`.
|
||||
@@ -218,12 +218,12 @@ If you need to post-process separately (e.g., re-normalizing an existing file):
|
||||
|
||||
```bash
|
||||
# Full pipeline: trim → normalize → convert
|
||||
db/connectors/audio-post pipeline input.wav --output output.ogg
|
||||
tooling/db/audio-post pipeline input.wav --output output.ogg
|
||||
|
||||
# Individual steps
|
||||
db/connectors/audio-post trim input.wav
|
||||
db/connectors/audio-post normalize input.wav --lufs -16
|
||||
db/connectors/audio-post convert input.wav --output output.ogg
|
||||
tooling/db/audio-post trim input.wav
|
||||
tooling/db/audio-post normalize input.wav --lufs -16
|
||||
tooling/db/audio-post convert input.wav --output output.ogg
|
||||
```
|
||||
|
||||
## Manual Synthesis (Insert-Tech Sounds)
|
||||
|
||||
@@ -169,7 +169,7 @@ Construct the ticket title and description from the report summary and any
|
||||
investigation findings. Use the ticket CLI:
|
||||
|
||||
```bash
|
||||
db/connectors/ticket create bug "{title}" --team {team} --description "{description}"
|
||||
tooling/db/ticket create bug "{title}" --team {team} --description "{description}"
|
||||
```
|
||||
|
||||
The description should include:
|
||||
|
||||
@@ -20,14 +20,14 @@ and workflows.
|
||||
|
||||
For precise indexing of specific content:
|
||||
```bash
|
||||
python3 db/connectors/qdrant_connector.py index "unique-id" "Text content to index" --metadata source=manual heading="Custom heading"
|
||||
python3 tooling/db/qdrant_connector.py index "unique-id" "Text content to index" --metadata source=manual heading="Custom heading"
|
||||
```
|
||||
|
||||
### Create collection
|
||||
|
||||
Initialize the Qdrant collection (run once during setup):
|
||||
```bash
|
||||
python3 db/connectors/qdrant_connector.py create-collection
|
||||
python3 tooling/db/qdrant_connector.py create-collection
|
||||
```
|
||||
|
||||
## Bulk Indexing
|
||||
@@ -35,7 +35,7 @@ python3 db/connectors/qdrant_connector.py create-collection
|
||||
Index all project documents at once:
|
||||
```bash
|
||||
for f in decisions/*.md DISCUSSION.md TEAM.md docs/discussions/*.md docs/briefings/*.md; do
|
||||
db/connectors/qdrant-index "$f"
|
||||
tooling/db/qdrant-index "$f"
|
||||
done
|
||||
```
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ Extract ticket IDs from `#NNN` patterns. For each ticket that is
|
||||
currently `in_progress`, update it to `review`:
|
||||
|
||||
```bash
|
||||
db/connectors/ticket status <id> review
|
||||
tooling/db/ticket status <id> review
|
||||
```
|
||||
|
||||
Report which tickets were moved to review. Skip tickets that are
|
||||
|
||||
@@ -83,12 +83,12 @@ raw diff to reviewers — cleaner context, better reviews.
|
||||
worktrees. Each team branch is checked out at:
|
||||
|
||||
```
|
||||
/var/home/jeroenschweitzer/Projects/settled-reach/<branch>/
|
||||
/var/mnt/data/projects/settled-reach/<branch>/
|
||||
```
|
||||
|
||||
For example, the `copy` branch lives at:
|
||||
```
|
||||
/var/home/jeroenschweitzer/Projects/settled-reach/copy/content/dialogue/...
|
||||
/var/mnt/data/projects/settled-reach/copy/content/dialogue/...
|
||||
```
|
||||
|
||||
**All reviewer agents** (regardless of Bash access) should read source files
|
||||
@@ -103,10 +103,10 @@ worktree path. Example instruction for agents:
|
||||
|
||||
```
|
||||
Read the changed files from the branch worktree. The branch is checked
|
||||
out at: /var/home/jeroenschweitzer/Projects/settled-reach/<branch>/
|
||||
out at: /var/mnt/data/projects/settled-reach/<branch>/
|
||||
|
||||
For example, to read `content/dialogue/the-terminal/kael-davan.yaml`,
|
||||
use: /var/home/jeroenschweitzer/Projects/settled-reach/<branch>/content/dialogue/the-terminal/kael-davan.yaml
|
||||
use: /var/mnt/data/projects/settled-reach/<branch>/content/dialogue/the-terminal/kael-davan.yaml
|
||||
```
|
||||
|
||||
Also tell agents to read relevant `decisions/*.md` files from the same
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Use `model: sonnet` for all reviewers — sufficient for review, saves cost.
|
||||
|
||||
**All agents read from worktree paths.** Each branch is checked out at:
|
||||
`/var/home/jeroenschweitzer/Projects/settled-reach/<branch>/`
|
||||
`/var/mnt/data/projects/settled-reach/<branch>/`
|
||||
|
||||
Tell every reviewer agent to read source files from the worktree using the
|
||||
Read tool. Include the worktree base path and a list of changed files in
|
||||
|
||||
@@ -47,10 +47,40 @@ project state. Only generate briefings for teams that have tickets in the sprint
|
||||
| `audio` | `audio` | Inigo (sound design) | Soundscapes, ambient layers, diegetic cues, audio propagation |
|
||||
| `visual` | `visual` | Araminta (art direction) | Art assets, sprites, visual consistency, style guides |
|
||||
| `ci` | `ci` | Justine (build/deploy) | Build pipelines, CI/CD, tooling, packaging |
|
||||
| `planning` | `planning` | Purpose-assembled (see below) | Design discussions, decision resolution, workshop-style tickets |
|
||||
|
||||
When writing briefings, name the assigned agents in the **Agents** line of each
|
||||
file so the team knows who to spawn.
|
||||
|
||||
### Planning Team Tickets
|
||||
|
||||
Some tickets need **design discussion** before implementation can begin — tagged
|
||||
"NEEDS DESIGN DISCUSSION" or blocking multiple downstream tickets with open
|
||||
questions. These run on the `planning` branch as structured discussions with
|
||||
the user and a purpose-assembled agent panel.
|
||||
|
||||
**When to create a planning ticket:**
|
||||
- Ticket description says "NEEDS DESIGN" or "NEEDS DESIGN DISCUSSION"
|
||||
- Ticket blocks 2+ downstream tickets across different teams
|
||||
- Open Q-NNN items that block sprint candidates
|
||||
- Architectural decisions that need multi-domain input before implementation
|
||||
|
||||
**Planning briefing format** (differs from implementation briefings):
|
||||
- **Agents line**: List agents by domain relevance, not fixed team roster.
|
||||
Pick from: Gestalt (systems), Miri (worldbuilding), Araminta (visual/spatial),
|
||||
Tyre (technical), Paula (narrative), Ozzie (player experience), Gore (themes),
|
||||
Nigel (replayability). Typically 4-6 domain agents, plus Qatux (documenter —
|
||||
records decisions, updates domain files) and SI (project manager — creates
|
||||
follow-up tickets, updates sprint assignments).
|
||||
- **Discussion rounds**: Structure the conversation into 2-3 rounds
|
||||
(inventory → proposals → convergence)
|
||||
- **Context section**: List all existing design docs, decisions, and related
|
||||
tickets that participants must read before the discussion
|
||||
- **Output specification**: What the discussion must produce — typically a
|
||||
D-record in `decisions/`, possibly a design doc in `docs/design/`
|
||||
- **Decision questions**: Specific questions the discussion must answer,
|
||||
not open-ended exploration
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Run sprint prepare
|
||||
@@ -58,7 +88,7 @@ file so the team knows who to spawn.
|
||||
Get carry-overs, backlog candidates, and decision gaps in one shot:
|
||||
|
||||
```bash
|
||||
db/connectors/sprint prepare
|
||||
tooling/db/sprint prepare
|
||||
```
|
||||
|
||||
This auto-detects the next sprint number (max ID + 1), creates the sprint
|
||||
@@ -73,10 +103,10 @@ record in `planning` status if needed, and outputs:
|
||||
For critical epics, check their children for granular candidates:
|
||||
|
||||
```bash
|
||||
db/connectors/ticket children <epic_id>
|
||||
tooling/db/ticket children <epic_id>
|
||||
```
|
||||
|
||||
Use `db/connectors/ticket show --brief <id> [<id>...]` to quickly scan multiple tickets.
|
||||
Use `tooling/db/ticket show --brief <id> [<id>...]` to quickly scan multiple tickets.
|
||||
|
||||
### 3. Read existing code state
|
||||
|
||||
@@ -154,14 +184,14 @@ Update it with the theme and goal, then assign tickets:
|
||||
|
||||
```bash
|
||||
# Update the sprint with theme and goal
|
||||
db/connectors/sqlite-exec "UPDATE sprints SET name='Sprint N: Theme', goal='goal' WHERE id=N"
|
||||
tooling/db/sqlite-exec "UPDATE sprints SET name='Sprint N: Theme', goal='goal' WHERE id=N"
|
||||
|
||||
# Assign tickets
|
||||
db/connectors/ticket sprint assign <ticket_id> <sprint_id>
|
||||
tooling/db/ticket sprint assign <ticket_id> <sprint_id>
|
||||
```
|
||||
|
||||
The sprint stays in `planning` status until explicitly activated via
|
||||
`db/connectors/sprint start`. This prevents starting an unplanned sprint.
|
||||
`tooling/db/sprint start`. This prevents starting an unplanned sprint.
|
||||
|
||||
### 8. Present summary
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ Each team gets one briefing file at `docs/sprints/sprint-N/<team>.md`.
|
||||
|---|-------|------------|
|
||||
| #ID | Title | #dependency or — |
|
||||
|
||||
Use `db/connectors/ticket show <id>` for full details.
|
||||
Use `tooling/db/ticket show <id>` for full details.
|
||||
|
||||
## Key Decisions
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ When `/sprint-start` is run on `main`, assess the current sprint state
|
||||
and do the next right thing. Query the database to determine the state:
|
||||
|
||||
```bash
|
||||
db/connectors/sqlite-query "SELECT id, name, status FROM sprints ORDER BY id DESC LIMIT 3"
|
||||
tooling/db/sqlite-query "SELECT id, name, status FROM sprints ORDER BY id DESC LIMIT 3"
|
||||
```
|
||||
|
||||
Then follow the **first matching case**:
|
||||
@@ -48,7 +48,7 @@ Then follow the **first matching case**:
|
||||
First, check whether the sprint's work is actually done:
|
||||
|
||||
```bash
|
||||
db/connectors/sprint status
|
||||
tooling/db/sprint status
|
||||
```
|
||||
|
||||
This shows ticket counts by status (done, in_progress, backlog).
|
||||
@@ -81,7 +81,7 @@ explicitly chooses to close.
|
||||
#### A1. Close the active sprint
|
||||
|
||||
```bash
|
||||
db/connectors/sprint stop
|
||||
tooling/db/sprint stop
|
||||
```
|
||||
|
||||
This marks the active sprint as completed and lists carry-over candidates.
|
||||
@@ -142,7 +142,7 @@ A sprint is ready to activate. Verify it looks complete:
|
||||
```
|
||||
2. Check the ticket count:
|
||||
```bash
|
||||
db/connectors/sprint status --sprint N
|
||||
tooling/db/sprint status --sprint N
|
||||
```
|
||||
|
||||
If briefings are missing or the sprint has 0 tickets, report the gap
|
||||
@@ -151,7 +151,7 @@ and suggest running `/sprint-plan` to complete planning.
|
||||
If everything looks ready, activate the sprint:
|
||||
|
||||
```bash
|
||||
db/connectors/sprint start
|
||||
tooling/db/sprint start
|
||||
```
|
||||
|
||||
Then report:
|
||||
@@ -184,7 +184,7 @@ If the merge has conflicts, report them and stop — do not force-resolve.
|
||||
Run the sprint CLI to get the full context dump in one shot:
|
||||
|
||||
```bash
|
||||
db/connectors/sprint start-work
|
||||
tooling/db/sprint start-work
|
||||
```
|
||||
|
||||
This auto-detects the active sprint and current team from the branch.
|
||||
@@ -204,7 +204,7 @@ If no matching briefing exists for the team, suggest running
|
||||
|
||||
For tickets that need more detail than the `start-work` summary provides:
|
||||
```bash
|
||||
db/connectors/ticket show <id>
|
||||
tooling/db/ticket show <id>
|
||||
```
|
||||
|
||||
### 6. Read key decisions
|
||||
@@ -218,7 +218,7 @@ Mark all actionable (unblocked, non-done) tickets in the sprint as
|
||||
`in_progress`:
|
||||
|
||||
```bash
|
||||
db/connectors/ticket status <id> in_progress
|
||||
tooling/db/ticket status <id> in_progress
|
||||
```
|
||||
|
||||
Then output a summary:
|
||||
@@ -296,8 +296,8 @@ Task(
|
||||
|
||||
2. DB SCRIPTS: When calling ticket/sprint/sqlite scripts, use
|
||||
the exact command with no wrappers or chaining. Examples:
|
||||
db/connectors/ticket show 528
|
||||
db/connectors/ticket list --sprint {N}
|
||||
tooling/db/ticket show 528
|
||||
tooling/db/ticket list --sprint {N}
|
||||
Do NOT prepend python3, do NOT chain with && or ;, do NOT
|
||||
add cleanup commands. Just the bare command.
|
||||
|
||||
@@ -346,7 +346,7 @@ Task(
|
||||
7. If no tasks remain, message the team lead. Do NOT shut down
|
||||
on your own.
|
||||
|
||||
Use `db/connectors/ticket show <id>` for full ticket specs.",
|
||||
Use `tooling/db/ticket show <id>` for full ticket specs.",
|
||||
description: "Sprint {N} {team}: {name}",
|
||||
run_in_background: true
|
||||
)
|
||||
|
||||
@@ -41,7 +41,7 @@ the workflow.
|
||||
Run these two commands in parallel:
|
||||
|
||||
```bash
|
||||
db/connectors/sprint sweep
|
||||
tooling/db/sprint sweep
|
||||
```
|
||||
|
||||
```bash
|
||||
|
||||
@@ -17,60 +17,60 @@ section. This skill covers the full command reference.
|
||||
|
||||
### List tickets (full flags)
|
||||
```bash
|
||||
db/connectors/ticket list [--status S] [--priority P] [--epic N] [--sprint N] [--assigned A] [--team T]
|
||||
tooling/db/ticket list [--status S] [--priority P] [--epic N] [--sprint N] [--assigned A] [--team T]
|
||||
```
|
||||
|
||||
### Create ticket
|
||||
```bash
|
||||
db/connectors/ticket create <type> <title> [--parent N] [--priority P] [--decision D] [--team T]
|
||||
tooling/db/ticket create <type> <title> [--parent N] [--priority P] [--decision D] [--team T]
|
||||
```
|
||||
Types: `initiative`, `epic`, `story`, `task`, `bug`
|
||||
Priorities: `critical`, `high`, `medium`, `low`
|
||||
|
||||
### Update status
|
||||
```bash
|
||||
db/connectors/ticket status <id> <new_status>
|
||||
db/connectors/ticket done <id> [<id> ...]
|
||||
tooling/db/ticket status <id> <new_status>
|
||||
tooling/db/ticket done <id> [<id> ...]
|
||||
```
|
||||
Statuses: `backlog`, `ready`, `in_progress`, `review`, `done`, `cancelled`
|
||||
|
||||
### Assignment
|
||||
```bash
|
||||
db/connectors/ticket assign <id> <agent>
|
||||
db/connectors/ticket unassign <id>
|
||||
tooling/db/ticket assign <id> <agent>
|
||||
tooling/db/ticket unassign <id>
|
||||
```
|
||||
|
||||
### Team assignment
|
||||
```bash
|
||||
db/connectors/ticket team <id> <teams>
|
||||
tooling/db/ticket team <id> <teams>
|
||||
```
|
||||
Teams are comma-separated, e.g. `server`, `client`, `server,client`.
|
||||
|
||||
### Sprint management
|
||||
```bash
|
||||
db/connectors/ticket sprint [--active]
|
||||
db/connectors/ticket sprint assign <id> <sprint_id>
|
||||
tooling/db/ticket sprint [--active]
|
||||
tooling/db/ticket sprint assign <id> <sprint_id>
|
||||
```
|
||||
|
||||
For sprint-scoped operations (status overview, context dumps, lifecycle),
|
||||
use the dedicated sprint CLI instead: `db/connectors/sprint --help`
|
||||
use the dedicated sprint CLI instead: `tooling/db/sprint --help`
|
||||
|
||||
### Dependencies
|
||||
```bash
|
||||
db/connectors/ticket deps <id>
|
||||
tooling/db/ticket deps <id>
|
||||
```
|
||||
|
||||
### Search and browse
|
||||
```bash
|
||||
db/connectors/ticket search <keyword>
|
||||
db/connectors/ticket epics [--status S]
|
||||
db/connectors/ticket children <id>
|
||||
db/connectors/ticket count [--status S]
|
||||
tooling/db/ticket search <keyword>
|
||||
tooling/db/ticket epics [--status S]
|
||||
tooling/db/ticket children <id>
|
||||
tooling/db/ticket count [--status S]
|
||||
```
|
||||
|
||||
### Batch show
|
||||
```bash
|
||||
db/connectors/ticket show --brief <id> [<id>...]
|
||||
tooling/db/ticket show --brief <id> [<id>...]
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
@@ -6,9 +6,45 @@ Format based on [Keep a Changelog](https://keepachangelog.com/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- Moved connector scripts from db/connectors/ to tooling/db/ (#274) — symlink at old path for backwards compatibility
|
||||
|
||||
## [v0.1.20] — 2026-02-25
|
||||
|
||||
### Added
|
||||
- Social site template schema — RoleSchema (#163), SpaceSpec (#164), TriangleDef (#106) with YAML deserialization, sample templates at server/data/templates/
|
||||
- Single-ownership model — TemplateOwnership component, TemplateReferenceMap resource, cross-template reference links preserved across save/load and tier eviction (#165, D-025)
|
||||
- Triangle generation — intra-template constraint satisfaction assigns NPCs to triangle roles, minimum 2 triangles per template with fallback on imperfect seeds (#107)
|
||||
- Triangle escalation system — tick_triangle_escalation runs per game-minute, tension increments toward ToleranceThreshold, TriangleCrisisEvent emitted on Active phase entry, ResolveTriangle stub command (#250, D-087)
|
||||
- Protocol v16 — TriangleCrisisEventWire on ObserverSnapshot for future client rendering of triangle crises
|
||||
- D-093: Sova Transit District spatial layout — 4 social sites (Terminal, Bar, Gate Cluster, Sector 3), 2 encounter nodes, zone palette, gate cluster 7-zone spec, z-level scheme (z=0 maintenance, z=1 main, z=2 observation gallery), 3 investigation paths, corridor widths
|
||||
- D-094: Spatial hierarchy — chunk (64×64 sim) → block (128×128 sim) → district (4×4 blocks, 256×256 visual), supersedes D-014 estimate
|
||||
- D-095: Horizon stations and transport lore — span gates (human-built, dual-use), horizon stations (alien-built, 4-8 apertures), "The Ring" per-system naming, sequential hop travel, The Loop internal tram
|
||||
- Generator architecture workshop brief (ticket #562) — top-down pipeline for district generation, targeting Q-036 resolution
|
||||
- SnapshotEventRouter — callable-based snapshot dispatch replaces inline if-has blocks in main.gd (#559)
|
||||
- YamlParser shared utility — unified YAML parsing for UI strings and checklist conditions (#560)
|
||||
|
||||
### Fixed
|
||||
- Wire triangle crisis event queue into observer snapshot — clients now receive TriangleCrisisEventWire via protocol v16 (was always empty)
|
||||
- Persist TriangleState in SaveStateV1 — triangle phase and tension survive save/load cycles
|
||||
- Validate dangling with_role references in TriangleDef constraint validation
|
||||
- Replace O(n²) fallback NPC assignment with BTreeSet; prevent same NPC assigned to two roles in one triangle
|
||||
- Replace O(N*M) scan in apply_resolve_triangle with BTreeMap index for O(1) per-command lookup
|
||||
- Add From impls for RoleId, TriangleId, StableId, TriangleCrisisEventWire — eliminate fragile .0 newtype access
|
||||
- Consolidate near-identical unit tests with integration counterparts
|
||||
|
||||
### Changed
|
||||
- Sova station profile updated — horizon gates located at The Krenn Ring (800 AU), not on Station Sova; Admin Hub houses transit processing facility only
|
||||
- game_state.gd: stationary_ticks and zone_id now read from server snapshot with deprecated client-side fallbacks (#557, D-020)
|
||||
- dialogue_box.gd: decoupled from GameState and AudioManager via signals — zero direct autoload references (#558, D-020)
|
||||
- main.gd: snapshot dispatch via SnapshotEventRouter, dialogue signal coordinator handlers (#559, #558)
|
||||
- ui_strings.gd and checklist_evaluator.gd: delegate to YamlParser, ~140 lines of duplication removed (#560)
|
||||
|
||||
## [v0.1.19] — 2026-02-25
|
||||
|
||||
### Added
|
||||
- Sprint 20: Shape planned — 11 tickets (server 6, client 4, planning 1) covering template/triangle schemas, client refactors, and district layout design discussion
|
||||
- Planning team ticket type in sprint-plan skill — supports design discussions with purpose-assembled agent panels, Qatux and SI for bookkeeping
|
||||
- Client PR #70 merged — save/load client UI, F5/F6 quicksave/quickload (#554)
|
||||
- Server PR #68 merged — Sprint 19 save/load, tier eviction, test infra (7 tickets, 2714 lines)
|
||||
- Client PR #67 merged — Sprint 19 test infra, session management, debug overlay (5 tickets, 2547 lines)
|
||||
|
||||
@@ -14,7 +14,7 @@ server/ # Rust/bevy_ecs simulation server
|
||||
tooling/ # Build tools, scripts, asset pipelines
|
||||
tests/ # Integration and end-to-end tests
|
||||
docs/ # Architecture, design, briefings, sprints, workshops
|
||||
db/ # Schema + connector scripts (ticket CLI, SQLite, Qdrant)
|
||||
db/ # Schema + seed data (connectors moved to tooling/db/)
|
||||
.claude/ # Agents, skills, rules
|
||||
decisions/ # Decision domain files (D-NNN confirmed, Q-NNN open, R-NNN rejected)
|
||||
```
|
||||
@@ -42,7 +42,7 @@ The ticketing database (`settledreach.db`) lives in the **parent directory** sha
|
||||
|
||||
### Before starting work
|
||||
1. Read your sprint briefing at `docs/sprints/sprint-N/{team}.md` for current tasks
|
||||
2. Use `db/connectors/ticket show <id>` for full ticket details
|
||||
2. Use `tooling/db/ticket show <id>` for full ticket details
|
||||
3. Read the relevant `decisions/*.md` domain file(s) referenced in the briefing
|
||||
4. Background context: `docs/briefings/{your-name}.md`, `docs/discussions/`
|
||||
|
||||
@@ -52,19 +52,19 @@ The ticketing database (`settledreach.db`) lives in the **parent directory** sha
|
||||
|
||||
| Tool | Command | Full reference |
|
||||
|------|---------|----------------|
|
||||
| Tickets | `db/connectors/ticket list`, `show`, `create`, `assign` | `/ticket` skill |
|
||||
| Sprints | `db/connectors/sprint status`, `start-work`, `prepare` | `/sprint-start` skill |
|
||||
| SQL queries | `db/connectors/sqlite-query "SELECT ..."` | — |
|
||||
| SQL writes | `db/connectors/sqlite-exec "UPDATE ..."` | — |
|
||||
| Decisions | `db/connectors/decision next`, `claim`, `check-dupes` | — |
|
||||
| Doc search | `db/connectors/qdrant-search "query"` | `/docs-search` skill |
|
||||
| Doc index | `db/connectors/qdrant-index path/to/file.md` | `/docs-search` skill |
|
||||
| Tickets | `tooling/db/ticket list`, `show`, `create`, `assign` | `/ticket` skill |
|
||||
| Sprints | `tooling/db/sprint status`, `start-work`, `prepare` | `/sprint-start` skill |
|
||||
| SQL queries | `tooling/db/sqlite-query "SELECT ..."` | — |
|
||||
| SQL writes | `tooling/db/sqlite-exec "UPDATE ..."` | — |
|
||||
| Decisions | `tooling/db/decision next`, `claim`, `check-dupes` | — |
|
||||
| Doc search | `tooling/db/qdrant-search "query"` | `/docs-search` skill |
|
||||
| Doc index | `tooling/db/qdrant-index path/to/file.md` | `/docs-search` skill |
|
||||
|
||||
### File conventions
|
||||
- Decisions: domain files in `decisions/` (see `decisions/README.md` for index)
|
||||
- Decision IDs: `D-NNN` (confirmed), `Q-NNN` (open questions), `R-NNN` (rejected)
|
||||
- **Claim IDs before writing:** `db/connectors/decision claim D <domain> "title"` — prevents ID collisions across worktrees
|
||||
- **Claim IDs before writing:** `tooling/db/decision claim D <domain> "title"` — prevents ID collisions across worktrees
|
||||
- Diagrams: `.d2` source + `.png` renders in `docs/diagrams/{category}/`. Create or update diagrams via `/d2-diagram` when D-records are added or modified.
|
||||
- Discussion rounds: numbered sequentially, archived to `docs/discussions/` when complete
|
||||
- Briefings: one per agent, updated after decision-producing rounds
|
||||
- Tickets: managed via `db/connectors/ticket` CLI or `/ticket` skill
|
||||
- Tickets: managed via `tooling/db/ticket` CLI or `/ticket` skill
|
||||
|
||||
@@ -284,16 +284,16 @@ db-install:
|
||||
# --- Decisions ---
|
||||
|
||||
decisions-sync:
|
||||
@db/connectors/decisions-sync
|
||||
@tooling/db/decisions-sync
|
||||
|
||||
decisions-coverage:
|
||||
@db/connectors/sqlite-query "SELECT d.domain, COUNT(DISTINCT d.id) as decisions, COUNT(DISTINCT t.decision_ref) as with_tickets FROM decisions d LEFT JOIN tickets t ON d.id = t.decision_ref WHERE d.status='active' AND d.type='confirmed' GROUP BY d.domain"
|
||||
@tooling/db/sqlite-query "SELECT d.domain, COUNT(DISTINCT d.id) as decisions, COUNT(DISTINCT t.decision_ref) as with_tickets FROM decisions d LEFT JOIN tickets t ON d.id = t.decision_ref WHERE d.status='active' AND d.type='confirmed' GROUP BY d.domain"
|
||||
|
||||
decisions-active:
|
||||
@db/connectors/sqlite-query "SELECT id, domain, title FROM decisions WHERE status='active' AND type='confirmed' ORDER BY domain, id"
|
||||
@tooling/db/sqlite-query "SELECT id, domain, title FROM decisions WHERE status='active' AND type='confirmed' ORDER BY domain, id"
|
||||
|
||||
decisions-orphan:
|
||||
@db/connectors/sqlite-query "SELECT id, title FROM decisions WHERE type='confirmed' AND status='active' AND id NOT IN (SELECT DISTINCT decision_ref FROM tickets WHERE decision_ref IS NOT NULL)"
|
||||
@tooling/db/sqlite-query "SELECT id, title FROM decisions WHERE type='confirmed' AND status='active' AND id NOT IN (SELECT DISTINCT decision_ref FROM tickets WHERE decision_ref IS NOT NULL)"
|
||||
|
||||
# --- Content Validation ---
|
||||
|
||||
|
||||
@@ -109,6 +109,7 @@ notifications:
|
||||
load_failed: "Load failed."
|
||||
connection_lost: "Signal interrupted."
|
||||
connection_restored: "Signal restored."
|
||||
loading: "Resuming..."
|
||||
|
||||
# ============================================================
|
||||
# KNOWLEDGE PANEL LABELS
|
||||
@@ -183,6 +184,9 @@ menu:
|
||||
confirm_quit: "Unsaved progress will be lost."
|
||||
confirm_yes: "Yes"
|
||||
confirm_no: "No"
|
||||
load_game_browse: "LOAD GAME"
|
||||
load_game_back: "BACK"
|
||||
load_game_empty: "No saves found."
|
||||
|
||||
settings:
|
||||
audio_volume: "Volume"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[gd_scene load_steps=26 format=3 uid="uid://bswrmh7w8dbgm"]
|
||||
[gd_scene load_steps=27 format=3 uid="uid://bswrmh7w8dbgm"]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/main.gd" id="1_main"]
|
||||
[ext_resource type="Script" path="res://scripts/rendering/world_renderer.gd" id="2_world"]
|
||||
@@ -21,10 +21,11 @@
|
||||
[ext_resource type="PackedScene" path="res://ui/checklist_overlay.tscn" id="18_checklist"]
|
||||
[ext_resource type="PackedScene" path="res://ui/bug_report_dialog.tscn" id="19_bugreport"]
|
||||
[ext_resource type="PackedScene" path="res://ui/settings_dialog.tscn" id="21_settings"]
|
||||
[ext_resource type="Script" path="res://scripts/ui/debug_overlay.gd" id="22_debug"]
|
||||
[ext_resource type="Script" path="res://ui/debug_overlay.gd" id="22_debug"]
|
||||
[ext_resource type="PackedScene" path="res://ui/time_display.tscn" id="23_tdisplay"]
|
||||
[ext_resource type="PackedScene" path="res://ui/examine_display.tscn" id="24_examine"]
|
||||
[ext_resource type="PackedScene" path="res://ui/journal_panel.tscn" id="25_journal"]
|
||||
[ext_resource type="PackedScene" path="res://ui/loading_screen.tscn" id="26_loading"]
|
||||
|
||||
[node name="Game" type="Node2D"]
|
||||
script = ExtResource("1_main")
|
||||
@@ -190,3 +191,6 @@ layer = 30
|
||||
|
||||
; #528: Audio settings dialog — 5-bus volume sliders, ESC/OPEN_MENU to toggle
|
||||
[node name="SettingsDialog" parent="ModalLayer" instance=ExtResource("21_settings")]
|
||||
|
||||
; #257: Loading screen — full-screen overlay during save/load round-trip
|
||||
[node name="LoadingScreen" parent="ModalLayer" instance=ExtResource("26_loading")]
|
||||
|
||||
@@ -59,8 +59,66 @@ text = "CONTINUE"
|
||||
theme_override_font_sizes/font_size = 15
|
||||
theme_override_colors/font_color = Color(0.906, 0.773, 0.278, 1.0)
|
||||
|
||||
[node name="LoadGameBtn" type="Button" parent="VBox"]
|
||||
layout_mode = 2
|
||||
text = "LOAD GAME"
|
||||
theme_override_font_sizes/font_size = 15
|
||||
theme_override_colors/font_color = Color(0.906, 0.773, 0.278, 1.0)
|
||||
|
||||
[node name="QuitBtn" type="Button" parent="VBox"]
|
||||
layout_mode = 2
|
||||
text = "QUIT"
|
||||
theme_override_font_sizes/font_size = 15
|
||||
theme_override_colors/font_color = Color(0.533, 0.565, 0.627, 1.0)
|
||||
|
||||
[node name="LoadGamePanel" type="Control" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
visible = false
|
||||
|
||||
[node name="PanelBg" type="ColorRect" parent="LoadGamePanel"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0.05, 0.05, 0.08, 0.96)
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="LoadGamePanel"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -160.0
|
||||
offset_top = -180.0
|
||||
offset_right = 160.0
|
||||
offset_bottom = 180.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/separation = 12
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="LoadGamePanel/VBox"]
|
||||
layout_mode = 2
|
||||
text = "LOAD GAME"
|
||||
horizontal_alignment = 1
|
||||
theme_override_font_sizes/font_size = 20
|
||||
theme_override_colors/font_color = Color(0.784, 0.816, 0.878, 1.0)
|
||||
|
||||
[node name="SavesScroll" type="ScrollContainer" parent="LoadGamePanel/VBox"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(320, 240)
|
||||
|
||||
[node name="SavesList" type="VBoxContainer" parent="LoadGamePanel/VBox/SavesScroll"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="BackBtn" type="Button" parent="LoadGamePanel/VBox"]
|
||||
layout_mode = 2
|
||||
text = "BACK"
|
||||
theme_override_font_sizes/font_size = 14
|
||||
theme_override_colors/font_color = Color(0.533, 0.565, 0.627, 1.0)
|
||||
|
||||
@@ -75,6 +75,11 @@ var rng_seed: Variant = null
|
||||
# One-shot: consumed by main.gd after display, then set back to null.
|
||||
var save_result: Variant = null
|
||||
|
||||
# #257: Pending load path — set by main menu "Load Game" selection.
|
||||
# main.gd sends LOAD_GAME on startup if non-empty, then clears this field.
|
||||
# Format: user://saves/<game-id>/<filename>.sav or "" if no pending load.
|
||||
var pending_load_path: String = ""
|
||||
|
||||
# v7 fields (#431, D-059/D-060)
|
||||
var pending_recognitions: Array = [] # [{entity_id, x, y, z, remaining_ticks, total_delay_ticks}]
|
||||
|
||||
@@ -109,14 +114,17 @@ var medium_sound_events: Array = []
|
||||
var close_sound_events: Array = []
|
||||
|
||||
# D-071 (#530): Consecutive ticks without player position change.
|
||||
# Incremented per snapshot in apply_snapshot(). Reset to 0 on movement.
|
||||
# D-020: Server-authoritative — read from snapshot "stationary_ticks" field.
|
||||
# Fallback: client-side accumulation (deprecated, remove when server populates field).
|
||||
# ListeningFocus boost activates at 30+ ticks (main.gd manages the dip).
|
||||
var stationary_ticks: int = 0
|
||||
# DEPRECATED: Only used by client-side accumulation fallback. Remove with fallback.
|
||||
var _prev_player_position: Vector2 = Vector2(-1e9, -1e9) # sentinel: no previous position
|
||||
|
||||
# D-073 (#529): Server-authoritative zone_id from the player's current tile.
|
||||
# Extracted in apply_snapshot() — avoids O(N) tile scan in main.gd per Tyre review.
|
||||
# Empty string when zone_id field absent (server hasn't shipped OQ-09 yet).
|
||||
# D-020: Read directly from snapshot "zone_id" field.
|
||||
# Fallback: client-side tile lookup (deprecated, remove when server populates field).
|
||||
# Empty string when zone_id field absent.
|
||||
var current_zone_id: String = ""
|
||||
|
||||
func apply_snapshot(snapshot: Dictionary) -> void:
|
||||
@@ -140,12 +148,19 @@ func apply_snapshot(snapshot: Dictionary) -> void:
|
||||
push_warning("GameState: no Player entity found in %d entities" % [
|
||||
visible_entities.size()])
|
||||
|
||||
# D-071 (#530): Track consecutive stationary ticks for ListeningFocus boost.
|
||||
# Compares current player_position against previous snapshot's position.
|
||||
if player_position == _prev_player_position:
|
||||
stationary_ticks += 1
|
||||
# D-020/D-071 (#530): Server-authoritative stationary_ticks for ListeningFocus boost.
|
||||
# Prefer server-sent value; fall back to client-side accumulation until server populates.
|
||||
if snapshot.has("stationary_ticks") and snapshot.stationary_ticks is int:
|
||||
# D-020: direct field assignment from server-authoritative snapshot.
|
||||
stationary_ticks = snapshot.stationary_ticks
|
||||
else:
|
||||
stationary_ticks = 0
|
||||
# DEPRECATED fallback — client-side accumulation. Remove when server sends
|
||||
# "stationary_ticks" in ObserverSnapshot (D-020 violation: derives behavior-
|
||||
# driving state on the client). Server tracks this in ListeningFocus component.
|
||||
if player_position == _prev_player_position:
|
||||
stationary_ticks += 1
|
||||
else:
|
||||
stationary_ticks = 0
|
||||
_prev_player_position = player_position
|
||||
|
||||
# Tiles for rendering: test mode sends "tiles", live server sends tile data in "visible_tiles"
|
||||
@@ -295,16 +310,24 @@ func apply_snapshot(snapshot: Dictionary) -> void:
|
||||
if snapshot.has("player_knowledge") and snapshot.player_knowledge is Dictionary:
|
||||
player_knowledge = snapshot.player_knowledge
|
||||
|
||||
# D-073 (#529): O(1) zone_id lookup. Build coord→tile dict from member visible_tiles
|
||||
# (populated above from either "tiles" test-mode key or "visible_tiles" live key).
|
||||
# Must use the member var, not snapshot.visible_tiles, so test mode is covered.
|
||||
var _tile_by_coord: Dictionary = {}
|
||||
for vtile in visible_tiles:
|
||||
if vtile is Dictionary and vtile.has("x") and vtile.has("y"):
|
||||
_tile_by_coord[Vector2i(vtile.x, vtile.y)] = vtile
|
||||
var player_pos_key := Vector2i(int(player_position.x), int(player_position.y))
|
||||
var player_tile = _tile_by_coord.get(player_pos_key, null)
|
||||
current_zone_id = player_tile.get("zone_id", "") if player_tile else ""
|
||||
# D-020/D-073 (#529): Server-authoritative zone_id for zone ambient crossfade.
|
||||
# Prefer server-sent top-level value; fall back to client-side tile lookup until
|
||||
# server populates top-level "zone_id" in ObserverSnapshot.
|
||||
if snapshot.has("zone_id") and snapshot.zone_id is String:
|
||||
# D-020: direct field assignment from server-authoritative snapshot.
|
||||
current_zone_id = snapshot.zone_id
|
||||
else:
|
||||
# DEPRECATED fallback — client-side tile lookup. Remove when server sends
|
||||
# top-level "zone_id" in ObserverSnapshot (D-020 violation: derives zone
|
||||
# identity on the client via tile iteration). Server sends zone_id per
|
||||
# VisibleTile but not as a top-level snapshot field.
|
||||
var _tile_by_coord: Dictionary = {}
|
||||
for vtile in visible_tiles:
|
||||
if vtile is Dictionary and vtile.has("x") and vtile.has("y"):
|
||||
_tile_by_coord[Vector2i(vtile.x, vtile.y)] = vtile
|
||||
var player_pos_key := Vector2i(int(player_position.x), int(player_position.y))
|
||||
var player_tile = _tile_by_coord.get(player_pos_key, null)
|
||||
current_zone_id = player_tile.get("zone_id", "") if player_tile else ""
|
||||
|
||||
# v2: visible_tiles with visibility sectors
|
||||
# Derives visible_positions when not explicitly provided (real server mode)
|
||||
|
||||
@@ -49,44 +49,6 @@ func reload() -> void:
|
||||
|
||||
## Parse YAML with arbitrary nesting depth.
|
||||
## Returns flat Dictionary with dotted keys: { "section.sub.key": "value" }.
|
||||
## Delegates to YamlParser.parse_flat() (#560).
|
||||
static func _parse_yaml(text: String) -> Dictionary:
|
||||
var strings := {}
|
||||
var stack: Array = [] # [[indent, key], ...]
|
||||
for line in text.split("\n"):
|
||||
var stripped := line.strip_edges(false, true)
|
||||
if stripped.is_empty() or stripped.begins_with("#"):
|
||||
continue
|
||||
var indent := line.length() - line.lstrip(" ").length()
|
||||
var content := stripped.strip_edges()
|
||||
var colon_pos := content.find(":")
|
||||
if colon_pos < 0:
|
||||
continue
|
||||
var key := content.substr(0, colon_pos).strip_edges()
|
||||
var val := content.substr(colon_pos + 1).strip_edges()
|
||||
# Trailing comment without a value — treat as section header
|
||||
if val.begins_with("#"):
|
||||
val = ""
|
||||
# Pop sections at same or deeper indent
|
||||
while stack.size() > 0 and stack.back()[0] >= indent:
|
||||
stack.pop_back()
|
||||
if val.is_empty():
|
||||
# Section header — push onto stack
|
||||
stack.push_back([indent, key])
|
||||
else:
|
||||
# Leaf value — extract from quotes or strip inline comment
|
||||
if val.begins_with("\""):
|
||||
var end_quote := val.find("\"", 1)
|
||||
if end_quote > 0:
|
||||
val = val.substr(1, end_quote - 1)
|
||||
else:
|
||||
val = val.substr(1)
|
||||
else:
|
||||
var comment_pos := val.find(" #")
|
||||
if comment_pos >= 0:
|
||||
val = val.substr(0, comment_pos).strip_edges()
|
||||
var dotted_key := ""
|
||||
for entry in stack:
|
||||
dotted_key += entry[1] + "."
|
||||
dotted_key += key
|
||||
strings[dotted_key] = val
|
||||
return strings
|
||||
return YamlParser.parse_flat(text)
|
||||
|
||||
@@ -233,12 +233,7 @@ func _find_entity(entity_id: int) -> bool:
|
||||
return false
|
||||
|
||||
|
||||
# -- YAML parsing (checklist-specific) -----------------------------------------
|
||||
# Handles the constrained checklist YAML format: top-level key:value pairs,
|
||||
# a conditions array of flat dictionaries. No nested arrays or anchors.
|
||||
#
|
||||
# Limitation: unquoted values containing " #" are truncated at the comment marker.
|
||||
# Use quoted strings ("value # with hash") if values must contain literal hashes.
|
||||
# -- YAML parsing --------------------------------------------------------------
|
||||
|
||||
func _load_checklist_file(path: String) -> Dictionary:
|
||||
if not FileAccess.file_exists(path):
|
||||
@@ -252,103 +247,6 @@ func _load_checklist_file(path: String) -> Dictionary:
|
||||
return parse_checklist_yaml(text)
|
||||
|
||||
|
||||
## Delegates to YamlParser.parse() (#560).
|
||||
static func parse_checklist_yaml(text: String) -> Dictionary:
|
||||
var result := {}
|
||||
var conditions: Array = []
|
||||
var current_item: Dictionary = {}
|
||||
var in_conditions := false
|
||||
|
||||
for line in text.split("\n"):
|
||||
var stripped := line.strip_edges(false, true)
|
||||
if stripped.is_empty() or stripped.strip_edges().begins_with("#"):
|
||||
continue
|
||||
|
||||
var indent := line.length() - line.lstrip(" ").length()
|
||||
var content := stripped.strip_edges()
|
||||
|
||||
# Detect conditions: array header
|
||||
if content == "conditions:":
|
||||
in_conditions = true
|
||||
continue
|
||||
|
||||
if not in_conditions:
|
||||
# Top-level key: value
|
||||
var colon := content.find(":")
|
||||
if colon >= 0:
|
||||
var key := content.substr(0, colon).strip_edges()
|
||||
var val_str := content.substr(colon + 1).strip_edges()
|
||||
result[key] = _parse_value(val_str)
|
||||
else:
|
||||
if content.begins_with("- "):
|
||||
# New array item — flush previous
|
||||
if not current_item.is_empty():
|
||||
conditions.append(current_item)
|
||||
current_item = {}
|
||||
var rest := content.substr(2).strip_edges()
|
||||
var colon := rest.find(":")
|
||||
if colon >= 0:
|
||||
var key := rest.substr(0, colon).strip_edges()
|
||||
var val_str := rest.substr(colon + 1).strip_edges()
|
||||
current_item[key] = _parse_value(val_str)
|
||||
elif indent >= 2 and not current_item.is_empty():
|
||||
# Continuation of current array item
|
||||
var colon := content.find(":")
|
||||
if colon >= 0:
|
||||
var key := content.substr(0, colon).strip_edges()
|
||||
var val_str := content.substr(colon + 1).strip_edges()
|
||||
current_item[key] = _parse_value(val_str)
|
||||
elif indent == 0:
|
||||
# Back to top level — shouldn't happen in valid checklist YAML
|
||||
in_conditions = false
|
||||
if not current_item.is_empty():
|
||||
conditions.append(current_item)
|
||||
current_item = {}
|
||||
var colon := content.find(":")
|
||||
if colon >= 0:
|
||||
var key := content.substr(0, colon).strip_edges()
|
||||
var val_str := content.substr(colon + 1).strip_edges()
|
||||
result[key] = _parse_value(val_str)
|
||||
|
||||
# Flush last item
|
||||
if not current_item.is_empty():
|
||||
conditions.append(current_item)
|
||||
|
||||
if not conditions.is_empty():
|
||||
result["conditions"] = conditions
|
||||
|
||||
return result
|
||||
|
||||
|
||||
static func _parse_value(val: String) -> Variant:
|
||||
if val.is_empty():
|
||||
return ""
|
||||
|
||||
# Strip inline comments (not inside quotes)
|
||||
if not val.begins_with("\""):
|
||||
var comment_pos := val.find(" #")
|
||||
if comment_pos >= 0:
|
||||
val = val.substr(0, comment_pos).strip_edges()
|
||||
|
||||
# Quoted string
|
||||
if val.begins_with("\""):
|
||||
var end_quote := val.find("\"", 1)
|
||||
if end_quote > 0:
|
||||
return val.substr(1, end_quote - 1)
|
||||
return val.substr(1)
|
||||
|
||||
# Boolean
|
||||
if val == "true":
|
||||
return true
|
||||
if val == "false":
|
||||
return false
|
||||
|
||||
# Float (contains decimal point)
|
||||
if val.contains(".") and val.is_valid_float():
|
||||
return val.to_float()
|
||||
|
||||
# Integer
|
||||
if val.is_valid_int():
|
||||
return val.to_int()
|
||||
|
||||
# Plain string
|
||||
return val
|
||||
return YamlParser.parse(text)
|
||||
|
||||
+137
-87
@@ -21,6 +21,7 @@ extends Node2D
|
||||
@onready var debug_overlay = $UILayer/DebugOverlay # #511: F3 debug overlay
|
||||
@onready var bug_report_dialog = $ModalLayer/BugReportDialog # #495: F12 WRONG button
|
||||
@onready var settings_dialog = $ModalLayer/SettingsDialog # #528: audio settings (ESC/OPEN_MENU)
|
||||
@onready var loading_screen = $ModalLayer/LoadingScreen # #257: blocking overlay during load
|
||||
|
||||
var _last_dialogue_npc_id: int = -1 # D-064: NPC entity_id for WalkAway input
|
||||
var _last_dialogue_npc_name: String = "" # #535: NPC name for dialogue_response attribution
|
||||
@@ -33,6 +34,7 @@ var _flash_rect: ColorRect = null # #502/#501: ephemeral screen flash overlay (
|
||||
var _teleport_in_progress: bool = false # #501/#117: forces camera snap (not lerp) on next _process frame
|
||||
var _pending_record_inputs: Array = [] # #507: accumulates server-bound inputs across frames; flushed into record_tick() on snapshot arrival
|
||||
var _current_zone: String = "" # D-073 (#529): zone tracking for ambient crossfades
|
||||
var _router: SnapshotEventRouter # #559: callable-based snapshot dispatch
|
||||
|
||||
const LISTENING_FOCUS_TICKS: int = 30 # D-071: stationary ticks before ListeningFocus boost activates
|
||||
|
||||
@@ -48,6 +50,17 @@ func _ready() -> void:
|
||||
# Connect to simulation (test mode sets CONNECTED immediately)
|
||||
SimBridge.connect_to_sim()
|
||||
|
||||
# #257: If returning from main menu "Load Game" selection, defer dispatch until connected.
|
||||
# In test mode, connect_to_sim() sets CONNECTED synchronously — dispatch fires immediately.
|
||||
# In live mode, state is CONNECTING — signal handler dispatches once connected.
|
||||
if not GameState.pending_load_path.is_empty():
|
||||
if loading_screen:
|
||||
loading_screen.show_loading()
|
||||
if SimBridge.state == SimBridge.ConnectionState.CONNECTED:
|
||||
_dispatch_pending_load()
|
||||
else:
|
||||
SimBridge.connection_state_changed.connect(_on_sim_connected_for_load)
|
||||
|
||||
# Camera anchor: snap to player position before the first frame renders.
|
||||
# In test mode poll_snapshot() returns synchronously — position is set
|
||||
# immediately. In live mode the snapshot isn't available yet — _process
|
||||
@@ -65,11 +78,51 @@ func _ready() -> void:
|
||||
dialogue_box.confrontation_monologue.connect(_on_confrontation_monologue)
|
||||
dialogue_box.pause_requested.connect(_on_dialogue_pause_requested)
|
||||
dialogue_box.unpause_requested.connect(_on_dialogue_unpause_requested)
|
||||
# D-020 (#558): Decoupled signals — coordinator routes state changes.
|
||||
dialogue_box.dialogue_state_changed.connect(_on_dialogue_state_changed)
|
||||
dialogue_box.audio_dip_requested.connect(_on_audio_dip_requested)
|
||||
dialogue_box.audio_dip_cleared.connect(_on_audio_dip_cleared)
|
||||
|
||||
# #496: Print gauntlet session summary on disconnect
|
||||
if gauntlet_hud:
|
||||
SimBridge.connection_state_changed.connect(_on_connection_state_changed)
|
||||
|
||||
# #559: Register snapshot dispatch handlers — replaces inline dispatch in _process().
|
||||
_router = SnapshotEventRouter.new()
|
||||
# Always-run: child nodes that update from GameState on every snapshot tick.
|
||||
if world_renderer:
|
||||
_router.register_always(world_renderer.update_from_state)
|
||||
_router.register_always(_propagate_insert_state)
|
||||
_router.register_always(_update_interaction_list)
|
||||
if inventory_grid:
|
||||
_router.register_always(inventory_grid.update_from_state)
|
||||
if stance_indicator:
|
||||
_router.register_always(stance_indicator.update_from_state)
|
||||
if fog_entities:
|
||||
_router.register_always(fog_entities.update_from_state)
|
||||
_router.register_always(_play_recognition_chimes)
|
||||
if gauntlet_hud:
|
||||
_router.register_always(gauntlet_hud.update_from_state)
|
||||
if checklist_overlay:
|
||||
_router.register_always(checklist_overlay.update_from_state)
|
||||
if time_display:
|
||||
_router.register_always(time_display.update_from_state)
|
||||
if journal_panel:
|
||||
_router.register_always(journal_panel.update_from_state)
|
||||
if debug_overlay:
|
||||
_router.register_always(debug_overlay.update_from_state)
|
||||
_router.register_always(_play_close_sound_events)
|
||||
_router.register_always(_update_zone)
|
||||
_router.register_always(_update_listening_focus)
|
||||
_router.register_always(_consume_examine_result)
|
||||
# Keyed: consume methods guarded by specific snapshot fields.
|
||||
_router.register("current_monologue", _consume_monologue)
|
||||
_router.register("current_dialogue", _consume_dialogue)
|
||||
_router.register("conversation_events", _consume_conversation_events)
|
||||
_router.register("conversation_ended", _consume_conversation_ended)
|
||||
_router.register("dialogue_response", _consume_dialogue_response)
|
||||
_router.register("save_result", _consume_save_result)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
# Main game loop: poll snapshot, apply state, flush input
|
||||
@@ -89,92 +142,9 @@ func _process(delta: float) -> void:
|
||||
camera.global_position = GameState.player_position * Constants.TILE_SIZE
|
||||
_camera_anchored = true
|
||||
|
||||
# Update renderers with new state
|
||||
if world_renderer and world_renderer.has_method("update_from_state"):
|
||||
world_renderer.update_from_state()
|
||||
|
||||
# OQ-07 (#522): propagate insert state to all z-layer-6 display nodes.
|
||||
# Cursor shape still fires (D-056 option a) — only verb labels suppressed.
|
||||
var insert_state := GameState.insert_active
|
||||
if cursor_renderer and cursor_renderer.has_method("set_insert_active"):
|
||||
cursor_renderer.set_insert_active(insert_state)
|
||||
if interaction_list and interaction_list.has_method("set_insert_active"):
|
||||
interaction_list.set_insert_active(insert_state)
|
||||
if interaction_prompt and interaction_prompt.has_method("set_insert_active"):
|
||||
interaction_prompt.set_insert_active(insert_state)
|
||||
if minimap and minimap.has_method("set_insert_active"):
|
||||
minimap.set_insert_active(insert_state)
|
||||
|
||||
# D-057: Update interaction list from game state
|
||||
# Suppress during dialogue — player is in conversation, verb list is noise
|
||||
if interaction_list and interaction_list.has_method("update_from_state"):
|
||||
if dialogue_box and dialogue_box.is_dialogue_active():
|
||||
if interaction_list.is_showing():
|
||||
interaction_list.hide_list()
|
||||
else:
|
||||
interaction_list.update_from_state()
|
||||
|
||||
# D-065: Update inventory grid
|
||||
if inventory_grid and inventory_grid.has_method("update_from_state"):
|
||||
inventory_grid.update_from_state()
|
||||
|
||||
# D-053: Update stance indicator
|
||||
if stance_indicator and stance_indicator.has_method("update_from_state"):
|
||||
stance_indicator.update_from_state()
|
||||
|
||||
# D-059/D-060: Update fog entity visualization (#431)
|
||||
if fog_entities and fog_entities.has_method("update_from_state"):
|
||||
fog_entities.update_from_state()
|
||||
|
||||
# D-067: Recognition chime — fire sfx_monologue_chime on first fog recognition
|
||||
_play_recognition_chimes()
|
||||
|
||||
# #496: Update gauntlet HUD (room timer + personal bests)
|
||||
if gauntlet_hud and gauntlet_hud.has_method("update_from_state"):
|
||||
gauntlet_hud.update_from_state()
|
||||
|
||||
# #503: Update checklist overlay (auto-checklist progress tracking)
|
||||
if checklist_overlay and checklist_overlay.has_method("update_from_state"):
|
||||
checklist_overlay.update_from_state()
|
||||
|
||||
# #263: Update time display (D-013, D-031)
|
||||
if time_display and time_display.has_method("update_from_state"):
|
||||
time_display.update_from_state()
|
||||
|
||||
# #264: Update journal panel — auto-close on dialogue, refresh if open
|
||||
if journal_panel and journal_panel.has_method("update_from_state"):
|
||||
journal_panel.update_from_state()
|
||||
|
||||
# #511: Update debug overlay (F3 toggle, dev tool)
|
||||
if debug_overlay and debug_overlay.has_method("update_from_state"):
|
||||
debug_overlay.update_from_state()
|
||||
|
||||
# D-018 #125: Play close-range sound events via positional 2D audio
|
||||
_play_close_sound_events()
|
||||
|
||||
# D-073 (#529): Zone ambient crossfade — detect player tile zone, trigger set_zone on change.
|
||||
_update_zone()
|
||||
|
||||
# D-071 (#530): ListeningFocus boost — stationary 30+ ticks boosts WorldSFX.
|
||||
# Only activates when no dialogue/confrontation dip is active (D-070).
|
||||
_update_listening_focus()
|
||||
|
||||
# #174: Show examine result if server sent one this tick (#242)
|
||||
_consume_examine_result()
|
||||
|
||||
# Show monologue if server sent one this tick (#414)
|
||||
_consume_monologue()
|
||||
|
||||
# D-061: Show dialogue if server sent one this tick (#434)
|
||||
_consume_dialogue()
|
||||
|
||||
# #535: Consume overheard conversation events and responses
|
||||
_consume_conversation_events()
|
||||
_consume_conversation_ended()
|
||||
_consume_dialogue_response()
|
||||
|
||||
# #554: Show save/load result notification
|
||||
_consume_save_result()
|
||||
# #559: Dispatch snapshot to registered handlers (router pattern).
|
||||
# Always-run handlers update child nodes; keyed handlers fire for present fields.
|
||||
_router.dispatch(snapshot)
|
||||
|
||||
# Track camera to player (D-015: locked, fixed-north).
|
||||
# #117: Manual exponential smoothing — same pattern as EntityRenderer.LERP_SPEED.
|
||||
@@ -203,6 +173,15 @@ func _process(delta: float) -> void:
|
||||
if input.action == InputMapper.Action.OPEN_JOURNAL:
|
||||
_toggle_journal()
|
||||
continue
|
||||
# #257: LOAD_GAME — send first, then show loading screen (avoids stuck overlay if send fails)
|
||||
if input.action == InputMapper.Action.LOAD_GAME:
|
||||
var err := SimBridge.send_input(input)
|
||||
_pending_record_inputs.append(input)
|
||||
if err == OK and loading_screen:
|
||||
loading_screen.show_loading()
|
||||
elif err != OK:
|
||||
push_error("main.gd: LOAD_GAME send_input failed: %s" % error_string(err))
|
||||
continue
|
||||
# #528: ESC/OPEN_MENU — client-only, toggle audio settings dialog
|
||||
if input.action == InputMapper.Action.OPEN_MENU:
|
||||
if settings_dialog:
|
||||
@@ -247,6 +226,32 @@ func _process(delta: float) -> void:
|
||||
_pending_record_inputs.clear()
|
||||
|
||||
|
||||
# OQ-07 (#522): Propagate insert state to all z-layer-6 display nodes.
|
||||
# Cursor shape still fires (D-056 option a) — only verb labels suppressed.
|
||||
func _propagate_insert_state() -> void:
|
||||
var insert_state := GameState.insert_active
|
||||
if cursor_renderer:
|
||||
cursor_renderer.set_insert_active(insert_state)
|
||||
if interaction_list:
|
||||
interaction_list.set_insert_active(insert_state)
|
||||
if interaction_prompt:
|
||||
interaction_prompt.set_insert_active(insert_state)
|
||||
if minimap:
|
||||
minimap.set_insert_active(insert_state)
|
||||
|
||||
|
||||
# D-057: Update interaction list from game state.
|
||||
# Suppress during dialogue — player is in conversation, verb list is noise.
|
||||
func _update_interaction_list() -> void:
|
||||
if not interaction_list:
|
||||
return
|
||||
if dialogue_box and dialogue_box.is_dialogue_active():
|
||||
if interaction_list.is_showing():
|
||||
interaction_list.hide_list()
|
||||
else:
|
||||
interaction_list.update_from_state()
|
||||
|
||||
|
||||
# D-018 #125: Play close-range sound events — fired once per snapshot tick.
|
||||
# Each event is passed to AudioManager.play_sound_event() for 2D positional playback
|
||||
# on the WorldSFX bus. Events with no registered asset are silently skipped (D-038).
|
||||
@@ -384,12 +389,15 @@ func _consume_dialogue_response() -> void:
|
||||
GameState.dialogue_response = null
|
||||
|
||||
|
||||
# #554: Show save/load result notification from server response.
|
||||
# #554/#257: Show save/load result notification; hide loading screen on load complete.
|
||||
func _consume_save_result() -> void:
|
||||
if GameState.save_result == null:
|
||||
return
|
||||
var result: Dictionary = GameState.save_result
|
||||
GameState.save_result = null # consume once
|
||||
# #257: Dismiss loading screen regardless of success/failure
|
||||
if loading_screen:
|
||||
loading_screen.hide_loading()
|
||||
var msg: String
|
||||
if result.get("success", false):
|
||||
if result.get("kind", "") == "save":
|
||||
@@ -456,12 +464,54 @@ func _on_dialogue_dismissed() -> void:
|
||||
})
|
||||
|
||||
|
||||
# D-020 (#558): Coordinator handles dialogue state changes from dialogue_box.
|
||||
# Synchronous signal — GameState.dialogue_active updates same frame (D-064).
|
||||
func _on_dialogue_state_changed(active: bool) -> void:
|
||||
GameState.dialogue_active = active
|
||||
|
||||
|
||||
# D-020 (#558): Coordinator routes audio dip requests from dialogue_box.
|
||||
func _on_audio_dip_requested(profile: String) -> void:
|
||||
AudioManager.apply_dip(profile)
|
||||
|
||||
|
||||
# D-020 (#558): Coordinator routes audio dip clear from dialogue_box.
|
||||
func _on_audio_dip_cleared() -> void:
|
||||
AudioManager.clear_dip()
|
||||
|
||||
|
||||
# #496: Finalize gauntlet stats on disconnect
|
||||
func _on_connection_state_changed(old_state: SimBridge.ConnectionState, new_state: SimBridge.ConnectionState) -> void:
|
||||
if new_state == SimBridge.ConnectionState.DISCONNECTED and gauntlet_hud:
|
||||
gauntlet_hud.finalize()
|
||||
|
||||
|
||||
# #257: Deferred LOAD_GAME dispatch — fires once when SimBridge reaches CONNECTED.
|
||||
# pending_load_path is set by main_menu.gd before scene change.
|
||||
func _on_sim_connected_for_load(_old_state: SimBridge.ConnectionState, new_state: SimBridge.ConnectionState) -> void:
|
||||
if new_state != SimBridge.ConnectionState.CONNECTED:
|
||||
return
|
||||
if SimBridge.connection_state_changed.is_connected(_on_sim_connected_for_load):
|
||||
SimBridge.connection_state_changed.disconnect(_on_sim_connected_for_load)
|
||||
_dispatch_pending_load()
|
||||
|
||||
|
||||
func _dispatch_pending_load() -> void:
|
||||
var load_path := GameState.pending_load_path
|
||||
if load_path.is_empty():
|
||||
return
|
||||
GameState.pending_load_path = ""
|
||||
var err := SimBridge.send_input({
|
||||
"action": InputMapper.Action.LOAD_GAME,
|
||||
"timestamp_msec": Time.get_ticks_msec(),
|
||||
"action_data": {"path": load_path},
|
||||
})
|
||||
if err != OK:
|
||||
push_error("main.gd: failed to send LOAD_GAME after connection — %s" % error_string(err))
|
||||
if loading_screen:
|
||||
loading_screen.hide_loading(false)
|
||||
|
||||
|
||||
# #501: Detect large position jump indicating a teleport (not normal movement).
|
||||
const TELEPORT_DISTANCE_THRESHOLD: float = 5.0
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ class_name Protocol
|
||||
|
||||
## Protocol version — must match server PROTOCOL_VERSION in bridge/types.rs.
|
||||
## Reject snapshots where version != this value.
|
||||
const PROTOCOL_VERSION: int = 15
|
||||
const PROTOCOL_VERSION: int = 16
|
||||
|
||||
|
||||
# -- Decode: bytes from server → GDScript types --------------------------------
|
||||
@@ -244,6 +244,24 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
"error": raw_save.get("error"),
|
||||
}
|
||||
|
||||
# TODO(server): Send stationary_ticks in ObserverSnapshot (D-071, D-020).
|
||||
# Server already tracks this in ListeningFocus component (server/src/simulation/listening.rs).
|
||||
# When server populates this field, client-side accumulation fallback in game_state.gd
|
||||
# can be removed — apply_snapshot() should contain only direct field assignments.
|
||||
var stationary_ticks: Variant = null
|
||||
var raw_st: Variant = raw.get("stationary_ticks")
|
||||
if raw_st != null:
|
||||
stationary_ticks = int(raw_st)
|
||||
|
||||
# TODO(server): Send top-level zone_id string in ObserverSnapshot (D-073, D-020).
|
||||
# Server sends zone_id per VisibleTile but not as a top-level snapshot field.
|
||||
# When server populates this, client-side tile iteration fallback in game_state.gd
|
||||
# can be removed — apply_snapshot() should contain only direct field assignments.
|
||||
var zone_id: Variant = null
|
||||
var raw_zid: Variant = raw.get("zone_id")
|
||||
if raw_zid is String:
|
||||
zone_id = raw_zid
|
||||
|
||||
# v14: player_knowledge (#264, D-041) — partial KG dump for journal panel.
|
||||
# {entities: [{entity_id, name, confidence, source, state, relationship, last_observed_tick}],
|
||||
# facts: [{fact_id, confidence, source, state, acquired_tick}]}
|
||||
@@ -302,6 +320,8 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
"examine_result": examine_result,
|
||||
"player_knowledge": player_knowledge,
|
||||
"save_result": save_result,
|
||||
"stationary_ticks": stationary_ticks,
|
||||
"zone_id": zone_id,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
class_name SnapshotEventRouter
|
||||
## Routes snapshot fields to registered handlers (D-020, #559).
|
||||
##
|
||||
## Decouples main.gd from knowing which child node handles which snapshot field.
|
||||
## Handlers are registered in main.gd._ready(); dispatch() is called each snapshot tick.
|
||||
##
|
||||
## Two handler types:
|
||||
## - Keyed: called only when the snapshot contains a specific field.
|
||||
## - Always: called every dispatch (every snapshot tick), regardless of fields present.
|
||||
##
|
||||
## All handlers are zero-argument callables — they read from GameState directly.
|
||||
## This preserves GameState as the single source of truth post-apply_snapshot().
|
||||
|
||||
## Keyed handlers: field_name → Array[Callable]
|
||||
## Array per field allows multiple handlers on the same key (e.g., two consumers of same data).
|
||||
var _keyed: Dictionary = {} # String → Array[Callable]
|
||||
|
||||
## Always handlers: called every dispatch regardless of snapshot content.
|
||||
var _always: Array[Callable] = []
|
||||
|
||||
|
||||
## Register a handler for a specific snapshot field key.
|
||||
## Handler is called (with no arguments) when snapshot.has(field) is true.
|
||||
## Multiple handlers per field are supported — they run in registration order.
|
||||
func register(field: String, handler: Callable) -> void:
|
||||
if not _keyed.has(field):
|
||||
_keyed[field] = []
|
||||
_keyed[field].append(handler)
|
||||
|
||||
|
||||
## Register a handler that runs every dispatch tick (not keyed to a field).
|
||||
## Use for child nodes that update from GameState on every snapshot, e.g. update_from_state().
|
||||
func register_always(handler: Callable) -> void:
|
||||
_always.append(handler)
|
||||
|
||||
|
||||
## Dispatch a snapshot: call always handlers first, then keyed handlers for present fields.
|
||||
## Handlers read from GameState.* directly — apply_snapshot() must be called before dispatch().
|
||||
func dispatch(snapshot: Dictionary) -> void:
|
||||
for handler in _always:
|
||||
handler.call()
|
||||
for field in _keyed:
|
||||
if snapshot.has(field):
|
||||
for handler in _keyed[field]:
|
||||
handler.call()
|
||||
@@ -0,0 +1,163 @@
|
||||
class_name YamlParser
|
||||
## Shared YAML parser — common subset used by ui_strings.gd and checklist_evaluator.gd.
|
||||
##
|
||||
## Handles: nested sections (maps), arrays of dict items (- key: val), typed values.
|
||||
## Returns a hierarchical Dictionary. Use flatten() to convert to dotted-key format
|
||||
## (as UIStrings._parse_yaml() requires).
|
||||
##
|
||||
## Limitations: single-line values only; no YAML anchors/aliases; no flow syntax.
|
||||
## String values: quotes stripped. Booleans, ints, and floats are type-inferred.
|
||||
##
|
||||
## Spec ref: #560 (Sprint 20 — unify duplicate YAML parsers), D-030 (testability).
|
||||
|
||||
|
||||
## Parse YAML text into a hierarchical Dictionary.
|
||||
## Nested sections become nested dicts. Array items (- key: val) become Arrays.
|
||||
## Values are type-inferred: bool, int, float, or String.
|
||||
static func parse(text: String) -> Dictionary:
|
||||
var root: Dictionary = {}
|
||||
# Stack: [{indent: int, key: String}] — path of open section headers
|
||||
var stack: Array = []
|
||||
# Array state
|
||||
var current_array: Variant = null # Array being built, or null
|
||||
var current_item: Variant = null # Dict being built for current array item, or null
|
||||
var array_parent_indent: int = -1 # indent of the "key:" line that owns the array
|
||||
|
||||
for raw_line in text.split("\n"):
|
||||
var stripped := raw_line.strip_edges(false, true)
|
||||
if stripped.is_empty() or stripped.strip_edges().begins_with("#"):
|
||||
continue
|
||||
var indent: int = raw_line.length() - raw_line.lstrip(" ").length()
|
||||
var content: String = stripped.strip_edges()
|
||||
|
||||
# --- Array item (- key: value) ---
|
||||
if content.begins_with("- "):
|
||||
# First item: convert parent section's {} placeholder to []
|
||||
if current_array == null and stack.size() > 0:
|
||||
var parent := _node_at(root, stack, true)
|
||||
var arr_key: String = stack.back()["key"]
|
||||
var new_arr: Array = []
|
||||
parent[arr_key] = new_arr
|
||||
current_array = new_arr
|
||||
array_parent_indent = stack.back()["indent"]
|
||||
# Flush previous item and start a new one
|
||||
if current_item != null:
|
||||
current_array.append(current_item)
|
||||
current_item = {}
|
||||
var rest: String = content.substr(2).strip_edges()
|
||||
var colon: int = rest.find(":")
|
||||
if colon >= 0:
|
||||
var k: String = rest.substr(0, colon).strip_edges()
|
||||
var v: String = rest.substr(colon + 1).strip_edges()
|
||||
current_item[k] = _parse_value(v)
|
||||
continue
|
||||
|
||||
# --- Continuation line within current array item ---
|
||||
if current_array != null and indent > array_parent_indent:
|
||||
var colon: int = content.find(":")
|
||||
if colon >= 0 and current_item != null:
|
||||
var k: String = content.substr(0, colon).strip_edges()
|
||||
var v: String = content.substr(colon + 1).strip_edges()
|
||||
current_item[k] = _parse_value(v)
|
||||
continue
|
||||
|
||||
# --- End of array (indent has returned to array level or above) ---
|
||||
if current_array != null:
|
||||
if current_item != null:
|
||||
current_array.append(current_item)
|
||||
current_item = null
|
||||
current_array = null
|
||||
array_parent_indent = -1
|
||||
if stack.size() > 0:
|
||||
stack.pop_back() # pop the array-owning key
|
||||
|
||||
# --- Regular key: value or section header ---
|
||||
var colon: int = content.find(":")
|
||||
if colon < 0:
|
||||
continue
|
||||
var key: String = content.substr(0, colon).strip_edges()
|
||||
var val_str: String = content.substr(colon + 1).strip_edges()
|
||||
|
||||
# Pop sections at the same or deeper indent (we're back at a shallower level)
|
||||
while stack.size() > 0 and stack.back()["indent"] >= indent:
|
||||
stack.pop_back()
|
||||
|
||||
var node: Dictionary = _node_at(root, stack, false)
|
||||
|
||||
if val_str.is_empty() or val_str.begins_with("#"):
|
||||
# Section header — create nested dict (may become Array if - items follow)
|
||||
node[key] = {}
|
||||
stack.push_back({"indent": indent, "key": key})
|
||||
else:
|
||||
node[key] = _parse_value(val_str)
|
||||
|
||||
# Flush the last array item if the file ended inside an array
|
||||
if current_array != null and current_item != null:
|
||||
current_array.append(current_item)
|
||||
|
||||
return root
|
||||
|
||||
|
||||
## Convenience: parse text and flatten to dotted-key format in one call.
|
||||
## Used by UIStrings._parse_yaml() — equivalent to flatten(parse(text)).
|
||||
static func parse_flat(text: String) -> Dictionary:
|
||||
return flatten(parse(text))
|
||||
|
||||
|
||||
## Flatten a hierarchical dict to dotted-key format (for UIStrings compatibility).
|
||||
## {"a": {"b": "v"}} → {"a.b": "v"}
|
||||
## Arrays are skipped — dotted-key format does not represent them.
|
||||
## All values are converted to String (UIStrings stores display text, not typed data).
|
||||
static func flatten(d: Dictionary, prefix: String = "") -> Dictionary:
|
||||
var result: Dictionary = {}
|
||||
for k in d:
|
||||
var full_key: String = (prefix + "." if not prefix.is_empty() else "") + str(k)
|
||||
var v = d[k]
|
||||
if v is Dictionary:
|
||||
result.merge(flatten(v, full_key))
|
||||
elif not v is Array:
|
||||
result[full_key] = str(v)
|
||||
return result
|
||||
|
||||
|
||||
## Parse a single YAML value string into a typed GDScript value.
|
||||
## Strips inline comments, handles quoted strings, infers bool/int/float/String.
|
||||
static func _parse_value(val: String) -> Variant:
|
||||
if val.is_empty():
|
||||
return ""
|
||||
# Strip inline comment outside quotes
|
||||
if not val.begins_with("\""):
|
||||
var comment_pos: int = val.find(" #")
|
||||
if comment_pos >= 0:
|
||||
val = val.substr(0, comment_pos).strip_edges()
|
||||
# Quoted string — extract content between quotes
|
||||
if val.begins_with("\""):
|
||||
var end_quote: int = val.find("\"", 1)
|
||||
if end_quote > 0:
|
||||
return val.substr(1, end_quote - 1)
|
||||
return val.substr(1)
|
||||
# Boolean
|
||||
if val == "true": return true
|
||||
if val == "false": return false
|
||||
# Float (must have decimal point)
|
||||
if val.contains(".") and val.is_valid_float():
|
||||
return val.to_float()
|
||||
# Integer
|
||||
if val.is_valid_int():
|
||||
return val.to_int()
|
||||
# Plain string
|
||||
return val
|
||||
|
||||
|
||||
## Navigate root following the stack key path.
|
||||
## parent=true: navigate one level less (returns the parent node, not the leaf).
|
||||
static func _node_at(root: Dictionary, stack: Array, parent: bool) -> Dictionary:
|
||||
var node: Dictionary = root
|
||||
var depth: int = stack.size() - (1 if parent else 0)
|
||||
for i in range(depth):
|
||||
var k: String = stack[i]["key"]
|
||||
if node.has(k) and node[k] is Dictionary:
|
||||
node = node[k]
|
||||
else:
|
||||
break
|
||||
return node
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -10,7 +10,7 @@ extends GdUnitTestSuite
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
const DEBUG_SCENE_PATH: String = "res://scenes/main.tscn"
|
||||
const DEBUG_SCRIPT_PATH: String = "res://scripts/ui/debug_overlay.gd"
|
||||
const DEBUG_SCRIPT_PATH: String = "res://ui/debug_overlay.gd"
|
||||
|
||||
|
||||
func _make_overlay() -> Control:
|
||||
@@ -51,7 +51,7 @@ func after_test() -> void:
|
||||
|
||||
func test_debug_overlay_script_exists() -> void:
|
||||
assert_bool(ResourceLoader.exists(DEBUG_SCRIPT_PATH)).override_failure_message(
|
||||
"debug_overlay.gd must exist at res://scripts/ui/debug_overlay.gd (#348)"
|
||||
"debug_overlay.gd must exist at res://ui/debug_overlay.gd (#348)"
|
||||
).is_true()
|
||||
|
||||
|
||||
|
||||
@@ -596,3 +596,77 @@ func test_is_dialogue_active_true_after_show_dialogue() -> void:
|
||||
box.show_dialogue("NPC", "Speech.", _make_options(["Reply"]))
|
||||
assert_bool(box.is_dialogue_active()).is_true()
|
||||
box.queue_free()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# D-020 (#558): Signal decoupling — dialogue_box emits signals instead of
|
||||
# directly mutating GameState or calling AudioManager.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func test_dialogue_state_changed_emits_true_on_show() -> void:
|
||||
## D-020: show_dialogue() must emit dialogue_state_changed(true).
|
||||
var box := _make_dialogue_box()
|
||||
if box == null: return
|
||||
var received: Array = []
|
||||
box.dialogue_state_changed.connect(func(active): received.append(active))
|
||||
box.show_dialogue("NPC", "Speech.", _make_options(["Reply"]))
|
||||
assert_bool(received.has(true)).override_failure_message(
|
||||
"dialogue_state_changed(true) must be emitted on show_dialogue"
|
||||
).is_true()
|
||||
box.queue_free()
|
||||
|
||||
|
||||
func test_dialogue_state_changed_emits_false_on_hide() -> void:
|
||||
## D-020: hide_dialogue() must emit dialogue_state_changed(false).
|
||||
var box := _make_dialogue_box()
|
||||
if box == null: return
|
||||
var received: Array = []
|
||||
box.dialogue_state_changed.connect(func(active): received.append(active))
|
||||
box.show_dialogue("NPC", "Speech.", _make_options(["Reply"]))
|
||||
box.hide_dialogue()
|
||||
assert_bool(received.has(false)).override_failure_message(
|
||||
"dialogue_state_changed(false) must be emitted on hide_dialogue"
|
||||
).is_true()
|
||||
box.queue_free()
|
||||
|
||||
|
||||
func test_audio_dip_requested_emits_dialogue_on_show() -> void:
|
||||
## D-020: show_dialogue() must emit audio_dip_requested("dialogue").
|
||||
var box := _make_dialogue_box()
|
||||
if box == null: return
|
||||
var received: Array = []
|
||||
box.audio_dip_requested.connect(func(profile): received.append(profile))
|
||||
box.show_dialogue("NPC", "Speech.", _make_options(["Reply"]))
|
||||
assert_bool(received.has("dialogue")).override_failure_message(
|
||||
"audio_dip_requested('dialogue') must be emitted on show_dialogue"
|
||||
).is_true()
|
||||
box.queue_free()
|
||||
|
||||
|
||||
func test_audio_dip_cleared_emits_on_hide() -> void:
|
||||
## D-020: hide_dialogue() must emit audio_dip_cleared.
|
||||
var box := _make_dialogue_box()
|
||||
if box == null: return
|
||||
var cleared := [false]
|
||||
box.audio_dip_cleared.connect(func(): cleared[0] = true)
|
||||
box.show_dialogue("NPC", "Speech.", _make_options(["Reply"]))
|
||||
box.hide_dialogue()
|
||||
assert_bool(cleared[0]).override_failure_message(
|
||||
"audio_dip_cleared must be emitted on hide_dialogue"
|
||||
).is_true()
|
||||
box.queue_free()
|
||||
|
||||
|
||||
func test_no_direct_game_state_mutation() -> void:
|
||||
## D-020: dialogue_box must not directly mutate GameState.dialogue_active.
|
||||
## After show_dialogue, GameState.dialogue_active should remain unchanged
|
||||
## (only the coordinator updates it via signal handler).
|
||||
var box := _make_dialogue_box()
|
||||
if box == null: return
|
||||
GameState.dialogue_active = false
|
||||
box.show_dialogue("NPC", "Speech.", _make_options(["Reply"]))
|
||||
assert_bool(GameState.dialogue_active).override_failure_message(
|
||||
"GameState.dialogue_active must NOT be mutated directly by dialogue_box"
|
||||
).is_false()
|
||||
box.queue_free()
|
||||
GameState.dialogue_active = false
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
## Sprint 20 #558: dialogue_box.gd decoupling tests.
|
||||
##
|
||||
## Verifies D-020 compliance: dialogue_box.gd emits signals instead of mutating
|
||||
## GameState or calling AudioManager directly. main.gd wires the signal handlers.
|
||||
##
|
||||
## D-030: fixture-based, server-free, no subprocess required.
|
||||
class_name TestDialogueSprint20
|
||||
extends GdUnitTestSuite
|
||||
|
||||
|
||||
func _make_dialogue_box() -> Control:
|
||||
if not ResourceLoader.exists("res://ui/dialogue_box.tscn"):
|
||||
push_warning("TestDialogueSprint20: dialogue_box.tscn not found — scene tests skipped")
|
||||
return null
|
||||
var node: Control = load("res://ui/dialogue_box.tscn").instantiate()
|
||||
add_child(node)
|
||||
return node
|
||||
|
||||
|
||||
func before_test() -> void:
|
||||
GameState.dialogue_active = false
|
||||
|
||||
|
||||
func after_test() -> void:
|
||||
GameState.dialogue_active = false
|
||||
|
||||
|
||||
# -- dialogue_state_changed signal -------------------------------------------
|
||||
|
||||
func test_show_dialogue_emits_dialogue_state_changed_true() -> void:
|
||||
## show_dialogue() must emit dialogue_state_changed(true) not mutate GameState directly.
|
||||
var box := _make_dialogue_box()
|
||||
if box == null: return
|
||||
auto_free(box)
|
||||
var received: Variant = null
|
||||
box.dialogue_state_changed.connect(func(active: bool): received = active)
|
||||
box.show_dialogue("NPC", "Hello.", [])
|
||||
assert_that(received).override_failure_message(
|
||||
"show_dialogue() must emit dialogue_state_changed(true) (#558)"
|
||||
).is_equal(true)
|
||||
|
||||
|
||||
func test_show_dialogue_does_not_mutate_game_state_directly() -> void:
|
||||
## Without a connected handler, GameState.dialogue_active must stay false.
|
||||
## Proves dialogue_box.gd has zero direct GameState mutation (D-020 #558).
|
||||
var box := _make_dialogue_box()
|
||||
if box == null: return
|
||||
auto_free(box)
|
||||
GameState.dialogue_active = false
|
||||
box.show_dialogue("NPC", "Hello.", [])
|
||||
assert_bool(GameState.dialogue_active).override_failure_message(
|
||||
"dialogue_box must not mutate GameState.dialogue_active directly (D-020 #558)"
|
||||
).is_false()
|
||||
|
||||
|
||||
func test_hide_dialogue_emits_dialogue_state_changed_false() -> void:
|
||||
## hide_dialogue() must emit dialogue_state_changed(false).
|
||||
var box := _make_dialogue_box()
|
||||
if box == null: return
|
||||
auto_free(box)
|
||||
box.show_dialogue("NPC", "Hello.", [])
|
||||
var received: Variant = null
|
||||
box.dialogue_state_changed.connect(func(active: bool): received = active)
|
||||
box.hide_dialogue()
|
||||
assert_that(received).override_failure_message(
|
||||
"hide_dialogue() must emit dialogue_state_changed(false) (#558)"
|
||||
).is_equal(false)
|
||||
|
||||
|
||||
# -- audio_dip_requested / audio_dip_cleared signals -------------------------
|
||||
|
||||
func test_show_dialogue_emits_audio_dip_requested_dialogue() -> void:
|
||||
## show_dialogue() must emit audio_dip_requested("dialogue") not call AudioManager.
|
||||
var box := _make_dialogue_box()
|
||||
if box == null: return
|
||||
auto_free(box)
|
||||
var received_profile: Variant = null
|
||||
box.audio_dip_requested.connect(func(profile: String): received_profile = profile)
|
||||
box.show_dialogue("NPC", "Hello.", [])
|
||||
assert_that(received_profile).override_failure_message(
|
||||
"show_dialogue() must emit audio_dip_requested('dialogue') (#558)"
|
||||
).is_equal("dialogue")
|
||||
|
||||
|
||||
func test_show_dialogue_does_not_call_audio_manager_directly() -> void:
|
||||
## Without a connected handler, AudioManager state must be unchanged by show_dialogue().
|
||||
## Verifies no direct AudioManager call in dialogue_box.gd (D-020 #558).
|
||||
var box := _make_dialogue_box()
|
||||
if box == null: return
|
||||
auto_free(box)
|
||||
var dip_before := AudioManager.get_active_dip()
|
||||
box.show_dialogue("NPC", "Hello.", [])
|
||||
var dip_after := AudioManager.get_active_dip()
|
||||
assert_str(dip_after).override_failure_message(
|
||||
"dialogue_box must not call AudioManager.apply_dip() directly (D-020 #558)"
|
||||
).is_equal(dip_before)
|
||||
|
||||
|
||||
func test_hide_dialogue_emits_audio_dip_cleared() -> void:
|
||||
## Ending a conversation must emit audio_dip_cleared not call AudioManager directly.
|
||||
var box := _make_dialogue_box()
|
||||
if box == null: return
|
||||
auto_free(box)
|
||||
box.show_dialogue("NPC", "Hello.", [])
|
||||
var cleared := false
|
||||
box.audio_dip_cleared.connect(func(): cleared = true)
|
||||
box.hide_dialogue()
|
||||
assert_bool(cleared).override_failure_message(
|
||||
"hide_dialogue() must emit audio_dip_cleared (#558)"
|
||||
).is_true()
|
||||
|
||||
|
||||
func test_audio_dip_cleared_count_on_conversation_end() -> void:
|
||||
## Verify audio_dip_cleared fires when conversation ends.
|
||||
var box := _make_dialogue_box()
|
||||
if box == null: return
|
||||
auto_free(box)
|
||||
var cleared_count := 0
|
||||
box.audio_dip_cleared.connect(func(): cleared_count += 1)
|
||||
box.show_dialogue("NPC", "Speak.", [])
|
||||
box.hide_dialogue()
|
||||
assert_int(cleared_count).override_failure_message(
|
||||
"audio_dip_cleared must fire at least once when conversation ends (#558)"
|
||||
).is_greater_equal(1)
|
||||
|
||||
|
||||
# -- coordinator wiring verification -----------------------------------------
|
||||
|
||||
func test_signal_handler_wires_game_state() -> void:
|
||||
## Simulate main.gd: connect dialogue_state_changed to update GameState.dialogue_active.
|
||||
## Verifies the coordinator pattern works end-to-end (D-020 #558).
|
||||
var box := _make_dialogue_box()
|
||||
if box == null: return
|
||||
auto_free(box)
|
||||
box.dialogue_state_changed.connect(func(active: bool): GameState.dialogue_active = active)
|
||||
box.show_dialogue("NPC", "Hello.", [])
|
||||
assert_bool(GameState.dialogue_active).override_failure_message(
|
||||
"With handler wired, GameState.dialogue_active must be true after show_dialogue (#558)"
|
||||
).is_true()
|
||||
box.hide_dialogue()
|
||||
assert_bool(GameState.dialogue_active).override_failure_message(
|
||||
"With handler wired, GameState.dialogue_active must be false after hide_dialogue (#558)"
|
||||
).is_false()
|
||||
@@ -19,6 +19,8 @@ func before_each() -> void:
|
||||
GameState.player_stance = "Walk"
|
||||
GameState.player_inventory = []
|
||||
GameState.stationary_ticks = 0
|
||||
GameState._prev_player_position = Vector2(-1e9, -1e9)
|
||||
GameState.current_zone_id = ""
|
||||
GameState.insert_active = true
|
||||
|
||||
|
||||
@@ -115,19 +117,49 @@ func test_apply_snapshot_inventory_absent_clears_list() -> void:
|
||||
assert_that(GameState.player_inventory.size()).is_equal(0)
|
||||
|
||||
|
||||
# -- Stationary tick counter (D-071) -------------------------------------
|
||||
# -- Stationary ticks: server-authoritative (D-020/D-071) -----------------
|
||||
|
||||
func test_stationary_ticks_increments_when_player_position_unchanged() -> void:
|
||||
func test_stationary_ticks_from_server_snapshot() -> void:
|
||||
## D-020: When server sends stationary_ticks, client reads it directly.
|
||||
GameState.apply_snapshot({"tick": 1, "entities": [], "stationary_ticks": 42})
|
||||
assert_int(GameState.stationary_ticks).is_equal(42)
|
||||
|
||||
|
||||
func test_stationary_ticks_server_value_overrides_client_accumulation() -> void:
|
||||
## D-020: Server value takes priority — client must not accumulate on top of it.
|
||||
var snapshot := {
|
||||
"tick": 1,
|
||||
"entities": [{"entity_id": 1, "x": 10.0, "y": 10.0, "z": 0, "kind": {"variant": "Player", "data": null}}],
|
||||
"stationary_ticks": 5,
|
||||
}
|
||||
GameState.apply_snapshot(snapshot)
|
||||
GameState.apply_snapshot(snapshot) # same position, but server sends 5 again
|
||||
assert_int(GameState.stationary_ticks).is_equal(5) # server value, not 6
|
||||
|
||||
|
||||
func test_stationary_ticks_missing_field_degrades_gracefully() -> void:
|
||||
## D-020 fallback: when server omits stationary_ticks, no crash, defaults to 0.
|
||||
GameState.stationary_ticks = 0
|
||||
GameState.apply_snapshot({"tick": 1, "entities": []})
|
||||
# No crash; field retains a valid integer value.
|
||||
assert_int(GameState.stationary_ticks).is_greater_equal(0)
|
||||
|
||||
|
||||
# -- Stationary ticks: deprecated client-side fallback (D-071) -----------
|
||||
|
||||
func test_stationary_ticks_fallback_increments_when_position_unchanged() -> void:
|
||||
## Deprecated fallback: client accumulates when server omits the field.
|
||||
var snapshot := {
|
||||
"tick": 1,
|
||||
"entities": [{"entity_id": 1, "x": 10.0, "y": 10.0, "z": 0, "kind": {"variant": "Player", "data": null}}],
|
||||
}
|
||||
GameState.apply_snapshot(snapshot) # first call: position changes from ZERO
|
||||
GameState.apply_snapshot(snapshot) # first call: position changes from sentinel
|
||||
GameState.apply_snapshot(snapshot) # second call: position unchanged → +1
|
||||
assert_int(GameState.stationary_ticks).is_greater(0)
|
||||
|
||||
|
||||
func test_stationary_ticks_resets_on_player_movement() -> void:
|
||||
func test_stationary_ticks_fallback_resets_on_movement() -> void:
|
||||
## Deprecated fallback: client resets on movement when server omits the field.
|
||||
var s1 := {
|
||||
"tick": 1,
|
||||
"entities": [{"entity_id": 1, "x": 10.0, "y": 10.0, "z": 0, "kind": {"variant": "Player", "data": null}}],
|
||||
@@ -143,6 +175,33 @@ func test_stationary_ticks_resets_on_player_movement() -> void:
|
||||
assert_int(GameState.stationary_ticks).is_equal(0)
|
||||
|
||||
|
||||
# -- Zone ID: server-authoritative (D-020/D-073) -------------------------
|
||||
|
||||
func test_zone_id_from_server_snapshot() -> void:
|
||||
## D-020: When server sends top-level zone_id, client reads it directly.
|
||||
GameState.apply_snapshot({"tick": 1, "entities": [], "zone_id": "zone_alpha"})
|
||||
assert_that(GameState.current_zone_id).is_equal("zone_alpha")
|
||||
|
||||
|
||||
func test_zone_id_server_value_overrides_tile_derivation() -> void:
|
||||
## D-020: Server top-level zone_id takes priority over tile-derived zone_id.
|
||||
var snapshot := {
|
||||
"tick": 1,
|
||||
"entities": [{"entity_id": 1, "x": 5.0, "y": 5.0, "z": 0, "kind": {"variant": "Player", "data": null}}],
|
||||
"tiles": [{"x": 5, "y": 5, "z": 0, "type": "floor", "zone_id": "zone_from_tile"}],
|
||||
"zone_id": "zone_from_server",
|
||||
}
|
||||
GameState.apply_snapshot(snapshot)
|
||||
assert_that(GameState.current_zone_id).is_equal("zone_from_server")
|
||||
|
||||
|
||||
func test_zone_id_missing_field_degrades_gracefully() -> void:
|
||||
## D-020 fallback: when server omits zone_id and no tiles match, defaults to "".
|
||||
GameState.current_zone_id = ""
|
||||
GameState.apply_snapshot({"tick": 1, "entities": []})
|
||||
assert_that(GameState.current_zone_id).is_equal("")
|
||||
|
||||
|
||||
# -- insert_active (OQ-07, #522) -----------------------------------------
|
||||
|
||||
func test_apply_snapshot_insert_active_false() -> void:
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
## Sprint 20 #557: GameState.apply_snapshot() refactor tests.
|
||||
##
|
||||
## Verifies D-020 compliance: apply_snapshot() reads server-authoritative values
|
||||
## for stationary_ticks and zone_id directly from the snapshot when present,
|
||||
## and degrades gracefully when the server has not yet added these fields.
|
||||
##
|
||||
## Does NOT replace test_game_state.gd or test_snapshot_zone_id.gd — those cover
|
||||
## existing behaviour. This file covers the new code paths added in Sprint 20.
|
||||
##
|
||||
## D-030: fixture-based, server-free, no subprocess required.
|
||||
class_name TestGameStateSprint20
|
||||
extends GdUnitTestSuite
|
||||
|
||||
|
||||
func before_each() -> void:
|
||||
GameState.stationary_ticks = 0
|
||||
GameState._prev_player_position = Vector2(-1e9, -1e9)
|
||||
GameState.current_zone_id = ""
|
||||
GameState.player_position = Vector2.ZERO
|
||||
GameState.visible_tiles = []
|
||||
GameState.visible_positions = {}
|
||||
GameState.visibility_sectors = {}
|
||||
|
||||
|
||||
# -- stationary_ticks: server-authoritative path (D-020) ----------------------
|
||||
|
||||
func test_stationary_ticks_reads_server_value_when_present() -> void:
|
||||
# When snapshot includes stationary_ticks, apply_snapshot() must use the
|
||||
# server value directly without client-side accumulation (D-020).
|
||||
var snapshot := {
|
||||
"tick": 5,
|
||||
"entities": [
|
||||
{"entity_id": 1, "x": 10.0, "y": 10.0, "z": 0, "kind": {"variant": "Player", "data": null}},
|
||||
],
|
||||
"stationary_ticks": 42,
|
||||
}
|
||||
GameState.apply_snapshot(snapshot)
|
||||
assert_int(GameState.stationary_ticks).override_failure_message(
|
||||
"apply_snapshot() must read stationary_ticks=42 from snapshot (D-020)"
|
||||
).is_equal(42)
|
||||
|
||||
|
||||
func test_stationary_ticks_server_value_does_not_accumulate() -> void:
|
||||
# Server-sent value must be assigned directly — NOT added to existing value.
|
||||
# Two calls with stationary_ticks=10 must yield 10, not 20.
|
||||
var snapshot := {
|
||||
"tick": 1,
|
||||
"entities": [
|
||||
{"entity_id": 1, "x": 5.0, "y": 5.0, "z": 0, "kind": {"variant": "Player", "data": null}},
|
||||
],
|
||||
"stationary_ticks": 10,
|
||||
}
|
||||
GameState.apply_snapshot(snapshot)
|
||||
GameState.apply_snapshot(snapshot)
|
||||
assert_int(GameState.stationary_ticks).override_failure_message(
|
||||
"Server value must be assigned directly, not accumulated (D-020)"
|
||||
).is_equal(10)
|
||||
|
||||
|
||||
func test_stationary_ticks_server_can_reset_to_zero() -> void:
|
||||
# Server sends 0 when player moves — client must accept this reset.
|
||||
GameState.stationary_ticks = 50
|
||||
var snapshot := {
|
||||
"tick": 2,
|
||||
"entities": [
|
||||
{"entity_id": 1, "x": 5.0, "y": 5.0, "z": 0, "kind": {"variant": "Player", "data": null}},
|
||||
],
|
||||
"stationary_ticks": 0,
|
||||
}
|
||||
GameState.apply_snapshot(snapshot)
|
||||
assert_int(GameState.stationary_ticks).override_failure_message(
|
||||
"Server reset to 0 must override client-held value"
|
||||
).is_equal(0)
|
||||
|
||||
|
||||
# -- stationary_ticks: DEPRECATED fallback (graceful degradation) --------------
|
||||
|
||||
func test_stationary_ticks_fallback_when_field_absent() -> void:
|
||||
# When snapshot has no stationary_ticks, client-side accumulation must still
|
||||
# run (backward compat until server ships field). No crash.
|
||||
var snapshot := {
|
||||
"tick": 1,
|
||||
"entities": [
|
||||
{"entity_id": 1, "x": 10.0, "y": 10.0, "z": 0, "kind": {"variant": "Player", "data": null}},
|
||||
],
|
||||
}
|
||||
GameState.apply_snapshot(snapshot) # position changes from ZERO → resets to 0
|
||||
GameState.apply_snapshot(snapshot) # position unchanged → increments
|
||||
assert_int(GameState.stationary_ticks).override_failure_message(
|
||||
"Client-side fallback must increment stationary_ticks when field absent"
|
||||
).is_greater(0)
|
||||
|
||||
|
||||
func test_apply_snapshot_missing_stationary_ticks_no_crash() -> void:
|
||||
# Snapshots lacking stationary_ticks must not crash apply_snapshot().
|
||||
var snapshot := {"tick": 1, "entities": []}
|
||||
# No assertion needed beyond confirming no exception is raised.
|
||||
GameState.apply_snapshot(snapshot)
|
||||
assert_bool(true).is_true()
|
||||
|
||||
|
||||
# -- zone_id: server-authoritative path (D-020/D-073) -------------------------
|
||||
|
||||
func test_current_zone_id_reads_server_value_when_present() -> void:
|
||||
# When snapshot includes top-level zone_id, apply_snapshot() must use it
|
||||
# directly without tile lookup (D-020).
|
||||
var snapshot := {
|
||||
"tick": 1,
|
||||
"entities": [
|
||||
{"entity_id": 1, "x": 5.0, "y": 5.0, "z": 0, "kind": {"variant": "Player", "data": null}},
|
||||
],
|
||||
"zone_id": "zone_server_direct",
|
||||
}
|
||||
GameState.apply_snapshot(snapshot)
|
||||
assert_str(GameState.current_zone_id).override_failure_message(
|
||||
"apply_snapshot() must read zone_id='zone_server_direct' from snapshot (D-020)"
|
||||
).is_equal("zone_server_direct")
|
||||
|
||||
|
||||
func test_current_zone_id_server_value_overrides_tile_data() -> void:
|
||||
# When snapshot has both zone_id and visible_tiles with a different zone,
|
||||
# the top-level zone_id field takes priority.
|
||||
var snapshot := {
|
||||
"tick": 1,
|
||||
"entities": [
|
||||
{"entity_id": 1, "x": 5.0, "y": 5.0, "z": 0, "kind": {"variant": "Player", "data": null}},
|
||||
],
|
||||
"zone_id": "zone_from_server",
|
||||
"visible_tiles": [
|
||||
{"x": 5, "y": 5, "z": 0, "type": "floor", "zone_id": "zone_from_tile"},
|
||||
],
|
||||
}
|
||||
GameState.apply_snapshot(snapshot)
|
||||
assert_str(GameState.current_zone_id).override_failure_message(
|
||||
"Top-level zone_id must override tile-derived zone when both present"
|
||||
).is_equal("zone_from_server")
|
||||
|
||||
|
||||
# -- zone_id: DEPRECATED fallback (graceful degradation) ----------------------
|
||||
|
||||
func test_current_zone_id_fallback_to_tile_lookup_when_absent() -> void:
|
||||
# When snapshot lacks top-level zone_id, tile lookup fallback must run.
|
||||
# Existing test_snapshot_zone_id.gd covers detailed scenarios; this is a
|
||||
# smoke test confirming the fallback path still works after Sprint 20 refactor.
|
||||
var snapshot := {
|
||||
"tick": 1,
|
||||
"entities": [
|
||||
{"entity_id": 1, "x": 3.0, "y": 3.0, "z": 0, "kind": {"variant": "Player", "data": null}},
|
||||
],
|
||||
"visible_tiles": [
|
||||
{"x": 3, "y": 3, "z": 0, "type": "floor", "zone_id": "zone_tile_fallback"},
|
||||
],
|
||||
}
|
||||
GameState.apply_snapshot(snapshot)
|
||||
assert_str(GameState.current_zone_id).override_failure_message(
|
||||
"Tile lookup fallback must populate zone_id when top-level field absent"
|
||||
).is_equal("zone_tile_fallback")
|
||||
|
||||
|
||||
func test_apply_snapshot_missing_zone_id_no_crash() -> void:
|
||||
# Snapshots lacking both zone_id and visible_tiles must not crash.
|
||||
var snapshot := {"tick": 1, "entities": []}
|
||||
GameState.apply_snapshot(snapshot)
|
||||
assert_str(GameState.current_zone_id).is_equal("")
|
||||
@@ -0,0 +1,110 @@
|
||||
## Sprint 21 — Save/load game flow (#257)
|
||||
## Tests: LoadingScreen overlay, pending_load_path field, deferred dispatch guards.
|
||||
class_name TestSaveLoadFlowSprint21
|
||||
extends GdUnitTestSuite
|
||||
|
||||
|
||||
const LOADING_SCREEN_SCENE_PATH: String = "res://ui/loading_screen.tscn"
|
||||
|
||||
|
||||
func _make_loading_screen() -> Control:
|
||||
if not ResourceLoader.exists(LOADING_SCREEN_SCENE_PATH):
|
||||
push_warning("TestSaveLoadFlowSprint21: loading_screen.tscn not found — skip")
|
||||
return null
|
||||
var node: Control = load(LOADING_SCREEN_SCENE_PATH).instantiate()
|
||||
add_child(node)
|
||||
return node
|
||||
|
||||
|
||||
func before_test() -> void:
|
||||
GameState.pending_load_path = ""
|
||||
|
||||
|
||||
func after_test() -> void:
|
||||
GameState.pending_load_path = ""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# LoadingScreen scene
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func test_loading_screen_scene_exists() -> void:
|
||||
assert_bool(ResourceLoader.exists(LOADING_SCREEN_SCENE_PATH)).override_failure_message(
|
||||
"LoadingScreen scene must exist at res://ui/loading_screen.tscn (#257)"
|
||||
).is_true()
|
||||
|
||||
|
||||
func test_loading_screen_hidden_on_ready() -> void:
|
||||
var ls := _make_loading_screen()
|
||||
if ls == null: return
|
||||
assert_bool(ls.visible).override_failure_message(
|
||||
"LoadingScreen must be hidden on _ready (#257)"
|
||||
).is_false()
|
||||
ls.queue_free()
|
||||
|
||||
|
||||
func test_show_loading_makes_visible() -> void:
|
||||
var ls := _make_loading_screen()
|
||||
if ls == null: return
|
||||
ls.show_loading()
|
||||
assert_bool(ls.visible).override_failure_message(
|
||||
"show_loading() must make LoadingScreen visible"
|
||||
).is_true()
|
||||
ls.queue_free()
|
||||
|
||||
|
||||
func test_hide_loading_makes_invisible() -> void:
|
||||
var ls := _make_loading_screen()
|
||||
if ls == null: return
|
||||
ls.show_loading()
|
||||
ls.hide_loading()
|
||||
assert_bool(ls.visible).override_failure_message(
|
||||
"hide_loading() must hide LoadingScreen"
|
||||
).is_false()
|
||||
ls.queue_free()
|
||||
|
||||
|
||||
func test_hide_loading_accepts_success_param() -> void:
|
||||
var ls := _make_loading_screen()
|
||||
if ls == null: return
|
||||
ls.show_loading()
|
||||
ls.hide_loading(true)
|
||||
assert_bool(ls.visible).is_false()
|
||||
ls.show_loading()
|
||||
ls.hide_loading(false)
|
||||
assert_bool(ls.visible).is_false()
|
||||
ls.queue_free()
|
||||
|
||||
|
||||
func test_hide_loading_default_param_is_true() -> void:
|
||||
var ls := _make_loading_screen()
|
||||
if ls == null: return
|
||||
ls.show_loading()
|
||||
ls.hide_loading() # no argument — default success=true
|
||||
assert_bool(ls.visible).is_false()
|
||||
ls.queue_free()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GameState.pending_load_path field
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func test_pending_load_path_default_is_empty() -> void:
|
||||
GameState.pending_load_path = ""
|
||||
assert_str(GameState.pending_load_path).override_failure_message(
|
||||
"GameState.pending_load_path default must be empty string"
|
||||
).is_empty()
|
||||
|
||||
|
||||
func test_pending_load_path_can_be_set_and_read() -> void:
|
||||
var path := "user://saves/20260225-143022-a7b3f1/quicksave.sav"
|
||||
GameState.pending_load_path = path
|
||||
assert_str(GameState.pending_load_path).override_failure_message(
|
||||
"GameState.pending_load_path must persist the value set"
|
||||
).is_equal(path)
|
||||
|
||||
|
||||
func test_pending_load_path_can_be_cleared() -> void:
|
||||
GameState.pending_load_path = "user://saves/test/quicksave.sav"
|
||||
GameState.pending_load_path = ""
|
||||
assert_str(GameState.pending_load_path).is_empty()
|
||||
@@ -0,0 +1,89 @@
|
||||
## SnapshotEventRouter unit tests (#559).
|
||||
## Verifies callable-based dispatch: keyed handlers receive correct field values,
|
||||
## always handlers run on every dispatch, absent fields don't trigger handlers.
|
||||
##
|
||||
## D-030: fixture-based, server-free, no subprocess required.
|
||||
class_name TestSnapshotEventRouter
|
||||
extends GdUnitTestSuite
|
||||
|
||||
|
||||
# -- Keyed handlers -----------------------------------------------------------
|
||||
|
||||
func test_keyed_handler_called_when_field_present() -> void:
|
||||
var router := SnapshotEventRouter.new()
|
||||
var received: Array = []
|
||||
router.register("current_monologue", func(): received.append("monologue"))
|
||||
router.dispatch({"current_monologue": {"text": "Test."}})
|
||||
assert_that(received.size()).is_equal(1)
|
||||
assert_that(received[0]).is_equal("monologue")
|
||||
|
||||
|
||||
func test_keyed_handler_not_called_when_field_absent() -> void:
|
||||
var router := SnapshotEventRouter.new()
|
||||
var received: Array = []
|
||||
router.register("current_monologue", func(): received.append("monologue"))
|
||||
router.dispatch({"tick": 1})
|
||||
assert_that(received.size()).is_equal(0)
|
||||
|
||||
|
||||
func test_multiple_keyed_handlers_same_field() -> void:
|
||||
## Multiple handlers on the same key run in registration order.
|
||||
var router := SnapshotEventRouter.new()
|
||||
var order: Array = []
|
||||
router.register("save_result", func(): order.append("first"))
|
||||
router.register("save_result", func(): order.append("second"))
|
||||
router.dispatch({"save_result": {"success": true}})
|
||||
assert_that(order).is_equal(["first", "second"])
|
||||
|
||||
|
||||
func test_keyed_handlers_multiple_fields() -> void:
|
||||
## Each keyed handler fires only for its registered field.
|
||||
var router := SnapshotEventRouter.new()
|
||||
var received: Array = []
|
||||
router.register("current_monologue", func(): received.append("mono"))
|
||||
router.register("current_dialogue", func(): received.append("dlg"))
|
||||
router.dispatch({"current_monologue": {"text": "Hi."}})
|
||||
assert_that(received).is_equal(["mono"])
|
||||
received.clear()
|
||||
router.dispatch({"current_dialogue": {"speech": "Hello."}, "current_monologue": {"text": "Hmm."}})
|
||||
assert_bool(received.has("mono")).is_true()
|
||||
assert_bool(received.has("dlg")).is_true()
|
||||
|
||||
|
||||
# -- Always handlers ----------------------------------------------------------
|
||||
|
||||
func test_always_handler_called_on_every_dispatch() -> void:
|
||||
var router := SnapshotEventRouter.new()
|
||||
var count: Array[int] = [0]
|
||||
router.register_always(func(): count[0] += 1)
|
||||
router.dispatch({"tick": 1})
|
||||
router.dispatch({"tick": 2})
|
||||
router.dispatch({})
|
||||
assert_int(count[0]).is_equal(3)
|
||||
|
||||
|
||||
func test_always_handler_runs_before_keyed() -> void:
|
||||
## Always handlers run before keyed handlers (dispatch order guarantee).
|
||||
var router := SnapshotEventRouter.new()
|
||||
var order: Array = []
|
||||
router.register("current_monologue", func(): order.append("keyed"))
|
||||
router.register_always(func(): order.append("always"))
|
||||
router.dispatch({"current_monologue": {"text": "Hi."}})
|
||||
assert_that(order[0]).is_equal("always")
|
||||
assert_that(order[1]).is_equal("keyed")
|
||||
|
||||
|
||||
# -- Empty dispatch ------------------------------------------------------------
|
||||
|
||||
func test_empty_snapshot_no_crash() -> void:
|
||||
var router := SnapshotEventRouter.new()
|
||||
router.register("foo", func(): pass)
|
||||
router.register_always(func(): pass)
|
||||
# Must not crash
|
||||
router.dispatch({})
|
||||
|
||||
|
||||
func test_no_handlers_no_crash() -> void:
|
||||
var router := SnapshotEventRouter.new()
|
||||
# Must not crash
|
||||
router.dispatch({"tick": 1, "entities": []})
|
||||
@@ -0,0 +1,261 @@
|
||||
## YamlParser unit tests (#560).
|
||||
## Verifies parse() (nested, typed) and parse_flat() (dotted keys, string values).
|
||||
## Covers: maps, nested maps, arrays of dicts, type conversion, comments,
|
||||
## quoted strings, empty input, edge cases.
|
||||
##
|
||||
## D-030: fixture-based, server-free, no subprocess required.
|
||||
class_name TestYamlParser
|
||||
extends GdUnitTestSuite
|
||||
|
||||
|
||||
# -- parse(): basic key-value pairs -------------------------------------------
|
||||
|
||||
func test_parse_simple_key_value() -> void:
|
||||
var result := YamlParser.parse("key: value")
|
||||
assert_that(result["key"]).is_equal("value")
|
||||
|
||||
|
||||
func test_parse_quoted_string() -> void:
|
||||
var result := YamlParser.parse('key: "hello world"')
|
||||
assert_that(result["key"]).is_equal("hello world")
|
||||
|
||||
|
||||
func test_parse_empty_quoted_string() -> void:
|
||||
var result := YamlParser.parse('key: ""')
|
||||
assert_that(result["key"]).is_equal("")
|
||||
|
||||
|
||||
func test_parse_integer_value() -> void:
|
||||
var result := YamlParser.parse("count: 42")
|
||||
assert_that(result["count"]).is_equal(42)
|
||||
assert_that(typeof(result["count"])).is_equal(TYPE_INT)
|
||||
|
||||
|
||||
func test_parse_negative_integer() -> void:
|
||||
var result := YamlParser.parse("offset: -3")
|
||||
assert_that(result["offset"]).is_equal(-3)
|
||||
|
||||
|
||||
func test_parse_float_value() -> void:
|
||||
var result := YamlParser.parse("radius: 2.5")
|
||||
assert_that(typeof(result["radius"])).is_equal(TYPE_FLOAT)
|
||||
assert_float(result["radius"]).is_equal_approx(2.5, 0.001)
|
||||
|
||||
|
||||
func test_parse_boolean_true() -> void:
|
||||
var result := YamlParser.parse("enabled: true")
|
||||
assert_that(result["enabled"]).is_equal(true)
|
||||
assert_that(typeof(result["enabled"])).is_equal(TYPE_BOOL)
|
||||
|
||||
|
||||
func test_parse_boolean_false() -> void:
|
||||
var result := YamlParser.parse("enabled: false")
|
||||
assert_that(result["enabled"]).is_equal(false)
|
||||
|
||||
|
||||
func test_parse_empty_input() -> void:
|
||||
var result := YamlParser.parse("")
|
||||
assert_that(result.size()).is_equal(0)
|
||||
|
||||
|
||||
func test_parse_only_comments() -> void:
|
||||
var result := YamlParser.parse("# comment\n# another")
|
||||
assert_that(result.size()).is_equal(0)
|
||||
|
||||
|
||||
func test_parse_inline_comment_stripped() -> void:
|
||||
var result := YamlParser.parse("room_id: test # this is a comment")
|
||||
assert_that(result["room_id"]).is_equal("test")
|
||||
|
||||
|
||||
func test_parse_quoted_value_with_hash() -> void:
|
||||
## Quoted strings preserve literal # characters.
|
||||
var result := YamlParser.parse('color: "#e0e8ff"')
|
||||
assert_that(result["color"]).is_equal("#e0e8ff")
|
||||
|
||||
|
||||
func test_parse_value_with_colon() -> void:
|
||||
var result := YamlParser.parse('time: "12:30"')
|
||||
assert_that(result["time"]).is_equal("12:30")
|
||||
|
||||
|
||||
# -- parse(): nested maps -----------------------------------------------------
|
||||
|
||||
func test_parse_nested_map() -> void:
|
||||
var yaml := "section:\n key: value"
|
||||
var result := YamlParser.parse(yaml)
|
||||
assert_that(result.has("section")).is_true()
|
||||
assert_that(result["section"] is Dictionary).is_true()
|
||||
assert_that(result["section"]["key"]).is_equal("value")
|
||||
|
||||
|
||||
func test_parse_deeply_nested() -> void:
|
||||
var yaml := "a:\n b:\n c: deep"
|
||||
var result := YamlParser.parse(yaml)
|
||||
assert_that(result["a"]["b"]["c"]).is_equal("deep")
|
||||
|
||||
|
||||
func test_parse_multiple_sections() -> void:
|
||||
var yaml := "hud:\n mode: Mode\ninteraction:\n talk: Talk"
|
||||
var result := YamlParser.parse(yaml)
|
||||
assert_that(result["hud"]["mode"]).is_equal("Mode")
|
||||
assert_that(result["interaction"]["talk"]).is_equal("Talk")
|
||||
|
||||
|
||||
func test_parse_multiple_keys_per_section() -> void:
|
||||
var yaml := "hud:\n a: 1\n b: 2\n c: 3"
|
||||
var result := YamlParser.parse(yaml)
|
||||
assert_that(result["hud"]["a"]).is_equal(1)
|
||||
assert_that(result["hud"]["b"]).is_equal(2)
|
||||
assert_that(result["hud"]["c"]).is_equal(3)
|
||||
|
||||
|
||||
func test_parse_sibling_subsections() -> void:
|
||||
var yaml := "states:\n a:\n x: 1\n b:\n x: 2"
|
||||
var result := YamlParser.parse(yaml)
|
||||
assert_that(result["states"]["a"]["x"]).is_equal(1)
|
||||
assert_that(result["states"]["b"]["x"]).is_equal(2)
|
||||
|
||||
|
||||
func test_parse_section_with_trailing_comment() -> void:
|
||||
## "section: # comment" should be treated as a section header.
|
||||
var yaml := "section: # comment\n key: value"
|
||||
var result := YamlParser.parse(yaml)
|
||||
assert_that(result["section"]["key"]).is_equal("value")
|
||||
|
||||
|
||||
# -- parse(): arrays of dicts -------------------------------------------------
|
||||
|
||||
func test_parse_single_array_item() -> void:
|
||||
var yaml := "conditions:\n - id: test-1\n type: near\n x: 10"
|
||||
var result := YamlParser.parse(yaml)
|
||||
assert_that(result.has("conditions")).is_true()
|
||||
assert_that(result["conditions"] is Array).is_true()
|
||||
assert_that(result["conditions"].size()).is_equal(1)
|
||||
assert_that(result["conditions"][0]["id"]).is_equal("test-1")
|
||||
assert_that(result["conditions"][0]["type"]).is_equal("near")
|
||||
assert_that(result["conditions"][0]["x"]).is_equal(10)
|
||||
|
||||
|
||||
func test_parse_multiple_array_items() -> void:
|
||||
var yaml := "conditions:\n - id: a\n x: 1\n - id: b\n x: 2"
|
||||
var result := YamlParser.parse(yaml)
|
||||
assert_that(result["conditions"].size()).is_equal(2)
|
||||
assert_that(result["conditions"][0]["id"]).is_equal("a")
|
||||
assert_that(result["conditions"][1]["id"]).is_equal("b")
|
||||
|
||||
|
||||
func test_parse_array_items_with_blank_lines() -> void:
|
||||
var yaml := "conditions:\n - id: a\n x: 1\n\n - id: b\n x: 2"
|
||||
var result := YamlParser.parse(yaml)
|
||||
assert_that(result["conditions"].size()).is_equal(2)
|
||||
|
||||
|
||||
func test_parse_top_level_plus_array() -> void:
|
||||
## Checklist format: top-level key-value pairs followed by a conditions array.
|
||||
var yaml := "room_id: warehouse\nconditions:\n - id: c1\n type: near\n x: 5"
|
||||
var result := YamlParser.parse(yaml)
|
||||
assert_that(result["room_id"]).is_equal("warehouse")
|
||||
assert_that(result["conditions"].size()).is_equal(1)
|
||||
assert_that(result["conditions"][0]["id"]).is_equal("c1")
|
||||
|
||||
|
||||
func test_parse_array_typed_values() -> void:
|
||||
var yaml := "items:\n - id: t\n x: 42\n radius: 2.5\n active: true"
|
||||
var result := YamlParser.parse(yaml)
|
||||
var item: Dictionary = result["items"][0]
|
||||
assert_that(item["x"]).is_equal(42)
|
||||
assert_that(typeof(item["radius"])).is_equal(TYPE_FLOAT)
|
||||
assert_that(item["active"]).is_equal(true)
|
||||
|
||||
|
||||
# -- parse_flat(): dotted keys -------------------------------------------------
|
||||
|
||||
func test_flat_simple() -> void:
|
||||
var yaml := "section:\n key: value"
|
||||
var result := YamlParser.parse_flat(yaml)
|
||||
assert_that(result.has("section.key")).is_true()
|
||||
assert_that(result["section.key"]).is_equal("value")
|
||||
|
||||
|
||||
func test_flat_deeply_nested() -> void:
|
||||
var yaml := "a:\n b:\n c: deep"
|
||||
var result := YamlParser.parse_flat(yaml)
|
||||
assert_that(result["a.b.c"]).is_equal("deep")
|
||||
|
||||
|
||||
func test_flat_multiple_sections() -> void:
|
||||
var yaml := "hud:\n mode: Mode\ninteraction:\n talk: Talk"
|
||||
var result := YamlParser.parse_flat(yaml)
|
||||
assert_that(result["hud.mode"]).is_equal("Mode")
|
||||
assert_that(result["interaction.talk"]).is_equal("Talk")
|
||||
|
||||
|
||||
func test_flat_values_are_strings() -> void:
|
||||
## parse_flat returns all values as strings, unlike parse() which returns typed.
|
||||
var yaml := "section:\n count: 42\n rate: 0.9\n active: true"
|
||||
var result := YamlParser.parse_flat(yaml)
|
||||
assert_that(result["section.count"]).is_equal("42")
|
||||
assert_that(result["section.rate"]).is_equal("0.9")
|
||||
assert_that(result["section.active"]).is_equal("true")
|
||||
|
||||
|
||||
func test_flat_quoted_value() -> void:
|
||||
var yaml := 'section:\n key: "hello world"'
|
||||
var result := YamlParser.parse_flat(yaml)
|
||||
assert_that(result["section.key"]).is_equal("hello world")
|
||||
|
||||
|
||||
func test_flat_inline_comment() -> void:
|
||||
var yaml := "hud:\n mode: Standard # default"
|
||||
var result := YamlParser.parse_flat(yaml)
|
||||
assert_that(result["hud.mode"]).is_equal("Standard")
|
||||
|
||||
|
||||
func test_flat_empty_quoted_value() -> void:
|
||||
var yaml := 'hud:\n prefix: "" # No prefix'
|
||||
var result := YamlParser.parse_flat(yaml)
|
||||
assert_that(result.has("hud.prefix")).is_true()
|
||||
assert_that(result["hud.prefix"]).is_equal("")
|
||||
|
||||
|
||||
func test_flat_skips_arrays() -> void:
|
||||
## Arrays have no dotted-key representation — they are skipped in flat output.
|
||||
var yaml := "room_id: test\nconditions:\n - id: c1\n x: 5"
|
||||
var result := YamlParser.parse_flat(yaml)
|
||||
assert_that(result.has("room_id")).is_true()
|
||||
# No dotted keys for array contents
|
||||
assert_that(result.has("conditions")).is_false()
|
||||
assert_that(result.has("conditions.0")).is_false()
|
||||
|
||||
|
||||
func test_flat_empty_input() -> void:
|
||||
var result := YamlParser.parse_flat("")
|
||||
assert_that(result.size()).is_equal(0)
|
||||
|
||||
|
||||
# -- Dialogue theme format (regression) ----------------------------------------
|
||||
|
||||
func test_dialogue_theme_format() -> void:
|
||||
## dialogue-theme.yaml has top-level values and one nested map (npc_colors).
|
||||
var yaml := "player_color: \"#e0e8ff\"\nnpc_colors:\n 0: \"#4a9ebb\"\n 1: \"#6bc9a6\"\npassive_opacity: 0.9"
|
||||
var flat := YamlParser.parse_flat(yaml)
|
||||
assert_that(flat["player_color"]).is_equal("#e0e8ff")
|
||||
assert_that(flat["npc_colors.0"]).is_equal("#4a9ebb")
|
||||
assert_that(flat["npc_colors.1"]).is_equal("#6bc9a6")
|
||||
assert_that(flat["passive_opacity"]).is_equal("0.9")
|
||||
|
||||
|
||||
# -- Checklist format (regression) ---------------------------------------------
|
||||
|
||||
func test_checklist_format() -> void:
|
||||
## checklist.yaml: top-level kv + conditions array with typed values.
|
||||
var yaml := "room_id: inventory_warehouse\nconditions:\n - id: test-1\n condition_type: player_near\n x: 10\n y: 20\n radius: 3.0"
|
||||
var result := YamlParser.parse(yaml)
|
||||
assert_that(result["room_id"]).is_equal("inventory_warehouse")
|
||||
var cond: Dictionary = result["conditions"][0]
|
||||
assert_that(cond["id"]).is_equal("test-1")
|
||||
assert_that(cond["condition_type"]).is_equal("player_near")
|
||||
assert_that(cond["x"]).is_equal(10)
|
||||
assert_that(cond["y"]).is_equal(20)
|
||||
assert_that(typeof(cond["radius"])).is_equal(TYPE_FLOAT)
|
||||
+17
-12
@@ -15,6 +15,11 @@ signal dialogue_dismissed # Walk-away or conversation end
|
||||
signal confrontation_monologue(text: String, duration: float) # D-063: beat monologue
|
||||
signal pause_requested # D-061: auto-pause — main.gd routes through input recording (#507)
|
||||
signal unpause_requested # D-061: auto-unpause
|
||||
# D-020 (#558): Decoupled signals — dialogue_box emits, main.gd (coordinator) handles.
|
||||
# Replaces direct GameState.dialogue_active mutation and AudioManager calls.
|
||||
signal dialogue_state_changed(active: bool)
|
||||
signal audio_dip_requested(profile: String)
|
||||
signal audio_dip_cleared
|
||||
|
||||
@onready var panel: PanelContainer = $PanelContainer
|
||||
@onready var dialogue_log: RichTextLabel = $PanelContainer/MarginContainer/VBoxContainer/DialogueLog
|
||||
@@ -286,10 +291,10 @@ func show_dialogue(npc_name: String, speech: String, options: Array = []) -> voi
|
||||
# Show panel
|
||||
_ensure_visible()
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
GameState.dialogue_active = true # D-064: block movement while in conversation
|
||||
dialogue_state_changed.emit(true) # D-064: coordinator blocks movement
|
||||
|
||||
# D-069: Dialogue dip
|
||||
AudioManager.apply_dip("dialogue")
|
||||
# D-069: Dialogue dip — coordinator routes to AudioManager
|
||||
audio_dip_requested.emit("dialogue")
|
||||
|
||||
# D-061: auto-pause — signal to main.gd for input recording (#507)
|
||||
pause_requested.emit()
|
||||
@@ -311,14 +316,14 @@ func _end_player_conversation() -> void:
|
||||
entry.timestamp_msec = now
|
||||
_log_dirty = true
|
||||
|
||||
# D-069: Clear dialogue/confrontation dip
|
||||
AudioManager.clear_dip()
|
||||
# D-069: Clear dialogue/confrontation dip — coordinator routes to AudioManager
|
||||
audio_dip_cleared.emit()
|
||||
|
||||
# D-061: unpause — signal to main.gd for input recording (#507)
|
||||
unpause_requested.emit()
|
||||
|
||||
# D-064: unblock movement immediately — log entries stay visible but don't block input.
|
||||
GameState.dialogue_active = false
|
||||
# D-064: unblock movement immediately — coordinator handles GameState update.
|
||||
dialogue_state_changed.emit(false)
|
||||
|
||||
# If no entries remain, hide the panel with fade.
|
||||
if _log_entries.is_empty():
|
||||
@@ -331,11 +336,11 @@ func hide_dialogue() -> void:
|
||||
_end_player_conversation()
|
||||
return # _end_player_conversation may call hide_dialogue if log is empty
|
||||
|
||||
GameState.dialogue_active = false
|
||||
dialogue_state_changed.emit(false) # D-020: coordinator handles GameState update
|
||||
|
||||
|
||||
func is_dialogue_active() -> bool:
|
||||
return _in_player_conversation or GameState.dialogue_active
|
||||
return _in_player_conversation
|
||||
|
||||
|
||||
func has_active_entries() -> bool:
|
||||
@@ -428,12 +433,12 @@ func _start_confrontation_beat(response_id: String, text: String) -> void:
|
||||
_active_tween.tween_property(panel, "modulate:a", CONFRONTATION_DIM_ALPHA, 0.2)
|
||||
|
||||
confrontation_monologue.emit(UIStrings.get_text(CONFRONTATION_MONOLOGUE_KEY), CONFRONTATION_BEAT_DURATION)
|
||||
AudioManager.apply_dip("confrontation")
|
||||
audio_dip_requested.emit("confrontation") # D-069: coordinator routes to AudioManager
|
||||
|
||||
_beat_tween = create_tween()
|
||||
_beat_tween.tween_interval(CONFRONTATION_BEAT_DURATION)
|
||||
_beat_tween.tween_callback(func():
|
||||
AudioManager.clear_dip()
|
||||
audio_dip_cleared.emit() # D-069: coordinator routes to AudioManager
|
||||
option_selected.emit(response_id, text)
|
||||
_end_player_conversation()
|
||||
)
|
||||
@@ -443,7 +448,7 @@ func _cancel_beat() -> void:
|
||||
if _beat_tween and _beat_tween.is_valid():
|
||||
_beat_tween.kill()
|
||||
_beat_tween = null
|
||||
AudioManager.clear_dip()
|
||||
audio_dip_cleared.emit() # D-069: coordinator routes to AudioManager
|
||||
|
||||
|
||||
# -- Log rendering --
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
extends Control
|
||||
## #257: Loading screen overlay — blocks input during save/load round-trip.
|
||||
## Shown when LOAD_GAME fires; hidden when save_result arrives (success or failure).
|
||||
## Full-screen, dark overlay with centered status text.
|
||||
|
||||
const BG_COLOR := Color(0.0, 0.0, 0.0, 0.75)
|
||||
const TEXT_COLOR := Color("#c8d0e0")
|
||||
const FONT_SIZE := 18
|
||||
|
||||
var _label: Label = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
visible = false
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_build_ui()
|
||||
|
||||
|
||||
func _build_ui() -> void:
|
||||
var bg := ColorRect.new()
|
||||
bg.color = BG_COLOR
|
||||
bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(bg)
|
||||
|
||||
_label = Label.new()
|
||||
_label.text = UIStrings.get_text("notifications.loading")
|
||||
_label.add_theme_font_size_override("font_size", FONT_SIZE)
|
||||
_label.add_theme_color_override("font_color", TEXT_COLOR)
|
||||
_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
_label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_label)
|
||||
|
||||
|
||||
func show_loading() -> void:
|
||||
visible = true
|
||||
|
||||
|
||||
## Hide the loading overlay. success=false is reserved for future failure-state UI.
|
||||
func hide_loading(success: bool = true) -> void:
|
||||
visible = false
|
||||
@@ -0,0 +1 @@
|
||||
uid://c6wtn7qk3mv2x
|
||||
@@ -0,0 +1,15 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://b7rv9mkl4qpw3"]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/loading_screen.gd" id="1_loading"]
|
||||
|
||||
; #257: Loading screen — full-screen overlay shown during save/load round-trip.
|
||||
; Blocks input; dismissed when save_result arrives from server.
|
||||
[node name="LoadingScreen" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 1
|
||||
script = ExtResource("1_loading")
|
||||
+81
-4
@@ -1,7 +1,8 @@
|
||||
extends Control
|
||||
## #258: Main menu — New Game / Continue / Quit.
|
||||
## #258: Main menu — New Game / Continue / Load Game / Quit.
|
||||
## New Game: generates per-game save directory (D-085), starts game.
|
||||
## Continue: loads most recent save directory (#554 full loading screen deferred).
|
||||
## Continue: loads most recent save directory.
|
||||
## Load Game: shows sorted save list for manual selection (#257).
|
||||
|
||||
const GAME_SCENE := "res://scenes/main.tscn"
|
||||
|
||||
@@ -16,22 +17,31 @@ const FONT_SIZE_BTN := 15
|
||||
|
||||
@onready var _new_game_btn: Button = $VBox/NewGameBtn
|
||||
@onready var _continue_btn: Button = $VBox/ContinueBtn
|
||||
@onready var _load_game_btn: Button = $VBox/LoadGameBtn
|
||||
@onready var _quit_btn: Button = $VBox/QuitBtn
|
||||
@onready var _load_panel: Control = $LoadGamePanel
|
||||
@onready var _saves_list: VBoxContainer = $LoadGamePanel/VBox/SavesScroll/SavesList
|
||||
@onready var _load_back_btn: Button = $LoadGamePanel/VBox/BackBtn
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_new_game_btn.pressed.connect(_on_new_game)
|
||||
_continue_btn.pressed.connect(_on_continue)
|
||||
_load_game_btn.pressed.connect(_on_load_game_browse)
|
||||
_quit_btn.pressed.connect(_on_quit)
|
||||
_load_back_btn.pressed.connect(_on_load_back)
|
||||
_load_panel.visible = false
|
||||
_refresh_continue_state()
|
||||
|
||||
|
||||
func _refresh_continue_state() -> void:
|
||||
var saves := SessionManager.list_game_dirs()
|
||||
_continue_btn.disabled = saves.is_empty()
|
||||
_load_game_btn.disabled = saves.is_empty()
|
||||
|
||||
|
||||
func _on_new_game() -> void:
|
||||
GameState.pending_load_path = "" # clear stale load path from previous Load selection
|
||||
var game_id := SessionManager.new_game()
|
||||
if game_id.is_empty():
|
||||
push_error("MainMenu: new_game() failed to create save directory — cannot start")
|
||||
@@ -40,8 +50,7 @@ func _on_new_game() -> void:
|
||||
|
||||
|
||||
func _on_continue() -> void:
|
||||
# #554: Full loading screen blocked until server SaveCommand lands.
|
||||
# For now: automatically load the most recent game directory.
|
||||
GameState.pending_load_path = "" # clear stale load path from previous Load selection
|
||||
var saves := SessionManager.list_game_dirs()
|
||||
if saves.is_empty():
|
||||
return
|
||||
@@ -49,5 +58,73 @@ func _on_continue() -> void:
|
||||
get_tree().change_scene_to_file(GAME_SCENE)
|
||||
|
||||
|
||||
var _list_built: bool = false # guard against queue_free() race on rapid reopen
|
||||
|
||||
|
||||
func _on_load_game_browse() -> void:
|
||||
if not _list_built:
|
||||
_build_saves_list()
|
||||
_list_built = true
|
||||
_load_panel.visible = true
|
||||
|
||||
|
||||
func _on_load_back() -> void:
|
||||
_load_panel.visible = false
|
||||
_list_built = false # allow rebuild on next open
|
||||
|
||||
|
||||
func _on_quit() -> void:
|
||||
get_tree().quit()
|
||||
|
||||
|
||||
## Build the saves list panel from existing save directories, sorted by date (newest first).
|
||||
func _build_saves_list() -> void:
|
||||
for child in _saves_list.get_children():
|
||||
child.queue_free()
|
||||
|
||||
var saves := SessionManager.list_game_dirs()
|
||||
if saves.is_empty():
|
||||
var lbl := Label.new()
|
||||
lbl.text = UIStrings.get_text("menu.load_game_empty")
|
||||
lbl.add_theme_color_override("font_color", Color(BTN_DISABLED_COLOR))
|
||||
lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
_saves_list.add_child(lbl)
|
||||
return
|
||||
|
||||
for save in saves:
|
||||
var btn := Button.new()
|
||||
btn.text = _format_save_entry(save)
|
||||
btn.add_theme_font_size_override("font_size", FONT_SIZE_BTN)
|
||||
var has_save_file: bool = not save.get("newest_save", "").is_empty()
|
||||
if has_save_file:
|
||||
btn.add_theme_color_override("font_color", BTN_NORMAL_COLOR)
|
||||
btn.pressed.connect(_on_save_selected.bind(save))
|
||||
else:
|
||||
btn.add_theme_color_override("font_color", BTN_DISABLED_COLOR)
|
||||
btn.disabled = true
|
||||
_saves_list.add_child(btn)
|
||||
|
||||
|
||||
func _on_save_selected(save: Dictionary) -> void:
|
||||
var game_id: String = save.get("game_id", "")
|
||||
var save_file: String = save.get("newest_save", "")
|
||||
if save_file.is_empty():
|
||||
push_error("MainMenu: save entry '%s' has no newest_save — load cancelled" % game_id)
|
||||
return
|
||||
SessionManager.resume_game(game_id)
|
||||
GameState.pending_load_path = "user://saves/" + game_id + "/" + save_file
|
||||
get_tree().change_scene_to_file(GAME_SCENE)
|
||||
|
||||
|
||||
## Format a save entry for display. game_id format: YYYYMMDD-HHMMSS-hex6.
|
||||
static func _format_save_entry(save: Dictionary) -> String:
|
||||
var game_id: String = save.get("game_id", "")
|
||||
var parts := game_id.split("-")
|
||||
if parts.size() >= 2 and parts[0].length() == 8 and parts[1].length() == 6:
|
||||
var d := parts[0]
|
||||
var t := parts[1]
|
||||
return "%s-%s-%s %s:%s:%s" % [
|
||||
d.substr(0, 4), d.substr(4, 2), d.substr(6, 2),
|
||||
t.substr(0, 2), t.substr(2, 2), t.substr(4, 2),
|
||||
]
|
||||
return game_id
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../tooling/db
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
-- Commonwealth Project Ticketing Database Schema
|
||||
-- Access via: python3 db/connectors/sqlite_connector.py <command>
|
||||
-- Access via: python3 tooling/db/sqlite_connector.py <command>
|
||||
-- DO NOT use sqlite3 CLI (crashes in Claude Code due to std::bad_alloc bug)
|
||||
|
||||
PRAGMA journal_mode=WAL;
|
||||
@@ -64,7 +64,7 @@ CREATE INDEX IF NOT EXISTS idx_history_ticket ON ticket_history(ticket_id);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Decision Sync Tables
|
||||
-- Populated by: python3 db/connectors/decisions_sync.py
|
||||
-- Populated by: python3 tooling/db/decisions_sync.py
|
||||
-- Source: decisions/*.md domain files
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
+4
-4
@@ -10,12 +10,12 @@ Cross-domain decisions live in one file with cross-reference notes in related fi
|
||||
|
||||
| File | Domain | Decisions |
|
||||
|------|--------|-----------|
|
||||
| [architecture.md](architecture.md) | Technical foundation | D-008, D-009, D-010, D-012, D-020, D-026, D-030, D-031, D-041, D-042, D-054, D-055, D-066, D-068, D-073, D-085, D-088 |
|
||||
| [architecture.md](architecture.md) | Technical foundation | D-008, D-009, D-010, D-012, D-020, D-026, D-030, D-031, D-041, D-042, D-054, D-055, D-066, D-068, D-073, D-085, D-088, D-094, D-096, D-097, D-099, D-100, D-101, D-102, D-103, D-106, D-108, D-109 |
|
||||
| [perception.md](perception.md) | Player observation | D-011, D-015, D-016, D-017, D-018, D-019, D-033, D-035, D-043, D-044, D-045, D-046, D-047, D-048, D-049, D-052, D-056, D-057, D-058, D-059, D-060, D-061, D-067, D-069, D-070, D-071, D-072, D-076, D-077, D-078, D-086 |
|
||||
| [content.md](content.md) | NPC, dialogue, templates | D-023, D-024, D-025, D-028, D-029, D-032, D-034, D-035, D-036, D-037, D-050, D-062, D-063, D-064, D-074, D-075, D-084, D-090, D-092 |
|
||||
| [content.md](content.md) | NPC, dialogue, templates | D-023, D-024, D-025, D-028, D-029, D-032, D-034, D-035, D-036, D-037, D-050, D-062, D-063, D-064, D-074, D-075, D-084, D-090, D-092, D-093, D-095, D-098, D-104, D-105, D-107 |
|
||||
| [scope.md](scope.md) | Game concept, prototype | D-001, D-003, D-005, D-006, D-007, D-013, D-014, D-027, D-038, D-039, D-051, D-053, D-065, D-087, D-089, D-091 |
|
||||
| [process.md](process.md) | Team, workflow | D-004, D-021, D-022, D-040 |
|
||||
| [questions.md](questions.md) | Open questions | Q-001 through Q-039 |
|
||||
| [questions.md](questions.md) | Open questions | Q-001 through Q-050 |
|
||||
| [rejected.md](rejected.md) | Rejected alternatives | R-001 through R-010 |
|
||||
|
||||
## Querying Decisions
|
||||
@@ -24,7 +24,7 @@ The SQLite database contains a `decisions` table synced from these files. Common
|
||||
|
||||
```bash
|
||||
# All active architecture decisions
|
||||
db/connectors/sqlite-query "SELECT id, title FROM decisions WHERE domain='architecture' AND status='active'"
|
||||
tooling/db/sqlite-query "SELECT id, title FROM decisions WHERE domain='architecture' AND status='active'"
|
||||
|
||||
# Decisions without implementing tickets
|
||||
make decisions-orphan
|
||||
|
||||
+103
-1
@@ -234,4 +234,106 @@ Technical foundation decisions that constrain implementation: engine, client-ser
|
||||
|
||||
---
|
||||
|
||||
*18 decisions. Last updated: 2026-02-12 (D-088 added — retroactive filing from v0.1 Content Scoping Workshop)*
|
||||
### D-094: District Spatial Hierarchy — Chunk, Block, District Naming and Sizes
|
||||
- **Date:** 2026-02-25
|
||||
- **Decision:** The spatial hierarchy for map generation and streaming is defined as follows. **Chunk** = 64×64 sim tiles (32×32 visual tiles, 32m) — the streaming and serialization unit. **Block** = 128×128 sim tiles (64×64 visual tiles, 64m) — the generator planning unit, composed of 4 chunks arranged in a 2×2 grid. Each block contains 4 chunks; chunks within a block can merge into one large edifice, remain separate (small buildings, gardens, cafes), or form L-shaped buildings across chunk boundaries. **District** = 4×4 blocks = 512×512 sim tiles (256×256 visual tiles, 256m) per z-level, containing 16 blocks and 64 chunks. Large civic structures (gate terminals, horizon station installations, stadiums, parks) span multiple blocks. Three z-levels for the Transit District = ~1.35MB (trivial). This decision amends D-012 and overrides the ~150×150 visual estimate in D-014.
|
||||
- **Rationale:** Chunk size of 32×32 visual (64×64 sim) gives a 32m streaming cell — large enough to hold a meaningful space, small enough for efficient streaming. The 2×2-chunk block provides a generator planning unit with enough granularity for per-chunk variation. The 4×4 block district (256×256 visual) gives a full district footprint generalisable as a template for the Q-036 generator. The chunk-based fill system within blocks allows the generator to place buildings of varying scale without hard-coding building dimensions.
|
||||
- **Raised by:** Tyre (chunk/block spec and memory confirmation), confirmed by team. Lead ratified district = 4×4 blocks.
|
||||
- **Dissent:** Araminta preferred 32×32 visual chunk size (effectively halving the chunk to a 16m cell). Overruled by lead and team majority — 32m chunk is the minimum viable streaming cell for the simulation architecture.
|
||||
- **Source:** Station District Layout Workshop, Ticket #153, Sprint 20. Round document: `docs/discussions/round-20-station-district-layout.md`
|
||||
- **Cross-reference:** D-012 (tile spec — amended), D-014 (v0.1 map spec — district bounding box superseded), D-066 (dual-scale grid), D-093 (Sova Transit District layout using this hierarchy), Q-036 (district generator)
|
||||
|
||||
---
|
||||
|
||||
### D-096: DistrictLayoutMode — Grid and Organic Support
|
||||
- **Date:** 2026-02-27
|
||||
- **Decision:** Two layout modes coexist for district generation. `Grid`: Commission-planned districts with rectilinear block placement. `Organic`: pioneer/growth districts with block offsets (±16 sim tiles per axis), rotation (0–3 steps, 15° increments), variable street width (0.75–2.0×). Hard technical ceiling: maximum rotation ±45°. Beyond 45°, tile-based pathfinding produces unacceptable movement artifacts. Organic districts produce curved-street impressions through angular jogs and irregular setbacks. Grid vs. Organic proportions must vary per seed to prevent predictable meta-level patterns.
|
||||
- **Rationale:** Grid = power imposed (Commission-planned). Organic = power negotiated (pioneer settlements, organic growth). Both modes encode political and settlement history in spatial form.
|
||||
- **Source:** Generator Architecture Workshop (#562), 2026-02-27. Full spec: `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-1.
|
||||
- **Raised by:** Tyre (technical architecture), Miri (cultural grammar). Full team sign-off.
|
||||
- **Dissent:** None.
|
||||
- **Cross-reference:** D-094 (spatial hierarchy), Q-036 (district generator)
|
||||
|
||||
### D-097: Guarantee Tier System — Universal / Full-Only / Conditional
|
||||
- **Date:** 2026-02-27
|
||||
- **Decision:** The district generator runs a `GuaranteeAuditResult` with three tiers of spatial guarantees. **Tier 1 — Universal (all inhabited):** Social Hub, Informal Zone, Encounter Corridor. **Tier 2 — Full-complexity:** Traffic Chokepoint, Institutional Space, Insider Space, Economic Node, Horizon View Corridor (coastal), BreachOnly Zone (≥1), Rooftop Discovery Zone (tall structures). **Tier 3 — Conditional:** A-1 Elevated Vantage, A-2 Egress Multiplicity, A-3 Temporal Opacity Window, A-4 Non-Institutional Route, Economic Asymmetry Signal, Power Gradient Visibility. A Full-complexity coastal urban hub gets up to 13 checks. Archetype placement must vary in angular position (not just distance) across seeds — audit fails if archetypes cluster predictably across a test batch of N seeds.
|
||||
- **Rationale:** The generator makes contracts it keeps. Guaranteed affordances ensure every playstyle has spatial affordances in any district, without hand-crafting each location.
|
||||
- **Source:** Generator Architecture Workshop (#562), 2026-02-27. Full spec: `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-2.
|
||||
- **Raised by:** Gestalt (tier structure + assassin lens integration), Tyre (GuaranteeAuditResult struct). Full team sign-off.
|
||||
- **Dissent:** None.
|
||||
- **Cross-reference:** D-103 (assassin lens guarantees A-1 through A-4), D-102 (horizon view corridor — Tier 2 coastal)
|
||||
|
||||
### D-099: WallBackside / TileBehindState — Dual Classification
|
||||
- **Date:** 2026-02-27
|
||||
- **Decision:** Two complementary enums classify tiles behind wall surfaces. `WallBackside` (structural): what is physically there — `AdjacentSpace | StructuralFill | ServiceVoid | ChunkBoundary | Exterior`. `TileBehindState` (gameplay): what kind of space this represents — `StructuralFill | HiddenRoom | Interstitial`. Mapping: `ServiceVoid → Interstitial`; `AdjacentSpace → HiddenRoom or StructuralFill` depending on access tier. Era-tagged infrastructure cavity contents with standardized color codes: Era 1 power conduit only (`#c8b840`), Era 2 power + water/coolant (`#4888c8`) + comm lines (`#b8b8b8`), Era 3 full bundle. Backside assignments within a template must have seed-driven variation — not fixed template values.
|
||||
- **Rationale:** "Every wall is a secret keeper." No tile is ever void. Dual classification separates structural truth (what's there physically) from gameplay meaning (what does this imply for the player's investigation).
|
||||
- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-4.
|
||||
- **Raised by:** Tyre (WallBackside), Gestalt (TileBehindState). Full team sign-off.
|
||||
- **Dissent:** None.
|
||||
|
||||
### D-100: Dynamic Modification via Overlay — DamageOverlay and RegenerationStrategy
|
||||
- **Date:** 2026-02-27
|
||||
- **Decision:** Generator output is immutable after Phase 1. All post-generation modifications are applied via overlay, not re-generation. `DamageOverlay` struct: `overlay_type` (GasExplosion | Fire | Structural { collapse_direction } | Flooding), `epicenter: ChunkLocalPos`, `radius: f32`, `intensity: f32`, `scatter_seed: u64` (variation within zone only). `RegenerationStrategy` enum: `LocalOverlay(DamageParameters)` for in-playthrough events (MANDATORY), `SoftReseed { seed_modifier: u64 }` at scenario boundaries only, `FullReseed` at era-level discontinuities only. Trauma event → visual stage mapping: PhysicalDestruction/ViolenceEvent → Stage 2 (Fresh Aftermath), decays to Stage 3; EconomicDisruption/PoliticalShock/MigrationShock → quarter fill modifier. Full stage sequence: Stage 1 Active → Stage 2 Fresh Aftermath → Stage 3 Stabilized → Stage 4 Reconstruction → Stage 5 Healed Scar. Destruction palette is corruption-only: no new colors introduced by destruction. Single exception: `#c8d8f0` open-sky tile appears when a roofed structure has its roof removed. See D-109 for the XOR prohibition as architectural mandate.
|
||||
- **Rationale:** Modification history diverges per playthrough on the same seed. Same world, different event histories, different delta layers — this is the replayability engine. Causal legibility requires the player to be able to read what happened from the world state.
|
||||
- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-5.
|
||||
- **Raised by:** Tyre (structs), Gestalt (LocalOverlay mandate). Destruction stages and palette constraint: Araminta (Round 5).
|
||||
- **Dissent:** None.
|
||||
- **Cross-reference:** D-109 (XOR prohibition as architectural mandate), D-107 (trauma events — cultural track)
|
||||
|
||||
### D-101: ZonePalette Modifier System
|
||||
- **Date:** 2026-02-27
|
||||
- **Decision:** Zone palettes use `ZonePalette { base: BasePalette, modifiers: Vec<PaletteModifier> }`. Eight canonical base terrain types: T1 temperate farmland (warm organic, natural lighting) / T2 industrial farmland (cool grey-green, artificial lighting) / T3 wilderness / T4 grassland / T5 coastal water (deep near-black blue, animated specular; referenced by D-102 horizon corridor guarantee) / T6 beach/coastal margin (warm dark tan) / T7 mountain/high terrain (dark blue-grey stone, snow at elevation) / T8 desert/arid. T1 and T2 are explicitly distinct farmland types. Additional terrain types must be specified with new numbers — not silent replacements for existing types. Modifier axes: A (heritage root → material character), B (economic tier → condition/density), C (era → material generation), plus faction overlay, climate, condition, season. Palette modifiers influence NPC appearance as well as environment (people dress like they're from here).
|
||||
- **Rationale:** A zone's visual identity must be legible at a glance. Palette modifiers create cultural visual identity without rewriting base terrain.
|
||||
- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-6.
|
||||
- **Raised by:** Araminta (terrain types and color specs, canonical T5/T7 numbering corrected Round 5), Tyre (palette struct). Full team sign-off.
|
||||
- **Dissent:** None.
|
||||
- **Cross-reference:** D-102 (horizon view corridor — T5 coastal water is the referenced terrain type), D-104 (heritage grammar overlay — modifier axis A)
|
||||
|
||||
### D-102: Horizon View Corridor as Coastal Guarantee
|
||||
- **Date:** 2026-02-27
|
||||
- **Decision:** A **negative-space** reservation for coastal districts: ≥8 visual tiles unobstructed view corridor from nearest public street to water's edge. No building, tree, or z=4 element may occupy this corridor. A low z=2 element (railing, bench, bollard) marks the waterfront point as a designed viewing location. Tier 2 Conditional guarantee — applies to Full-complexity coastal districts. Position within the district must vary per seed; the Wow Moment of seeing the horizon must be discovered, not expected.
|
||||
- **Rationale:** "Negative-space reservation" framing — the generator reserves space by prohibiting placement, not by placing something. The view of the horizon is a spatially guaranteed player experience.
|
||||
- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-7.
|
||||
- **Raised by:** Araminta (visual grammar and negative-space framing), Tyre (implementation constraint). Full team sign-off.
|
||||
- **Dissent:** None.
|
||||
- **Cross-reference:** D-097 (guarantee tier system — Tier 2), D-101 (ZonePalette — T5 coastal water is the terrain type this guarantee references)
|
||||
|
||||
### D-103: Assassin Lens Spatial Guarantees — A-1 through A-4
|
||||
- **Date:** 2026-02-27
|
||||
- **Decision:** Four derived spatial properties validated by the guarantee audit for Full-complexity districts. These are **derived properties of existing spatial configuration**, not assassin-tagged features — they add no generation cost; the audit validates existing output. **A-1 Elevated Vantage** (Tier 3): ≥1 position with clear LOS cone to Traffic Chokepoint. **A-2 Egress Multiplicity** (Tier 3): ≥2 exit routes to adjacent districts. **A-3 Temporal Opacity Window** (Tier 3): ≥1 time window where Social Hub has reduced ambient NPC coverage. **A-4 Non-Institutional Route** (mandatory Full-complexity): ≥1 route to any Insider zone not passing through high-security institutional spaces. A-1/A-2/A-3 are Tier 3 Conditional (trigger on `complexity_tier == Full`). A-4 is mandatory for all Full-complexity districts regardless of playstyle.
|
||||
- **Rationale:** The investigator/assassin playstyle needs guaranteed affordances without the generator explicitly building for assassination. Derived properties keep generation cost zero while ensuring spatial conditions exist.
|
||||
- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-8.
|
||||
- **Raised by:** Gestalt (assassin lens framing and derived-properties insight). Full team sign-off.
|
||||
- **Dissent:** None.
|
||||
- **Cross-reference:** D-097 (guarantee tier system — Tier 3)
|
||||
|
||||
### D-106: Vertical Scale Architecture and Rooftop Bar Clause
|
||||
- **Date:** 2026-02-27
|
||||
- **Decision:** Four height tiers: S1 (1–2 z-levels, surface + roof/mezzanine), S2 (3–10), S3 (11–30), S4 (30+). Shadow length is the primary height signal in top-down view (2–40 visual tiles). Lazy z-level loading: `ZLevelLoadState: Loaded | Skeleton | Ungenerated` — only current + adjacent z-levels filled by Phase 2. **Rooftop Bar Clause:** Every tall structure (z_band_count ≥ 3) must assign `RooftopConfig: Restricted | PublicWithHiddenLayer`. Discovery layer mandatory in both configurations. Heritage root **weights the probability** between the two configs — it does not determine the outcome. Final config is seeded per-building; a minority of buildings of any heritage root may be the non-dominant type (a Frost building with a rooftop bar must be possible). Z-band floor boundaries must have seed-variation within cultural ordering constraints. Vertical access routes are playthrough-history dependent.
|
||||
- **Rationale:** Height has meaning — floor 30 has information floor 1 cannot have because it is harder to reach. Full determination of rooftop config by heritage root kills the discovery moment.
|
||||
- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-11.
|
||||
- **Raised by:** Tyre (z-level architecture), Ozzie (Rooftop Bar Clause — discovery guarantee). Ozzie + Araminta corrected "determines" → "weights probability" in Round 5.
|
||||
- **Dissent:** None.
|
||||
- **Cross-reference:** D-094 (spatial hierarchy), D-097 (guarantee tier system — Rooftop Discovery Zone is Tier 2)
|
||||
|
||||
### D-108: MobileChunk Specification
|
||||
- **Date:** 2026-02-27
|
||||
- **Decision:** Entity-carried interior space attached to a mobile world entity. Not a district — uses the same chunk fill primitives in a simpler flat structure (no Phase 1/Phase 2 split, no block grid, no zone negotiation). Key structs: `MobileChunk`, `MobileInterior`, `VesselClass`, `MobileMovementState` (Docked / InTransit / InterSystem / Idle), `TransitSocialModifier`, `MobileNpcSlot`, `NpcPersistence` (Crew | Passenger). `Idle` = vessel parked at a location but not docked to infrastructure (anchored ship, grounded shuttle). Vessels are **persistent world entities** — interior cache keyed by entity_id persists across voyages for crew state. `Docked` state requires `dock_position`, `connected_chunk: Option<ChunkCoord>`, `docked_since: SimTick`, `scheduled_departure: Option<SimTick>`. `scheduled_departure` must be populated by the generator; vessels without departure schedules are an error state. Cultural grammar: `TransitSocialModifier` with `TransitVariant` (BoundedLinear | BoundedMobile | InterSystem). Vessel visual grammar (5 rules): (1) hull uses vessel-identity material, not zone palette; (2) windows reveal exterior context (docked vs. transit); (3) compression modifier tightens proportions; (4) section transitions use vessel-identity threshold elements; (5) class stratification via proportion, not palette. Replayability requirements R-V-1 through R-V-6 in `docs/workshops/generator-architecture/round-4-notes.md` §5. Memory: ~0.5–4KB metadata + up to 64KB ChunkData per vessel; paged by streaming model.
|
||||
- **Rationale:** "The journey is content — mobile environments are social pressure cookers, not loading screens with chairs." Vessel persistence and crew state continuity make the world feel real.
|
||||
- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-13.
|
||||
- **Raised by:** Tyre (struct design), Miri (cultural grammar — `miri-round4.md`), Nigel (replayability requirements), Ozzie (player experience). Visual grammar: Araminta (`araminta-round4.md` §2).
|
||||
- **Dissent:** Nigel initially proposed instanced districts for vessels; lead ruled entity-carried MobileChunk for persistence.
|
||||
- **Cross-reference:** D-100 (DamageOverlay applies to vessel damage), D-109 (LocalOverlay mandate), Q-046 (departure schedule — resolved by this D-record)
|
||||
|
||||
### D-109: DamageOverlay / RegenerationStrategy Prohibition — Architectural Mandate
|
||||
- **Date:** 2026-02-27
|
||||
- **Decision:** XOR reseeding for in-playthrough events is **architecturally prohibited**. `LocalOverlay` is the mandatory modification strategy for all events that occur while the player is present. `SoftReseed` and `FullReseed` are permitted only at scenario-boundary and era-level discontinuities respectively — events the player was not present for, where causal legibility is not required. This prohibition is filed as a separate D-record from D-100 because it establishes the modification principle for the entire game, not just the overlay mechanics.
|
||||
- **Rationale:** Causal legibility: the player must be able to look at a damaged district and understand what happened. XOR reseeding destroys the causal thread. Unanimous consensus across all workshop participants — the strongest architectural agreement of the entire workshop.
|
||||
- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-14.
|
||||
- **Raised by:** Gestalt (XOR prohibition framing), Tyre (RegenerationStrategy struct). Unanimous.
|
||||
- **Dissent:** None.
|
||||
- **Cross-reference:** D-100 (DamageOverlay + RegenerationStrategy full specification)
|
||||
|
||||
---
|
||||
|
||||
*29 decisions. Last updated: 2026-02-27 (D-096 through D-109 added — Generator Architecture Workshop #562)*
|
||||
|
||||
+61
-1
@@ -227,4 +227,64 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio
|
||||
|
||||
---
|
||||
|
||||
*19 decisions. Last updated: 2026-02-12 (D-090, D-092 added — retroactive filings from v0.1 Content Scoping Workshop and Wiki Review Workshop)*
|
||||
### D-093: Sova Transit District — Spatial Layout and District Topology
|
||||
- **Date:** 2026-02-25
|
||||
- **Decision:** The Sova Transit District spatial layout is confirmed. Four social sites (D-025): Terminal (Sova Logistics Hub, 44×28 visual), Bar/Last Shift (28×22 + 6m east ext.), Gate Cluster (40×32 visual, 7 zones — see zone spec below), Sector 3 residential (Drin/Naia anchor, ~15×12 visual, adjacent maintenance spine). Transit platform (The Loop stop, ~12×8 visual, bar-side) is classified as an encounter node, not a social site. District bounding box: 256×256 visual tiles (4×4 blocks per D-094). Three investigation paths confirmed: Path A (pattern recognition via camera logs/manifest), Path B (physical traversal via acoustic gap at restricted storage door seal → maintenance hatch), Path C (institutional, via Commission inspector relationship). Zone palette (surface hex / fog tint): gate cluster #b8bec4 / #0a1222; terminal #7a8490 / #0d1520; bar #6b4018 / #200c04; maintenance #4e5054 / #101214. Gate cluster zone spec (Araminta): aperture chamber 8×4 (restricted); freight staging 24×8 (private); passenger arrival 12×8 (semi-public); freight customs 20×10 (semi-private, 3–5 lanes at 2vt/lane); pedestrian customs 12×10 (semi-public, 3 lanes at 1vt); gate concourse 40×8 (public); observation gallery z=2 32×10 (Commission-only). Corridor widths: maintenance 2vt; internal building 3–4vt; secondary public 4vt; transition 6vt; gate concourse 8vt; service alcoves 1–2vt. Z-level scheme: z=0 maintenance corridor (Era 1, no Meridian), z=1 all main structures, z=2 gate cluster observation gallery only. Cross-z LOS: gallery rail = transparent low wall (player on z=2 sees z=1 below; upward LOS blocked except at staircase). Gate cluster social triangle: operations manager + senior freight handler + Commission inspector. Invisible infrastructure principle (G-08): every ring location reads as mundane; criminal function visible only to those who know. G-11: detective enters via gate cluster (Commission arrival); workers enter via transit platform (bar-side).
|
||||
- **Rationale:** Emerged from three-round workshop synthesis. Layout satisfies D-025 (social sites), D-036 (Sova Transit District), D-054 (tile movement), D-059 (fog zone palette), D-066 (dual-scale grid), D-011 (fog of perception), D-018 (sound model), D-027 (vertical slice criteria). Chunk/district hierarchy establishes architectural precedent for Q-036 generator.
|
||||
- **Raised by:** Full team — Gestalt (gameplay constraints), Miri (worldbuilding/lore), Araminta (visual/spatial), Tyre (technical), Paula (narrative), Ozzie (player experience). Compiled by Qatux.
|
||||
- **Dissent:** Araminta preferred 32×32 visual chunk size (overruled by lead and team majority). No other dissent.
|
||||
- **Source:** Station District Layout Workshop, Ticket #153, Sprint 20. Round document: `docs/discussions/round-20-station-district-layout.md`
|
||||
- **Cross-reference:** D-025 (social sites), D-036 (Sova setting), D-054 (tile movement), D-059 (fog system), D-066 (dual-scale grid), D-094 (district hierarchy), D-095 (transport lore), Q-036 (generator), Q-040–Q-044 (transport lore questions)
|
||||
|
||||
---
|
||||
|
||||
### D-095: Horizon Stations and Gate Infrastructure — Transport Lore
|
||||
- **Date:** 2026-02-25
|
||||
- **Decision:** Span gates are human-built structures with a single aperture enabling near-instantaneous transit. Operating schedule uses dual-use windows: freight (bulk of hours) and passenger (scheduled slots). Physical layout: aperture chamber → freight staging / passenger arrival → customs lanes → gate concourse. Horizon stations are alien-built installations (no identified builder species), self-maintaining, located at Oort-cloud distance, with 4–8 apertures per station. Per-system canonical name: "The Ring." Travel is sequential-hop only (A→B→C through intermediate systems; no direct long-range transit). Per-system access tiers vary (4 tiers — some systems allow single-hop to orbital customs; no direct planetary span gate). Station Sova's horizon gates are located at The Ring (Oort-cloud orbital); the Administrative Hub contains booking offices only (not the gates themselves — correction to prior station profile text). "The Loop" is Sova's internal tram network: 6 districts, 4-minute run from Residential Core to Transit District. Workers arrive at the transit platform (bar-side) and disperse to Terminal or bar.
|
||||
- **Rationale:** Resolves transport lore questions Q-040, Q-041, Q-043, Q-044 raised during Workshop #153. Miri's Round 3 contribution. Horizon station as alien-built infrastructure adds worldbuilding depth without requiring a named builder species. Sequential-hop travel creates natural story hooks (layover locations, transit records, smuggling route complexity).
|
||||
- **Raised by:** Miri (worldbuilding), confirmed by team.
|
||||
- **Dissent:** None.
|
||||
- **Source:** Station District Layout Workshop, Ticket #153, Sprint 20. Round document: `docs/discussions/round-20-station-district-layout.md`
|
||||
- **Cross-reference:** D-093 (gate cluster spatial layout), Q-040 (gate dual-use topology — resolved), Q-041 (horizon station model — resolved), Q-042 (intra-system transport — partially resolved), Q-043 (station internal transit — resolved), Q-044 (gate-train integration — resolved)
|
||||
|
||||
---
|
||||
|
||||
### D-098: TrianglePurpose Enum
|
||||
- **Date:** 2026-02-27
|
||||
- **Decision:** Social triangles carry `Vec<TrianglePurpose>` — a multi-tag set for playstyle accessibility. Enum values: `Investigation, Economic, Social, Political, Tactical, Mundane`. `Tactical` encodes the assassination contract in spatial form (target + protector + informant/witness). Multiple purpose tags per triangle: a smuggling operation can be `Economic + Tactical` simultaneously. Purpose tags ensure the right drama is surfaced to the player whose active lens matches — they add no generation cost, categorizing existing output.
|
||||
- **Rationale:** Purpose tags are the bridge between the generator's social structure and the player's active playstyle. The generator doesn't build for one playstyle — it tags what's already there.
|
||||
- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-3.
|
||||
- **Raised by:** Gestalt (triangle purpose model). Full team sign-off.
|
||||
- **Dissent:** None.
|
||||
- **Cross-reference:** D-025 (social site / functional cluster — triangles populate social sites), D-097 (guarantee tier system — triangles feed the audit)
|
||||
|
||||
### D-104: Heritage Grammar Overlay for Non-Urban Palettes
|
||||
- **Date:** 2026-02-27
|
||||
- **Decision:** Data-driven `HeritageGrammarOverlay` structs (10 per heritage root). Loaded once at generator startup, applied at Phase 2 chunk fill by weighted blending. Blend rules: continuous fields (decorative_density, repair_visibility, etc.) use weighted average; categorical fields (boundary_character, open_space_character) use dominant heritage weight; object tag lists use union of preferred/accent tags and intersection-exclusion of excluded tags. Phase 1 exception: `gathering_probability` evaluated at block planning for quarter pre-assignment. Authoring domain separation: **Miri** authors organizational principles, boundary character, spacing, social grammar (HeritageGrammarOverlay Rust struct / authored data). **Araminta** authors visual expression — object sets, arrangement algorithms, floor surface variants, overhead flora density and character, wall/structure material character, boundary material type, lighting temperature (TOML modifier files, one per heritage root). Shared: `ObjectTag` vocabulary must be co-maintained (see Q-049).
|
||||
- **Rationale:** Heritage roots must be spatially legible — the visual grammar of a Frost community versus a Tide community must be apparent to the observant player without a label.
|
||||
- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-9.
|
||||
- **Raised by:** Miri (cultural grammar spec), Araminta (TOML modifier design and authoring domain). Full team sign-off.
|
||||
- **Dissent:** None.
|
||||
- **Cross-reference:** D-101 (ZonePalette — heritage modifier axis A), D-105 (informal zone typology — heritage correlations), Q-049 (ObjectTag co-maintenance)
|
||||
|
||||
### D-105: Non-Urban Informal Zone Typology
|
||||
- **Date:** 2026-02-27
|
||||
- **Decision:** Informal zones (spaces outside the community's social field) are defined by the type of social permission governing them, not by institutional absence. Three types: `physical_distance` — sparse objects, unmaintained floor; isolation is the visual. `social_permission` — normal zone palette; gathering infrastructure present; cover is about convention, not geography. `utilitarian_cover` — functional work objects; space reads as work space; unofficial use invisible to casual observation. Heritage root correlations: Frost/Stone → `physical_distance`; Tide/Vine/Dust → `social_permission`; Iron/Salt → `utilitarian_cover`. (Dust = maximum communal observation, privacy is negotiated not physical; Iron = labor function covers presence.) Location within terrain seeded independently. Visual grammar per type: `docs/workshops/generator-architecture/araminta-round4.md`.
|
||||
- **Rationale:** Privacy mechanics emerge from community culture. How you hide in a Frost community (physical distance) is architecturally different from how you hide in a Dust community (social agreement). The typology makes privacy mechanics culturally legible.
|
||||
- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-10.
|
||||
- **Raised by:** Miri (heritage root correlations, canonical mapping corrected Round 5), Araminta (visual grammar per type). Full team sign-off.
|
||||
- **Dissent:** None.
|
||||
- **Cross-reference:** D-104 (heritage grammar overlay)
|
||||
|
||||
### D-107: Trauma Events as EraModification Subtypes
|
||||
- **Date:** 2026-02-27
|
||||
- **Decision:** Trauma events are a subtype of `EraModification` with dual-track effects. Five subtypes: `PhysicalDestruction, EconomicDisruption, PoliticalShock, ViolenceEvent, MigrationShock`. Separate tracks: (1) structural damage via `StructuralChange` in ChunkMutations (applied as `LocalOverlay` per D-109); (2) cultural response via NPC weight distribution shift in `DistrictRuntimeState.npc_pattern_weights`. Decay rate seeded per-community with variation around heritage-root baseline (`trauma_visual_decay_rate: slow | medium | fast`, default medium). Design principle: **Trauma intensifies culture, it does not transform it.** A stressed community becomes a more concentrated version of itself — Frost communities close harder, Tide communities grief more publicly, Iron communities organize more collectively. Decay is toward the community's pre-trauma baseline, not toward a new equilibrium. Players who have learned a heritage root's trust model can predict community behavior in the aftermath.
|
||||
- **Rationale:** Cultural response must be legible — and predictable to a player who has invested in understanding the heritage root. Trauma as amplifier (not transformer) rewards prior observation.
|
||||
- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-12.
|
||||
- **Raised by:** Miri (trauma subtypes and heritage decay model, "trauma intensifies culture" principle added Round 5), Tyre (struct design and decay architecture). Full team sign-off.
|
||||
- **Dissent:** None.
|
||||
- **Cross-reference:** D-100 (DamageOverlay — structural track), D-104 (heritage grammar — cultural baseline), D-109 (LocalOverlay mandate)
|
||||
|
||||
---
|
||||
|
||||
*25 decisions. Last updated: 2026-02-27 (D-098, D-104, D-105, D-107 added — Generator Architecture Workshop #562)*
|
||||
|
||||
+87
-1
@@ -242,6 +242,92 @@ Tracked questions awaiting discussion or resolution.
|
||||
- **Assigned to:** Tyre, Nigel
|
||||
- **Source:** Wiki Review Workshop R4
|
||||
|
||||
### Q-040: Gate dual-use topology — freight and commuter on shared span gate infrastructure
|
||||
- **Status:** Resolved → D-093 (gate cluster zone spec), D-095 (span gate dual-use windows)
|
||||
- **Question:** System span gates serve both freight and commuter traffic (one gate per system). How does this work physically? Is it one gate aperture with scheduling (freight window vs. passenger window), or parallel lanes (separate apertures for freight and passenger flows)? What does the gate facility look like from the inside — a single large bay or divided infrastructure?
|
||||
- **Layout implication:** Affects the gate cluster spatial design in the Transit District — the gate cluster must accommodate both freight staging and passenger throughflow, possibly at different times of day.
|
||||
- **Assigned to:** Miri
|
||||
- **Source:** Station District Layout Workshop (#153), Round 2. Surfaced by lead correction to Miri's S-02 (commuter transit ≠ second external gate).
|
||||
- **Cross-reference:** D-036 (Sova setting), Q-036 (district skeleton as generator output)
|
||||
|
||||
### Q-041: Interstellar travel mechanics — horizon stations and gate architecture
|
||||
- **Status:** Resolved → D-095 (horizon stations: alien-built, 4–8 apertures, Oort-cloud, "The Ring"; sequential hop travel)
|
||||
- **Question:** A system needs MORE than one horizon gate for multi-hop connectivity (one gate allows only 1:1 connections). Lead proposal (Round 3): **Horizon stations** — orbital installations at Oort-cloud distance, partially or wholly understood ancient alien technology, self-maintaining (Mass Effect relay/Citadel analog). Each horizon station holds a FIXED number of active and inactive horizon gates. Some systems may only have one hop to an orbital customs station with no direct planet-side span gate access. Remaining questions: How many gates per horizon station? What determines which gates are active vs. inactive? Is the travel instantaneous or traversal-based? What is "The Ring" (the orbital horizon station) like as a physical space?
|
||||
- **Assigned to:** Miri
|
||||
- **Source:** Station District Layout Workshop (#153), Round 2. Lead correction in Round 3: single-gate-per-system model insufficient for multi-hop travel; horizon station model proposed.
|
||||
- **Cross-reference:** D-036 (Sova setting), Q-039 (gate topology generation), Q-040 (gate dual-use topology)
|
||||
|
||||
### Q-042: Intra-system transport networks — passenger vs. freight, vehicles and modes
|
||||
- **Status:** Partially resolved → D-095 (span gates at star/planetary level; horizon stations at Oort distance). Intra-system hab-to-hab transit remains open.
|
||||
- **Question:** How do people and goods move within a star system (between orbital stations, planetary surfaces, and other in-system facilities)? Are there two separate networks (passenger transport and freight transport) or one shared network? What are the vehicle types and transit modes? How does intra-system transit interact with the span gate at the system's hub station?
|
||||
- **Assigned to:** Miri
|
||||
- **Source:** Station District Layout Workshop (#153), Round 2. Flagged by lead as transport lore requiring formal tracking.
|
||||
- **Cross-reference:** D-036 (Sova setting), Q-043 (station internal transit)
|
||||
|
||||
### Q-043: Station internal transit — intra-station transport system between districts
|
||||
- **Status:** Resolved → D-095 (The Loop: 6-district tram, 4-minute Residential Core → Transit District; transit platform is bar-side encounter node)
|
||||
- **Question:** What is the intra-station transport system on Station Sova? How do workers commute between districts (e.g., Residential Core → Transit District)? Is it a train, tram, shuttle, or pressurised corridor? What is the travel time and frequency? Where does the transit stop sit within the Transit District — gate-cluster-adjacent (workers arrive near freight operations) or bar-side-adjacent (workers arrive near their social space)?
|
||||
- **Layout implication for #153:** The Transit District must include an internal transit stop. Its position within the district affects NPC traffic patterns and the district entry topology. This is the active T-03b question for Round 2/3 of the Station District Layout Workshop.
|
||||
- **Assigned to:** Miri
|
||||
- **Source:** Station District Layout Workshop (#153), Round 2. Arose from lead correction: commuter transit = internal station transit, not a second external gate.
|
||||
- **Cross-reference:** D-036 (Sova setting), Q-042 (intra-system transport networks), S-02 revision
|
||||
|
||||
### Q-044: Gate-train integration — do transport vehicles use gates directly or transfer on each side
|
||||
- **Status:** Resolved → D-093/D-095 (gates are pedestrian/cargo-only; passengers transfer via gate concourse → transition corridor → transit platform; no direct gate-to-tram connection)
|
||||
- **Question:** If trains or shuttles are the intra-system or intra-station transit mode, do they use the span gate directly (a train enters the gate and exits at the destination, carriages and all)? Or are the gates pedestrian/cargo-only, requiring passengers and freight to transfer to separate transport on each side? What does this imply for gate terminal design — does it need platforms, or just processing space?
|
||||
- **Assigned to:** Miri
|
||||
- **Source:** Station District Layout Workshop (#153), Round 2. Flagged by lead as transport lore requiring formal tracking.
|
||||
- **Cross-reference:** Q-040 (gate dual-use topology), Q-043 (station internal transit)
|
||||
|
||||
---
|
||||
|
||||
*39 questions (7 resolved, 1 partially resolved, 31 open). Last updated: 2026-02-25 (Q-030 through Q-039 added — retroactive filings from Wiki Review Workshop and v0.1 Content Scoping Workshop)*
|
||||
### Q-045: Axis 11 — Network Footprint NPC tag
|
||||
- **Status:** Open
|
||||
- **Priority:** High
|
||||
- **Question:** Should the NPC model (D-024, 10 axes) gain an 11th axis: `network_footprint: Option<NetworkFootprintTag>` for NPCs who are locally insignificant in appearance but carry network-significant information or are relevant to external actors? Default `None` for procedural NPCs. Explicitly set for authored scenario NPCs. This would enable the storyteller to identify locally-invisible but network-critical nodes without breaking the NPC's mundane character.
|
||||
- **Context:** Raised during Generator Architecture Workshop (#562). The Ysabel Vorn litmus test (4.5/5 playstyle hooks, Backwater/Moderate setting) demonstrated that locally-insignificant NPCs can be key network nodes. Without this field, the generator has no mechanism to flag them to the storyteller.
|
||||
- **Source:** Generator Architecture Workshop (#562), Round 4. `docs/workshops/generator-architecture/workshop-outcomes.md` §NPC Model.
|
||||
- **Assigned to:** Miri
|
||||
|
||||
### Q-046: Departure schedule model — departure windows as generator output for docked vessels
|
||||
- **Status:** Resolved → D-108 (MobileChunk Specification)
|
||||
- **Resolution:** `scheduled_departure: Option<SimTick>` in `Docked` state is mandatory generator output. Vessels without departure schedules are an error state. The `Docked` struct must include `docked_since: SimTick` and `scheduled_departure: Option<SimTick>` — these fields must be added at implementation time (absent from Tyre's Round 4 canonical struct).
|
||||
- **Date resolved:** 2026-02-27
|
||||
- **Source:** Generator Architecture Workshop (#562)
|
||||
- **Assigned to:** Tyre + Miri
|
||||
|
||||
### Q-047: Mobile environment social arc — structural representation of journey timeline
|
||||
- **Status:** Open
|
||||
- **Priority:** Medium
|
||||
- **Question:** How is the social arc of a mobile environment journey (BoundedLinear / BoundedMobile) represented structurally? The journey has a beginning (boarding, strangers), middle (established dynamic), and end (departure, relationship crystallized). What game structures capture this timeline and enable the storyteller to intervene? Does `TransitSocialModifier` need a journey-phase field?
|
||||
- **Context:** Ozzie's player experience requirement from Generator Architecture Workshop (#562): "the journey must have a social arc — not just social presence." The stage+cast framing is correct; the formal structural representation is unspecified.
|
||||
- **Source:** Generator Architecture Workshop (#562), Round 4. `docs/workshops/generator-architecture/workshop-outcomes.md` §Open Questions.
|
||||
- **Assigned to:** Miri + Gestalt
|
||||
|
||||
### Q-048: DramaDensity enum naming — 3-level vs 5-level
|
||||
- **Status:** Open
|
||||
- **Priority:** Low
|
||||
- **Question:** The Round 4 struct uses `Quiescent / Active / Intense` (3 levels). Round 3 proposed `Zero / Low / Medium / High / Flashpoint` (5 levels). Which should be canonical? Nigel's position: `Flashpoint` should be preserved as a distinct peak value — it is the storyteller's maximum-pressure instrument and should not collapse into `Intense`. If 3 levels are chosen for implementation simplicity, `Flashpoint` should still be the distinct peak name, not `Intense`.
|
||||
- **Context:** ComplexityTier → DramaDensity ceiling (established): Full → any intensity; Moderate → Active max; Minimal → Quiescent max; Empty → Zero only. Naming must be consistent with these ceiling values.
|
||||
- **Source:** Generator Architecture Workshop (#562), Rounds 3–4. `docs/workshops/generator-architecture/workshop-outcomes.md` §Open Questions.
|
||||
- **Assigned to:** Tyre + Gestalt
|
||||
|
||||
### Q-049: ObjectTag vocabulary co-maintenance — Miri and Araminta shared dependency
|
||||
- **Status:** Open
|
||||
- **Priority:** Medium
|
||||
- **Question:** The `ObjectTag` vocabulary must be co-maintained between Miri's `HeritageGrammarOverlay` (cultural grammar, Rust struct) and Araminta's asset categorization (visual expression, TOML files). What is the governance model? Who owns the canonical tag list? How are additions and deprecations coordinated? Does the vocabulary live in the Rust struct definition or in a shared data file?
|
||||
- **Context:** If the vocabulary diverges, the generator will reference tags that don't exist in asset categories, or assets will be authored that the grammar never references. This is a silent correctness failure.
|
||||
- **Source:** Generator Architecture Workshop (#562), Round 4. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-9.
|
||||
- **Assigned to:** Miri + Araminta
|
||||
|
||||
### Q-050: Assassination difficulty synthesis — formal spec combining stored baseline with on-demand computation
|
||||
- **Status:** Open
|
||||
- **Priority:** Medium
|
||||
- **Question:** Formal specification needed for the synthesis combining stored cultural baseline (`DerivedDistrictAnalysis` on Phase 1 skeleton) with on-demand runtime computation for player-facing assessment. Key constraint from Miri: **on-demand computation is display-only** — all game logic (tactical triangle instantiation, guarantee audit) uses the Phase 1 `DerivedDistrictAnalysis` value. The on-demand computation is subordinate to the stored baseline, not a replacement.
|
||||
- **Context:** Minor tension between Gestalt's "computed entirely on demand" position and Miri's "stored cultural baseline" position. Synthesis accepted by both participants in Generator Architecture Workshop (#562); formal spec needed for implementation.
|
||||
- **Source:** Generator Architecture Workshop (#562), Round 4. `docs/workshops/generator-architecture/workshop-outcomes.md` §Open Questions.
|
||||
- **Assigned to:** Gestalt + Miri
|
||||
|
||||
---
|
||||
|
||||
*50 questions (12 resolved, 2 partially resolved, 36 open). Last updated: 2026-02-27 (Q-045 through Q-050 added — Generator Architecture Workshop #562; Q-046 resolved immediately by D-108)*
|
||||
|
||||
+7
-7
@@ -13,7 +13,7 @@ decisions/ Decision domain files (source of truth for all D/Q/R entri
|
||||
.config/ Configuration files (linters, formatters, CI)
|
||||
.cache/ Local caches for testing/linting (gitignored)
|
||||
docs/ Design, architecture, briefings, workshops
|
||||
db/ SQLite ticketing + decisions database and connectors
|
||||
db/ SQLite schema + seed data (connectors at tooling/db/)
|
||||
```
|
||||
|
||||
Unit tests live inside their respective projects (`server/` uses `#[cfg(test)]` inline + `tests/` directory per D-030). The top-level `tests/` directory is for integration tests that cross the client-server boundary (IPC round-trip, serialization fixtures, divergence tests).
|
||||
@@ -269,17 +269,17 @@ Key components:
|
||||
|
||||
Use wrapper scripts:
|
||||
```bash
|
||||
db/connectors/sqlite-query "SELECT * FROM tickets WHERE status='open'"
|
||||
db/connectors/sqlite-exec "UPDATE tickets SET status='done' WHERE id=1"
|
||||
tooling/db/sqlite-query "SELECT * FROM tickets WHERE status='open'"
|
||||
tooling/db/sqlite-exec "UPDATE tickets SET status='done' WHERE id=1"
|
||||
```
|
||||
|
||||
## Qdrant / Document Search
|
||||
|
||||
```bash
|
||||
db/connectors/qdrant-search "asymmetric information design"
|
||||
db/connectors/qdrant-index docs/briefings/tyre.md
|
||||
db/connectors/qdrant-health
|
||||
db/connectors/qdrant-count
|
||||
tooling/db/qdrant-search "asymmetric information design"
|
||||
tooling/db/qdrant-index docs/briefings/tyre.md
|
||||
tooling/db/qdrant-health
|
||||
tooling/db/qdrant-count
|
||||
```
|
||||
|
||||
## Decisions System
|
||||
|
||||
Binary file not shown.
@@ -626,7 +626,7 @@ In a world where everyone is hiding something, the one person who isn't becomes
|
||||
**End Pattern Specification.**
|
||||
|
||||
**Files referenced:**
|
||||
- `/var/home/jeroenschweitzer/Projects/settled-reach/copy/wiki/npcs/naia-tamm.md` — Reference implementation
|
||||
- `/var/home/jeroenschweitzer/Projects/settled-reach/copy/decisions/content.md` — D-024 (10-axis model), D-028 (dialogue architecture), D-029 (entanglement ratio), D-032 (separate monologue pools), D-034 (THE FRIEND pattern), D-035 (tag taxonomy)
|
||||
- `/var/home/jeroenschweitzer/Projects/settled-reach/copy/docs/workshops/v01-content-scoping/round2-gestalt.md` — Pattern definitions and NPC mapping
|
||||
- `/var/home/jeroenschweitzer/Projects/settled-reach/copy/docs/workshops/v01-gap-analysis/round2-gestalt.md` — Unified observation system, character identity integration
|
||||
- `/var/mnt/data/projects/settled-reach/copy/wiki/npcs/naia-tamm.md` — Reference implementation
|
||||
- `/var/mnt/data/projects/settled-reach/copy/decisions/content.md` — D-024 (10-axis model), D-028 (dialogue architecture), D-029 (entanglement ratio), D-032 (separate monologue pools), D-034 (THE FRIEND pattern), D-035 (tag taxonomy)
|
||||
- `/var/mnt/data/projects/settled-reach/copy/docs/workshops/v01-content-scoping/round2-gestalt.md` — Pattern definitions and NPC mapping
|
||||
- `/var/mnt/data/projects/settled-reach/copy/docs/workshops/v01-gap-analysis/round2-gestalt.md` — Unified observation system, character identity integration
|
||||
|
||||
@@ -144,7 +144,9 @@ NPCs may reference locations and entities beyond Sova Station. These are real bu
|
||||
|
||||
**Other Krenn System stations:** The Krenn System has two other smaller orbital facilities (mining support station and an administrative relay). They're referenced occasionally in news tickers and operational scheduling. Not relevant to v0.1.
|
||||
|
||||
**The horizon gate:** Sova Station has a connection to the Reach's horizon gate network — the interstellar transport infrastructure. The horizon gate terminal is in the Administrative Hub district, not the Transit District. Characters with legitimate need can book transit to other systems. This connection is what makes Sova relevant to a larger smuggling network; contraband doesn't originate in-system, it comes from elsewhere via horizon gate and moves through Sova's span gate to Velen.
|
||||
**The Krenn Ring (horizon station):** The Krenn System's interstellar connection is the Krenn Ring — a horizon station at approximately 800 AU from the Krenn star, accessible by system vessel (~4–6 days from Station Sova). Horizon stations are ancient orbital installations of unknown origin, self-maintaining, each containing multiple gate apertures connecting to other star systems. The Krenn Ring is not on Station Sova; it is a separate installation in the outer system.
|
||||
|
||||
**The Administrative Hub's interstellar transit facility:** Sova Station's Administrative Hub district houses the transit processing facility for interstellar travel — customs clearance, booking offices, and the shuttle dock for vessels heading to the Krenn Ring. When NPCs or documents refer to "the horizon gate terminal," they mean this processing facility, not a gate aperture on the station itself. Characters with legitimate need book transit here, then travel by shuttle to the Krenn Ring to board. This connection is what makes Sova relevant to a larger smuggling network; contraband doesn't originate in-system, it comes from elsewhere via horizon gate and moves through Sova's span gate to Velen.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
# Spatial Layout: Gate Cluster (Span Gate Processing Facility)
|
||||
|
||||
**Ticket:** #153
|
||||
**Date:** 2026-02-25
|
||||
**Author:** Araminta (Visual Designer)
|
||||
**Status:** v0.1 — wireframe quality, unblocks copy and gate cluster NPC authoring
|
||||
|
||||
**Grid:** 1 cell = 1m visual tile (visual grammar §2.1). Simulation operates at 0.5m; each visual tile = 2×2 sim tiles.
|
||||
**Map area:** 40m wide × 32m deep (40×32 visual tiles) + observation gallery on z=2
|
||||
**Zone palette:** Cool institutional grey — Era 3 construction, Commission-grade maintenance (visual grammar §1.1)
|
||||
|
||||
---
|
||||
|
||||
## Spatial Character
|
||||
|
||||
The gate cluster is the newest structure in the Transit District. Era 3 construction: clean sightlines, uniform LED-white overhead lighting, minimal accumulated grime. Commission-monitored and Commission-maintained. Where the Terminal reads as institutional but worn, and the Bar as warm and accumulated, the gate cluster reads as **administered**. The architecture communicates that someone is watching.
|
||||
|
||||
The building is a funnel. The span gate aperture (~15–20m diameter ring) determines the widest point; the passenger and freight flows narrow through processing stages; they emerge into the gate concourse, which opens outward as public space. The spatial logic is deliberate: volume at intake, compression through customs, expansion at public exit.
|
||||
|
||||
Dual-use scheduling is the spatial and operational premise (D-095): freight windows and passenger windows share the single aperture. The "flicker" (90-second mode transition between sequences) is legible to experienced travelers — different lighting cues on the aperture chamber walls mark which mode is active.
|
||||
|
||||
**G-11 entry note:** The detective arrives via this cluster from a Commission shuttle. Workers arrive via the transit platform on the bar side. These are structurally separated entry vectors. The gate cluster is the detective's first experience of the district.
|
||||
|
||||
---
|
||||
|
||||
## Floor Plan
|
||||
|
||||
### z=1 (Ground Floor)
|
||||
|
||||
```
|
||||
N (span gate aperture — external, connects to The Ring)
|
||||
|
|
||||
1111111111222222222233333333334444444
|
||||
1234567890123456789012345678901234567890
|
||||
|
||||
############[APERTURE RING]######### row 01 <- span gate ring (structural boundary)
|
||||
# . . . APERTURE CHAMBER . . . # row 02
|
||||
# . . . . . . . . . . . . . . # row 03 ACCESS: RESTRICTED
|
||||
# . . . . . . . . . . . . . . # row 04 (airlock/transition zone)
|
||||
########[D]##########[D]############ row 05 <- chamber exit doors (freight W, passenger E)
|
||||
|
||||
############################[D]###### row 06 <- freight staging north wall (east door = PAB)
|
||||
# FREIGHT STAGING # PAB # row 07
|
||||
# [FK][FK] [FK][FK] . # . # row 08 ACCESS: private (freight) / semi-public (PAB)
|
||||
# [FK][FK] [FK][FK] . # . # row 09 PAB = Passenger Arrival Buffer
|
||||
# [FK][FK] [FK][FK] . # . # row 10
|
||||
# . . . . . . . . [CT][CT] # . # row 11 <- CT = cargo transporter dock points
|
||||
# . . . . . . . . [CT][CT] # . # row 12 <- PAB merges south into customs at row 13
|
||||
#####[D]####################[D]###### row 13 <- into customs lanes
|
||||
|
||||
###################[D]############### row 14 <- freight customs north entry
|
||||
# FCL | FCL | FCL | FCL | FCL # row 15 ACCESS: semi-private (freight customs)
|
||||
# [TS] | [TS] | [TS] | [TS] | [TS] # row 16 FCL = freight customs lane (5 lanes × 4vt)
|
||||
# || | || | || | || | || # row 17 TS = terminal/scanner station per lane
|
||||
# || | || | || | || | || # row 18 || = cargo lane (4vt wide, column breaks Q4)
|
||||
# [P] | [P] | [P] | [P] | [P] # row 19 P = pillar/LOS anchor (4-tile interval)
|
||||
# . . .|. . . |. . . |. . . |. . . # row 20 <- inspection floor
|
||||
#######|#######[D]####[D]###|######## row 21 <- customs south wall; PCL entry
|
||||
# PCL PCL PCL PCL PCL # row 22 ACCESS: semi-public (pedestrian customs)
|
||||
# [TS] [TS] [TS] [TS] [TS] . . # row 23 PCL = pedestrian customs lanes (3 lanes × 2vt)
|
||||
# [P] . . [P] . . [P] . . # row 24 <- queue markers + pillar anchors
|
||||
# . . . . . . . . . . . . . . . # row 25
|
||||
# . . . . . . . . . . . . . . . # row 26
|
||||
############[D]####[D]############### row 27 <- customs south doors to concourse
|
||||
|
||||
#################################### row 28 <- concourse north wall
|
||||
# . . [B] [B] . . [NT][NT] # row 29 ACCESS: public
|
||||
# . . . . . . . . . . # row 30 B = bench, NT = news ticker
|
||||
# . . [B] [B] . . . . . # row 31
|
||||
# . . . . . . . . [D]SC # row 32 <- staircase (SC) east end; Commission entry
|
||||
#################################### row 33 <- concourse south wall (district entry facade)
|
||||
|
||||
|
|
||||
S (district interior — Terminal forecourt, transition corridor)
|
||||
```
|
||||
|
||||
**Legend:**
|
||||
```
|
||||
# Wall (solid, blocks LOS and movement)
|
||||
. Open walkable floor
|
||||
[D] Doorway (traversable)
|
||||
[APERTURE RING] Span gate ring structure (impassable during transit; open between sequences)
|
||||
FCL Freight customs lane
|
||||
PCL Pedestrian customs lane
|
||||
[TS] Terminal/scanner station (customs clerk workstation)
|
||||
[FK] Freight staging kiosk / forwarder terminal
|
||||
[CT] Cargo transporter dock point (loading/unloading position)
|
||||
[B] Bench (public seating)
|
||||
[NT] News ticker display (wall-mounted)
|
||||
[P] Structural pillar (LOS anchor, column break, gallery support above)
|
||||
SC Staircase to z=2 observation gallery (east end of concourse)
|
||||
PAB Passenger Arrival Buffer (east of freight staging, rows 06–12)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### z=2 (Observation Gallery) — Commission-only
|
||||
|
||||
```
|
||||
N
|
||||
|
|
||||
(above customs lanes — rows 14–27 below)
|
||||
1111111111222222222233333333334444
|
||||
1234567890123456789012345678901234567
|
||||
|
||||
[GALLERY NORTH RAIL — partial glass/grating]
|
||||
################################# <- gallery west and east walls
|
||||
# . . . . GALLERY FLOOR . . . # Commission-only: pristine near-white
|
||||
# [DK][DK] . . . [DK][DK] # DK = observation desk / surveillance kit
|
||||
# . . . . . . . . . . . . . . # floor: #d4d8dc
|
||||
# . . . . . . . . . . . . . . # walls: #e0e4e8
|
||||
# [DK][DK] . . . [DK][DK] #
|
||||
# . . . . . . . . . . . . . . #
|
||||
# . . . . . . . . . . . . . . #
|
||||
# . . . . . . . . . . . . . . #
|
||||
################################# <- gallery south rail (partial glass/grating)
|
||||
|
|
||||
[SC] staircase descends to z=1 concourse east end
|
||||
|
|
||||
S
|
||||
```
|
||||
|
||||
**Gallery dimensions:** 32m wide × 10m deep (32×10 visual tiles). Positioned above the customs lanes (z=1 rows 14–27) and NOT above the concourse or staging zones.
|
||||
|
||||
**Cross-z LOS:** Gallery rail is transparent low wall (glass or metal grating). Observer on z=2 has LOS downward to z=1 customs lanes. Upward LOS from z=1 is blocked except at the staircase opening. Players cannot see gallery occupants from the customs floor unless standing at the staircase.
|
||||
|
||||
**Gallery floor is the customs ceiling** — approximately 4m structural clearance below.
|
||||
|
||||
---
|
||||
|
||||
## Zone Breakdown
|
||||
|
||||
### Zone 1 — Aperture Chamber (rows 01–05)
|
||||
**Dimensions:** 40×4 visual tiles
|
||||
**Access tier:** Restricted (Commission control + gate authority; no public entry)
|
||||
**Purpose:** The transition space between the span gate aperture and the main processing facility. All passengers and freight pass through here immediately after emerging from the span gate. The aperture ring is the physical gate structure — when a transit sequence is active, the ring glows with transit residue (lighting cue for mode). Between sequences, the ring is dark and cold.
|
||||
**NPC traffic:** Gate authority staff (2–3 stationed here per sequence). Arrivals flow through continuously during an active sequence; zero traffic between sequences.
|
||||
**LOS notes:** The chamber is enclosed. No LOS to any other zone except the two exit doors (row 05). Gate authority staff can observe the full chamber volume. No LOS from the staging zones into the chamber.
|
||||
**Key feature:** The "flicker" — the 90-second mode transition between freight and passenger sequences — is physically visible here. Lighting shifts, personnel rotate, cargo equipment is cleared or staged. An observer in the gate concourse (south) can hear the mode change but not see it.
|
||||
|
||||
### Zone 2 — Freight Staging (rows 06–13, west 24 tiles)
|
||||
**Dimensions:** 24×8 visual tiles
|
||||
**Access tier:** Private (authorized freight operators and customs personnel only)
|
||||
**Purpose:** Where inbound freight is offloaded, registered, and staged for the customs inspection lanes. [FK] forwarder terminals are where freight agents log their manifest declarations before the cargo moves to the lanes. [CT] dock points are active during freight windows; they are dormant (low power, no staff) during passenger windows.
|
||||
**NPC traffic:** Busy during freight windows. The operations manager (Triangle NPC) is typically here during active freight sequences — their role is coordinating flow from aperture to customs. Senior freight handlers work the dock points. Sparse during passenger windows.
|
||||
**LOS notes:** Full LOS across the staging floor from the forwarder terminals. The freight customs entry door (row 13) is visible from the staging area. The PAB door (east) is visible but the PAB interior is not.
|
||||
**Key feature:** The operations manager's position here — with LOS to the aperture chamber exits, the staging floor, and the customs entry — is the spatial expression of their authority. They see everything that comes in.
|
||||
|
||||
### Zone 3 — Passenger Arrival Buffer (rows 06–12, east 12 tiles)
|
||||
**Dimensions:** 12×8 visual tiles
|
||||
**Access tier:** Semi-public (arriving passengers only; no unauthorized entry from district side)
|
||||
**Purpose:** Where passengers emerging from the span gate are held in a staging queue before processing through pedestrian customs. Separate from freight staging — the physical separation is the architectural enforcement of D-095's dual-use windows. During a freight window, the PAB is closed; during a passenger window, it fills.
|
||||
**NPC traffic:** Moderated by gate sequence. Full during a passenger window; empty between or during freight windows.
|
||||
**LOS notes:** LOS within the PAB is full. No direct LOS from the district concourse into the PAB — the customs lanes form a visual barrier. A player in the concourse sees only the south face of the customs lane structure.
|
||||
**Key feature:** The detective's entry experience begins here (G-11). Arriving via Commission shuttle during a passenger window, they queue briefly before being waved through customs (or escorted directly to the observation gallery — see staircase at z=1 east end).
|
||||
|
||||
### Zone 4 — Freight Customs Lanes (rows 14–21, west 20 tiles)
|
||||
**Dimensions:** 20×10 visual tiles (5 lanes × 4vt each, within a 20vt-wide zone, rows 14–21)
|
||||
**Access tier:** Semi-private (freight operators entering the district; customs clerk staff)
|
||||
**Purpose:** Processing incoming freight through customs inspection. Five lanes, each 4 visual tiles wide, each staffed by a customs clerk at a [TS] terminal station. [P] pillars at 4-tile intervals serve dual purpose: LOS anchors for the customs floor and structural supports for the observation gallery above.
|
||||
**NPC traffic:** Customs clerks (5, one per lane) are stationed here during freight windows. The Commission inspector (Triangle NPC) circulates among lanes — their social dynamic with the clerks is expressed spatially by where they position themselves during inspections. During passenger windows, lanes are closed (screens down, no staff).
|
||||
**LOS notes:** Clear LOS along each lane from north wall to south wall. The pillar breaks ([P]) interrupt cross-lane LOS at 4-tile intervals — an observer cannot see continuously across all 5 lanes. The gallery rail above (z=2) allows the Commission inspector to observe all lanes simultaneously from elevation. This is the critical asymmetry: floor-level observers have partial LOS; gallery observers have full LOS.
|
||||
**Observation note:** The social triangle's power dynamic is visible in sightlines. The Commission inspector from the gallery sees the customs clerks in their entirety, including which freight forwarders are waved through vs. searched. The floor-level operations manager sees individual lanes but not the full picture. The detective, arriving from the gallery, can observe the customs floor before descending.
|
||||
|
||||
### Zone 5 — Pedestrian Customs Lanes (rows 22–27, east 12 tiles)
|
||||
**Dimensions:** 12×10 visual tiles (3 lanes × 2vt each, with queue space to east, rows 22–27)
|
||||
**Access tier:** Semi-public (arriving passengers processing into the district)
|
||||
**Purpose:** Processing arriving passengers through customs. Three lanes, each 2 visual tiles wide, each with a [TS] scanner station. Queue space runs east of the lane structure. Simpler operation than freight customs — personal items scan, biometric check, manifest tag if applicable.
|
||||
**NPC traffic:** Active only during passenger windows. During freight windows, the customs clerks from PCL rotate to assist with FCL overflow. The Commission inspector may operate from PCL during passenger windows if intelligence suggests surveillance value.
|
||||
**LOS notes:** Narrower lanes mean LOS is more constrained. An observer in the queue can see only the lane directly ahead. From the gate concourse (south), the south face of the customs structure presents as a low partition — passengers emerging from customs are visible from the concourse immediately on exit.
|
||||
**Key feature:** This is where observable inequity happens. The Commission inspector (or a directive they issue) results in one class of traveler being waved through while another is searched. This is visible to anyone in the concourse queue area — including the detective. The spatial proximity of the PCL south wall to the concourse benches [B] means concourse passengers witness the processing of arrivals.
|
||||
|
||||
### Zone 6 — Gate Concourse (rows 28–33)
|
||||
**Dimensions:** 40×8 visual tiles (full building width)
|
||||
**Access tier:** Public (all district residents, workers, and new arrivals)
|
||||
**Purpose:** The public-facing terminus of the gate cluster. Benches [B] for waiting passengers, news ticker [NT] on the east wall for transit schedules and general news, and the primary facade opening to the district south. The staircase (SC) at the east end is the access point to the observation gallery — it is Commission-coded at the base (a discreet panel, not a visible barrier).
|
||||
**NPC traffic:** Variable. Busy when a passenger sequence has just completed (arrivals dispersing). Sparse during freight windows (only workers and officials). The concourse is the natural convergence zone for all district-side personnel who have business at the gate cluster.
|
||||
**LOS notes:** Full east-west LOS across the concourse. The [P] pillars from the customs lanes above do not extend to the concourse floor — the south edge of the customs structure is a visual wall at row 27. From the benches, observers can see the customs exit doors (row 27) and watch arrivals emerge. Cannot see into customs lanes from bench positions.
|
||||
**Key feature:** The social reading zone on arrival. New arrivals (including the detective on their first visit) experience the concourse before moving into the district. The news ticker is a topic generator. The Commission staircase (east end) is present but low-key — coded access does not broadcast itself in Commission-grade facilities.
|
||||
|
||||
### Zone 7 — Observation Gallery (z=2, above zones 4–5)
|
||||
**Dimensions:** 32×10 visual tiles (above the full customs lane section)
|
||||
**Access tier:** Commission-only (staircase coded at z=1 east end of concourse)
|
||||
**Purpose:** The gallery is where the Commission inspector works during active processing sequences. From here, all freight and pedestrian customs lanes are simultaneously observable. Observation desks [DK] with surveillance kit allow real-time customs monitoring, camera feed access, and communication with gate authority staff below. This is the institutional oversight position — the spatial embodiment of Commission authority over district entry.
|
||||
**NPC traffic:** The Commission inspector during work hours. Possibly a second Commission observer (junior) — but sparse. This is not a social space; it is a surveillance position.
|
||||
**LOS notes:** Full LOS down to all customs lanes (z=2 → z=1, through gallery rail). Partial LOS to freight staging (row 13 door visible from gallery north rail). NO LOS to aperture chamber (wall blocks), NO LOS to concourse (gallery south rail is opaque below rail height). Gallery interior has no LOS from the customs floor below — the cross-z asymmetry is deliberate and complete.
|
||||
**Key feature:** The detective's first introduction to this space is via escort through the staircase. The experience of descending from gallery (full picture) to concourse (partial picture) is the spatial tutorial for the information asymmetry theme.
|
||||
|
||||
---
|
||||
|
||||
## Key Observation Positions
|
||||
|
||||
| Position | Code | LOS coverage | Why it matters |
|
||||
|----------|------|-------------|----------------|
|
||||
| Observation gallery (z=2, center) | POS-G1 | All freight + pedestrian customs lanes simultaneously | Commission inspector's domain. Highest-information position in the gate cluster. Asymmetric — not visible from below. |
|
||||
| Freight staging floor (center) | POS-G2 | Aperture chamber exits, freight staging, customs entry door | Operations manager's natural position. Sees intake and output but not gallery or pedestrian lanes. |
|
||||
| Gate concourse benches (rows 29-30, west) | POS-G3 | Customs exit doors (row 27), staircase base (east), full concourse | Player's first investigation position. Passive observation of arrivals emerging from customs and who accesses the staircase. |
|
||||
| Pedestrian customs queue (row 22, east) | POS-G4 | PCL lanes, customs exit direction | Observer in queue can watch the customs clerks processing arrivals. Visible inequity in who is waved through vs. searched. |
|
||||
| Concourse east end (near staircase) | POS-G5 | Staircase access panel, anyone ascending/descending | Monitoring staircase access reveals Commission movement. Coded panel is discreet but observable. |
|
||||
| Gallery north rail (z=2) | POS-G6 | Freight staging floor through rail, customs lane north entries | Extended north-viewing position from gallery — tracks cargo from aperture exit to lane entry. |
|
||||
|
||||
---
|
||||
|
||||
## Sightline Analysis
|
||||
|
||||
```
|
||||
FROM → Aperture Frt.Stg. PAB Frt.Cust. Ped.Cust. Concourse Gallery
|
||||
TO ↓
|
||||
Aperture SELF via door via door NO NO NO NO
|
||||
Frt.Staging via door SELF via door via door NO NO NO
|
||||
PAB via door via door SELF NO NO NO NO
|
||||
Frt.Customs NO via door NO SELF NO via door rail(z2→z1)
|
||||
Ped.Customs NO NO NO NO SELF via door rail(z2→z1)
|
||||
Concourse NO NO NO via door via door SELF NO
|
||||
Gallery NO rail(N) NO rail(full) rail(full) NO SELF
|
||||
|
||||
rail(z2→z1) = LOS from gallery down through transparent rail/grating
|
||||
rail(N) = gallery north rail has partial LOS to freight staging floor
|
||||
NO = wall or z-gap blocks
|
||||
via door = LOS when door open
|
||||
```
|
||||
|
||||
**Critical sightline: Gallery → all customs lanes**
|
||||
The Commission inspector on z=2 has full LOS over every freight and pedestrian customs lane simultaneously. No position on the z=1 customs floor achieves equivalent coverage. This asymmetry is the spatial expression of institutional oversight.
|
||||
|
||||
**Critical sightline gap: Concourse → customs interior**
|
||||
The concourse benches are south of the customs structure. The customs south wall (rows 14–21 for freight, 22–27 for pedestrian) presents as a visual barrier. A player on the benches sees the customs exit doors and emerging arrivals — but not what happens inside the lanes. Investigation of customs behavior requires entering the lanes or reaching the gallery.
|
||||
|
||||
**Critical sightline gap: Gallery → concourse**
|
||||
The gallery south rail is opaque below the rail height. The Commission inspector cannot observe the concourse from the gallery without descending. The gallery is a surveillance position for entry processing, not for the public space.
|
||||
|
||||
---
|
||||
|
||||
## Access Tier Map
|
||||
|
||||
```
|
||||
RESTRICTED PRIVATE SEMI-PRIVATE SEMI-PUBLIC PUBLIC
|
||||
────────── ─────── ──────────── ─────────── ──────
|
||||
Aperture Freight staging Freight customs Ped. customs Concourse
|
||||
chamber (auth. operators) lanes lanes (all)
|
||||
(clerks + (arriving
|
||||
[Gallery z=2: freight ops) passengers)
|
||||
Commission-only]
|
||||
```
|
||||
|
||||
Sequential access enforcement (H-06): A freight operator moving from aperture to district must pass through freight staging → freight customs → concourse. No spatial path skips a tier. Pedestrian arrivals pass through PAB → pedestrian customs → concourse. The two flows are physically separated (west half vs. east half of the building) and join only at the concourse.
|
||||
|
||||
---
|
||||
|
||||
## NPC Traffic Density Annotations
|
||||
|
||||
| Time | Aperture | Frt. Staging | PAB | Frt. Customs | Ped. Customs | Concourse | Gallery |
|
||||
|------|----------|-------------|-----|-------------|-------------|-----------|---------|
|
||||
| Dawn (05-07) | very sparse | very sparse | closed | closed | closed | very sparse | — |
|
||||
| Freight window 1 (07-12) | busy (freight) | busy | closed | busy | closed | moderate | inspector |
|
||||
| Passenger window (12-14) | moderate (pax) | sparse | moderate | closed | moderate | busy | inspector |
|
||||
| Freight window 2 (14-19) | busy (freight) | busy | closed | busy | closed | moderate | inspector |
|
||||
| Passenger window (19-20) | moderate (pax) | sparse | moderate | closed | moderate | busy | inspector |
|
||||
| Evening sparse (20-23) | sparse | sparse | closed | sparse | closed | sparse | varies |
|
||||
| Night (23-05) | very sparse | very sparse | closed | closed | closed | very sparse | — |
|
||||
|
||||
**Flicker windows:** 90-second transition between freight and passenger modes. During this window:
|
||||
- Aperture chamber resets (personnel exchange, lighting shifts, cargo equipment cleared or staged)
|
||||
- All customs lanes briefly closed
|
||||
- Concourse becomes transiently busier as travelers waiting for mode completion gather
|
||||
- The operations manager is most exposed — coordinating the reset, moving between staging and customs entry
|
||||
|
||||
**Detective entry:** Commission shuttles arrive during passenger windows as a matter of protocol. First contact with the district begins in the aperture chamber, proceeds to the PAB, and typically diverts to the gallery staircase before customs processing is required.
|
||||
|
||||
---
|
||||
|
||||
## Narrative Triangle Service Notes
|
||||
|
||||
### Triangle 5 — Gate Authority (Operations Manager – Senior Freight Handler – Commission Inspector)
|
||||
|
||||
This is an institutional-oversight triangle, structurally different from the Terminal's knowledge-and-leverage triangles (1–2) and the Bar's social-loyalty triangles (3–4).
|
||||
|
||||
- **Operations manager:** Their domain is the freight flow — aperture to staging to customs. They have private-tier access everywhere on the z=1 floor. They are measured by throughput: how much cargo clears customs in a window. They have an accommodation relationship with certain freight forwarders (see customs inequity below).
|
||||
- **Senior freight handler:** The forwarder who benefits from that accommodation. They know what they get, they know why, and they know the operations manager knows they know. This is the stable complicity leg of the triangle.
|
||||
- **Commission inspector:** Their domain is the gallery. They watch the customs floor from above. They may know about the accommodation, or may be about to discover it, or may be using it as leverage already. Their relationship to the operations manager is formally collaborative, actually adversarial.
|
||||
|
||||
**Spatial expression:** The operations manager never goes to the gallery. The Commission inspector rarely comes to the floor. The senior freight handler is on the floor. The triangle's tension is mediated by the cross-z sightline — the inspector can see the forwarder being waved through, and the operations manager knows the inspector is watching, but neither will acknowledge it in the same zone at the same time.
|
||||
|
||||
**Observable inequity (investigation entry point):** A player watching from the concourse benches (POS-G3) or from the pedestrian customs queue (POS-G4) can observe a freight forwarder being waved through freight customs without search while a commuter on the pedestrian side receives a full scan. This is not dramatic — it reads as normal. The player has to make the connection: waved through = known cargo = manifested incorrectly = this is where the lattice components enter.
|
||||
|
||||
**Tension staging locations:**
|
||||
- Freight staging floor (operations manager's ground; conversations here are authority-neutral)
|
||||
- Gallery (inspector's ground; a summons to the gallery is pressure)
|
||||
- Customs lane (the observable action space; what clerks actually do is determined by unspoken directives from above)
|
||||
- Concourse east end near staircase (the transition space; anyone ascending the staircase must pass anyone watching the staircase)
|
||||
|
||||
---
|
||||
|
||||
## Z-Level Notes
|
||||
|
||||
**z=0:** Not present in the gate cluster. Maintenance access to the gate cluster, if any, is via the district maintenance spine (z=0 elsewhere in district) and does not extend into the gate cluster interior. Era 3 construction has no maintenance corridor integration — maintenance occurs from above via service panels.
|
||||
|
||||
**z=1:** All gate cluster zones: aperture chamber, freight staging, PAB, freight customs, pedestrian customs, concourse. All NPCs and player movement on this level.
|
||||
|
||||
**z=2:** Observation gallery only. Staircase is the sole connection point (z=1 concourse east end ↔ z=2 gallery). Commission-coded access panel at base of staircase is present but low-profile (no visible lock, no visible panel labeling in Era 3 style — access is granted by insertion of Commission neural-tag proximity, not a key).
|
||||
|
||||
**Cross-z visibility rules:**
|
||||
- Gallery → customs lanes: full downward LOS through transparent rail/grating
|
||||
- Customs lanes → gallery: no upward LOS (gallery floor solid except rail; rail height above standing head height)
|
||||
- Staircase opening: local LOS only (see who is at the staircase base or top, not into gallery interior)
|
||||
|
||||
---
|
||||
|
||||
## Notes for Copy Team
|
||||
|
||||
1. **Aperture chamber is the in-world ritual.** Arriving via span gate is Commonwealth-mundane but the aperture ring has residual energy effects — ambient hum, slight color temperature shift as light normalizes from transit. Monologue lines for the detective's arrival should note this. Standard sensory detail for immersive-world arrivals.
|
||||
2. **The customs inequity is not dramatic.** When the senior freight handler is waved through, it should read as routine from NPC behavior — a nod, a scan confirmed, the lane opens. Overheard dialogue, if any, should be procedural: manifest check language, not conversational. The player learns that something is wrong from the pattern, not from a flagrant scene.
|
||||
3. **The Commission inspector's gallery is their professional comfort zone.** Dialogue set in the gallery (if the detective accesses it) should reflect this — the inspector is at ease up here, slightly less guarded. On the floor, they are performing authority. In the gallery, they are just watching.
|
||||
4. **The operations manager on the freight staging floor.** This is their element. Logistics language, shorthand with the senior freight handler. Any casual conversation with the detective here is the operations manager on home turf — helpful enough, not forthcoming.
|
||||
5. **The flicker is an ambient event.** Travelers who know the schedule stop and wait. Travelers who don't know find themselves in a 90-second limbo — nothing is moving, customs is closed. The concourse fills briefly. Use this as a social compression beat: forced proximity, idle waiting, overheard conversations that wouldn't happen mid-flow.
|
||||
6. **The staircase is visible, not obvious.** In Era 3 design language, it is clean, architectural, slightly more refined than the surrounding fittings. It doesn't broadcast Commission. Players who are paying attention will notice it; players who aren't will miss it. Second visit = "wait, I didn't see that last time."
|
||||
@@ -0,0 +1,235 @@
|
||||
# District Topology — Sova Transit District
|
||||
# D-093 spatial layout. D-094 hierarchy: 256×256 vt (4×4 blocks).
|
||||
# North = span gate entry. South-east = tram entry.
|
||||
|
||||
direction: down
|
||||
|
||||
vars: {
|
||||
bg: "#1a1e24"
|
||||
txt: "#c8d0e0"
|
||||
acc: "#c8d8f0"
|
||||
|
||||
pub: "#1a3320"
|
||||
spub: "#2e2a10"
|
||||
spriv: "#2e1a08"
|
||||
priv: "#2a0e0e"
|
||||
comm: "#0e1a2e"
|
||||
neut: "#1e2228"
|
||||
|
||||
s-pub: "#3a8a50"
|
||||
s-spub: "#b8a020"
|
||||
s-spriv: "#c86010"
|
||||
s-priv: "#c02020"
|
||||
s-comm: "#3060c0"
|
||||
s-neut: "#4a5060"
|
||||
}
|
||||
|
||||
# ── LEGEND ──
|
||||
|
||||
legend: Legend {
|
||||
style.fill: ${bg}
|
||||
style.stroke: ${acc}
|
||||
style.font-color: ${txt}
|
||||
style.font-size: 11
|
||||
direction: right
|
||||
|
||||
l1: Public { style.fill: ${pub}; style.stroke: ${s-pub}; style.font-color: ${txt} }
|
||||
l2: Semi-pub { style.fill: ${spub}; style.stroke: ${s-spub}; style.font-color: ${txt} }
|
||||
l3: Semi-priv { style.fill: ${spriv}; style.stroke: ${s-spriv}; style.font-color: ${txt} }
|
||||
l4: Private { style.fill: ${priv}; style.stroke: ${s-priv}; style.font-color: ${txt} }
|
||||
l5: Commission { style.fill: ${comm}; style.stroke: ${s-comm}; style.font-color: ${txt} }
|
||||
}
|
||||
|
||||
# ── NORTH ENTRY: SPAN GATE ──
|
||||
|
||||
span_gate: The Ring\n(span gate aperture) {
|
||||
shape: hexagon
|
||||
style.fill: ${comm}
|
||||
style.stroke: ${s-comm}
|
||||
style.font-color: ${txt}
|
||||
}
|
||||
|
||||
# ── GATE CLUSTER ──
|
||||
|
||||
gate: Gate Cluster · 40×32 vt {
|
||||
style.fill: ${bg}
|
||||
style.stroke: ${s-comm}
|
||||
style.font-color: ${txt}
|
||||
style.border-radius: 4
|
||||
|
||||
aperture: Aperture\n8×4 {
|
||||
style.fill: ${priv}; style.stroke: ${s-priv}; style.font-color: ${txt}
|
||||
}
|
||||
staging: Freight Staging\n24×8 {
|
||||
style.fill: ${priv}; style.stroke: ${s-priv}; style.font-color: ${txt}
|
||||
}
|
||||
pab: Passenger Arrival\n12×8 {
|
||||
style.fill: ${spub}; style.stroke: ${s-spub}; style.font-color: ${txt}
|
||||
}
|
||||
customs: Customs Lanes\n(freight 5×4vt + ped 3×2vt) {
|
||||
style.fill: ${spriv}; style.stroke: ${s-spriv}; style.font-color: ${txt}
|
||||
}
|
||||
concourse: Concourse\n40×8 · PUBLIC {
|
||||
style.fill: ${pub}; style.stroke: ${s-pub}; style.font-color: ${txt}
|
||||
}
|
||||
gallery: Gallery · z=2\n32×10 · COMMISSION {
|
||||
style.fill: ${comm}; style.stroke: ${s-comm}; style.font-color: ${txt}
|
||||
}
|
||||
|
||||
aperture -> staging: freight { style.stroke: ${s-priv} }
|
||||
aperture -> pab: passenger { style.stroke: ${s-spub} }
|
||||
staging -> customs { style.stroke: ${s-spriv} }
|
||||
pab -> customs { style.stroke: ${s-spub} }
|
||||
customs -> concourse { style.stroke: ${s-pub} }
|
||||
concourse -> gallery: "staircase (Commission)" {
|
||||
style.stroke: ${s-comm}; style.stroke-dash: 4
|
||||
}
|
||||
gallery -> customs: "LOS z=2 down" {
|
||||
style.stroke: ${s-comm}; style.stroke-dash: 4
|
||||
}
|
||||
}
|
||||
|
||||
span_gate -> gate.aperture: "dual-use transit\n(90s flicker)" {
|
||||
style.stroke: ${s-comm}
|
||||
}
|
||||
|
||||
# ── TERMINAL ──
|
||||
|
||||
terminal: Terminal · 44×28 vt {
|
||||
style.fill: ${bg}
|
||||
style.stroke: ${s-spriv}
|
||||
style.font-color: ${txt}
|
||||
style.border-radius: 4
|
||||
|
||||
forecourt: Forecourt\n44×4 {
|
||||
style.fill: ${spub}; style.stroke: ${s-spub}; style.font-color: ${txt}
|
||||
}
|
||||
cargo: Cargo Floor + Main Corridor {
|
||||
style.fill: ${spriv}; style.stroke: ${s-spriv}; style.font-color: ${txt}
|
||||
}
|
||||
storage: Restricted Storage\n(single coded door) {
|
||||
style.fill: ${priv}; style.stroke: ${s-priv}; style.font-color: ${txt}
|
||||
}
|
||||
hatch_t: M-HATCH-T {
|
||||
shape: diamond
|
||||
style.fill: ${priv}; style.stroke: ${s-priv}; style.font-color: ${txt}
|
||||
}
|
||||
|
||||
forecourt -> cargo { style.stroke: ${s-spriv} }
|
||||
cargo -> storage: "coded door" { style.stroke: ${s-priv} }
|
||||
storage -> hatch_t { style.stroke: ${s-priv} }
|
||||
}
|
||||
|
||||
gate.concourse -> terminal.forecourt: "district spine (south)" {
|
||||
style.stroke: ${s-pub}
|
||||
}
|
||||
|
||||
# ── TRANSITION CORRIDOR ──
|
||||
|
||||
corridor: Transition Corridor\n~40×6 vt {
|
||||
style.fill: ${spub}
|
||||
style.stroke: ${s-spub}
|
||||
style.font-color: ${txt}
|
||||
style.border-radius: 4
|
||||
}
|
||||
|
||||
terminal.cargo -> corridor: "public route" {
|
||||
style.stroke: ${s-spub}
|
||||
}
|
||||
|
||||
# ── BAR ──
|
||||
|
||||
bar: The Last Shift · 28×22 vt {
|
||||
style.fill: ${bg}
|
||||
style.stroke: ${s-pub}
|
||||
style.font-color: ${txt}
|
||||
style.border-radius: 4
|
||||
|
||||
approach: Bar Approach\n28×3 {
|
||||
style.fill: ${spub}; style.stroke: ${s-spub}; style.font-color: ${txt}
|
||||
}
|
||||
floor: Main Floor\n(corner booth · card table) {
|
||||
style.fill: ${pub}; style.stroke: ${s-pub}; style.font-color: ${txt}
|
||||
}
|
||||
bathroom: Bathroom Corridor\n(east ext.) {
|
||||
style.fill: ${spriv}; style.stroke: ${s-spriv}; style.font-color: ${txt}
|
||||
}
|
||||
backroom: Back Room\n(Lera) {
|
||||
style.fill: ${priv}; style.stroke: ${s-priv}; style.font-color: ${txt}
|
||||
}
|
||||
hatch_b: M-HATCH-B {
|
||||
shape: diamond
|
||||
style.fill: ${priv}; style.stroke: ${s-priv}; style.font-color: ${txt}
|
||||
}
|
||||
|
||||
approach -> floor { style.stroke: ${s-pub} }
|
||||
floor -> bathroom: "east door" { style.stroke: ${s-spriv} }
|
||||
floor -> backroom: "staff only" { style.stroke: ${s-priv} }
|
||||
bathroom -> hatch_b { style.stroke: ${s-priv} }
|
||||
}
|
||||
|
||||
corridor -> bar.approach: "bar-side decompression" {
|
||||
style.stroke: ${s-spub}
|
||||
}
|
||||
|
||||
# ── MAINTENANCE CORRIDOR (z=0) ──
|
||||
|
||||
maint: Maintenance Corridor\n(z=0 · Era 1 · 2vt wide)\nzero public traffic {
|
||||
style.fill: ${priv}
|
||||
style.stroke: ${s-priv}
|
||||
style.font-color: ${txt}
|
||||
style.border-radius: 4
|
||||
style.stroke-dash: 5
|
||||
}
|
||||
|
||||
junc: JUNC-1 {
|
||||
shape: diamond
|
||||
style.fill: ${priv}; style.stroke: ${s-priv}; style.font-color: ${txt}
|
||||
}
|
||||
|
||||
terminal.hatch_t -> maint: "z=1 down z=0" {
|
||||
style.stroke: ${s-priv}; style.stroke-dash: 5
|
||||
}
|
||||
maint -> junc { style.stroke: ${s-priv}; style.stroke-dash: 5 }
|
||||
junc -> bar.hatch_b: "z=0 up z=1" {
|
||||
style.stroke: ${s-priv}; style.stroke-dash: 5
|
||||
}
|
||||
|
||||
# ── SOUTH-EAST ENTRY: TRAM ──
|
||||
|
||||
the_loop: The Loop\n(station tram · 6 districts) {
|
||||
shape: hexagon
|
||||
style.fill: ${neut}
|
||||
style.stroke: ${s-neut}
|
||||
style.font-color: ${txt}
|
||||
}
|
||||
|
||||
platform: Transit Platform\n~12×8 vt · bar-side\n(encounter node) {
|
||||
style.fill: ${pub}
|
||||
style.stroke: ${s-pub}
|
||||
style.font-color: ${txt}
|
||||
style.border-radius: 4
|
||||
}
|
||||
|
||||
the_loop -> platform: "workers arrive here (G-11)" {
|
||||
style.stroke: ${s-neut}
|
||||
}
|
||||
platform -> bar.approach: "adjacent" {
|
||||
style.stroke: ${s-pub}
|
||||
}
|
||||
|
||||
# ── SECTOR 3 ──
|
||||
|
||||
sector3: Sector 3 Residential\n~15×12 vt · Drin/Naia {
|
||||
style.fill: ${spub}
|
||||
style.stroke: ${s-spub}
|
||||
style.font-color: ${txt}
|
||||
style.border-radius: 4
|
||||
}
|
||||
|
||||
sector3 -> terminal.forecourt: "near Terminal" {
|
||||
style.stroke: ${s-spub}; style.stroke-dash: 3
|
||||
}
|
||||
sector3 -> maint: "adjacent to spine" {
|
||||
style.stroke: ${s-neut}; style.stroke-dash: 3
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1006 KiB |
@@ -23,3 +23,4 @@ Historical discussion rounds from the Commonwealth game design process.
|
||||
| 17 | Content Architecture | D-023, D-024, D-025, D-026, D-027, D-028, D-029 | [round-17](round-17-content-architecture.md) |
|
||||
| 18 | v0.1 Gap Analysis Workshop | D-030, D-031, D-032, D-033, D-034, D-035, D-036, D-037, D-038, D-039, D-040 | [round-18](round-18-v01-gap-analysis-workshop.md) |
|
||||
| 19 | Knowledge Graph & Information Boundaries Workshop | D-041, Q-016 resolved, Q-019 partially resolved, Q-024, Q-025, Q-026 | [workshop brief](../workshops/knowledge-graph-information-boundaries/workshop-brief.md), [synthesis](../workshops/knowledge-graph-information-boundaries/round2-synthesis.md) |
|
||||
| 20 | Station District Layout Design (Workshop #153) | D-093, D-094, D-095; Q-040–Q-044 resolved/partially resolved | [round-20](round-20-station-district-layout.md) |
|
||||
|
||||
@@ -791,7 +791,7 @@ This path reduces risk by deferring sync tooling until domain split is validated
|
||||
**End of analysis.**
|
||||
|
||||
**File locations:**
|
||||
- Analysis: `/var/home/jeroenschweitzer/Projects/commonwealth/docs/discussions/decisions-restructure-si.md`
|
||||
- Current monolith: `/var/home/jeroenschweitzer/Projects/commonwealth/DECISIONS.md` (474 lines)
|
||||
- Current schema: `/var/home/jeroenschweitzer/Projects/commonwealth/db/schema.sql`
|
||||
- Analysis: `/var/mnt/data/projects/commonwealth/docs/discussions/decisions-restructure-si.md`
|
||||
- Current monolith: `/var/mnt/data/projects/commonwealth/DECISIONS.md` (474 lines)
|
||||
- Current schema: `/var/mnt/data/projects/commonwealth/db/schema.sql`
|
||||
- Current tickets: 273 total (24 initiatives, 33 epics, 200 stories, 15 tasks, 1 bug)
|
||||
|
||||
@@ -461,12 +461,12 @@ Assign me the sync script, schema additions, and Makefile targets. I can have Ph
|
||||
---
|
||||
|
||||
**File locations referenced:**
|
||||
- This review: `/var/home/jeroenschweitzer/Projects/commonwealth/docs/discussions/decisions-restructure-tyre.md`
|
||||
- Si's analysis: `/var/home/jeroenschweitzer/Projects/commonwealth/docs/discussions/decisions-restructure-si.md`
|
||||
- Qatux's analysis: `/var/home/jeroenschweitzer/Projects/commonwealth/docs/discussions/decisions-restructure-qatux.md`
|
||||
- Current schema: `/var/home/jeroenschweitzer/Projects/commonwealth/db/schema.sql`
|
||||
- Current decisions: `/var/home/jeroenschweitzer/Projects/commonwealth/DECISIONS.md`
|
||||
- Makefile: `/var/home/jeroenschweitzer/Projects/commonwealth/Makefile`
|
||||
- SQLite connector: `/var/home/jeroenschweitzer/Projects/commonwealth/db/connectors/sqlite_connector.py`
|
||||
- This review: `/var/mnt/data/projects/commonwealth/docs/discussions/decisions-restructure-tyre.md`
|
||||
- Si's analysis: `/var/mnt/data/projects/commonwealth/docs/discussions/decisions-restructure-si.md`
|
||||
- Qatux's analysis: `/var/mnt/data/projects/commonwealth/docs/discussions/decisions-restructure-qatux.md`
|
||||
- Current schema: `/var/mnt/data/projects/commonwealth/db/schema.sql`
|
||||
- Current decisions: `/var/mnt/data/projects/commonwealth/DECISIONS.md`
|
||||
- Makefile: `/var/mnt/data/projects/commonwealth/Makefile`
|
||||
- SQLite connector: `/var/mnt/data/projects/commonwealth/db/connectors/sqlite_connector.py`
|
||||
|
||||
**End of technical review.**
|
||||
|
||||
@@ -0,0 +1,663 @@
|
||||
# Round 20: Station District Layout Design — Ticket #153
|
||||
|
||||
**Sprint:** 20 (Shape)
|
||||
**Date:** 2026-02-25
|
||||
**Ticket:** #153 — Station district layout design
|
||||
**Participants:** Gestalt, Miri, Araminta, Tyre, Paula, Ozzie, Qatux (documenter)
|
||||
**Output target:** D-record in `decisions/content.md` or `decisions/architecture.md`
|
||||
**Blocks:** #155, #188
|
||||
|
||||
---
|
||||
|
||||
## Internal Round Structure
|
||||
|
||||
| Internal Round | Topic | Status |
|
||||
|----------------|-------|--------|
|
||||
| Round 1 | Inventory and constraints | Complete — see §1 below |
|
||||
| Round 2 | Topology proposals | Pending |
|
||||
| Round 3 | Convergence and D-record draft | Pending |
|
||||
|
||||
---
|
||||
|
||||
## §1 — ROUND 1: Constraints Summary
|
||||
|
||||
*Compiled by Qatux. Derived from 6 agent contributions: Ozzie, Gestalt, Miri, Tyre, Araminta, Paula.*
|
||||
|
||||
**Lead note (pre-round):** The spatial patterns decided here establish the district template that Q-036's generator will eventually use. Decisions here become architectural precedent — not just v0.1 configuration.
|
||||
|
||||
**Qatux framing:** Constraints below are tagged:
|
||||
- `[v0.1]` — specific to the hand-authored Sova Transit District
|
||||
- `[template]` — generalisable to any future generated district of this type
|
||||
- `[both]` — applies at both levels
|
||||
|
||||
Cross-reference: Q-036 (district skeleton as generator output) tracks where template decisions need formal specification.
|
||||
|
||||
---
|
||||
|
||||
### 1. Hard Constraints
|
||||
*Non-negotiable: confirmed decisions, confirmed technical facts, Paula's structural requirements that block narrative arcs if violated.*
|
||||
|
||||
| # | Constraint | Source | Tag | Cross-ref |
|
||||
|---|------------|--------|-----|-----------|
|
||||
| H-01 | Dual-scale grid: 0.5m sim tiles, 1m visual tiles. All tile counts in this document are **visual tiles** unless noted. | D-066 | both | D-066 |
|
||||
| H-02 | Tile-based movement. All spatial reasoning is discrete. Corridors must be ≥1 visual tile wide; functional spaces ≥2 tiles wide. | D-054 | both | D-054 |
|
||||
| H-03 | Fog zone temperature tint is already decided: Terminal = cool dark, Bar = warm dark, corridors = neutral dark. The gate cluster adds a fourth zone requiring a tint assignment. | D-059 | v0.1 | D-059 |
|
||||
| H-04 | Local map budget: ~150×150 tiles. Tyre confirms current layouts use ≤10% of this budget. Performance is not a binding constraint at current scope. | D-014, Tyre | both | D-014 |
|
||||
| H-05 | Social sites must be connected spaces of 15–40 tiles. | D-025 | template | D-025 |
|
||||
| H-06 | Access tiers must be traversed **sequentially** — public → semi-public → restricted. No spatial path may allow skipping a tier. | Gestalt | template | D-025 |
|
||||
| H-07 | Restricted storage has exactly **one** entrance (the coded door from the cargo floor). No second entrance, no back exit. | Paula | v0.1 | #311 |
|
||||
| H-08 | Maintenance corridor has **zero LOS** from all other spaces. Its interior is not visible from any public or semi-public zone. Detection requires: sound propagation (footsteps on grating) or witness at hatch entry/exit points only. | Paula, smuggling layout | v0.1 | #313 |
|
||||
| H-09 | No third route between Terminal and Bar. Exactly two routes exist: (a) the public transition corridor and (b) the maintenance corridor (ring-only, private). A third route would dissolve the ring's movement asymmetry. | Paula | v0.1 | #313 |
|
||||
| H-10 | Bathroom corridor interior has **zero LOS** from the bar floor. Observer at bar can see only the door, not the interior. | Paula, bar layout | v0.1 | #312 |
|
||||
| H-11 | No private path between manifest processing and supervisor's office. Maret's crossing of the main corridor is a public act with narrative cost — visibility is the spatial mechanism. | Paula, terminal layout | v0.1 | #311 |
|
||||
| H-12 | Chunk size decision required. Tyre recommends **32×32 sim tiles** (= 16×16 visual tiles) as the generation unit. Zone and access-tier boundaries should align to chunk edges where possible. | Tyre | template | D-073, Q-036 |
|
||||
| H-13 | Z-level allocation decision required. Tyre recommends maintenance corridor on **z=0**, bar/terminal structures on **z=1**. No additional z-levels without gameplay justification. | Tyre, Araminta | both | D-049 |
|
||||
|
||||
---
|
||||
|
||||
### 2. Setting Constraints
|
||||
*What the Sova station profile and worldbuilding require.*
|
||||
|
||||
| # | Constraint | Source | Tag |
|
||||
|---|------------|--------|-----|
|
||||
| S-01 | Span gate at Terminal's **north face**. Freight enters from north (inbound cargo from span gate); maintenance exits south. This is directional — north = external transit, south = district interior. | Miri, terminal layout | v0.1 |
|
||||
| S-02 | **Two separate entry vectors** into the district: (a) freight span gate and (b) commuter transit connection. Passengers and freight do not share the same entry point. | Miri | template |
|
||||
| S-03 | Sealed hull-section boundary. The district has a **finite, countable number of choke-point exits** — not open-ended. The player can know all exits. | Miri | template |
|
||||
| S-04 | Sector 3 is a named sub-area of the district with an unresolved ventilation issue. Must be referenced spatially — it has a location, even if not a full social site. | Miri | v0.1 |
|
||||
| S-05 | Maintenance spine is **Era 1 construction** — it predates the current buildings. The spine's route is fixed; buildings were placed around it. This explains: grating floors, cold-white sparse lighting, no Meridian coverage, and why the ring chose it. | Miri | v0.1 |
|
||||
| S-06 | Meridian coverage follows construction era gradient: new construction (gate cluster) = good coverage, mixed era (Terminal, Bar area) = degraded, Era 1 (maintenance spine) = none. | Miri | template |
|
||||
| S-07 | 800 permanent population = compact district. Walking distances are short. Everything is known. | Miri | v0.1 |
|
||||
| S-08 | Back room alley exit leads to the **district edge** (maintenance alley). It is not an interior district route — it accesses the hull boundary, enabling exit without crossing public district space. | Paula, bar layout | v0.1 |
|
||||
| S-09 | Voss does not appear at the bar. The spatial separation of Triangles 1/2 (Terminal-based) from Triangles 3/4 (Bar-adjacent) is architecturally enforced by Voss's absence from the bar. | Paula | v0.1 |
|
||||
| S-10 | Maintenance corridor carries **zero regular traffic**. It must be architecturally believable that no worker has reason to enter — it connects restricted storage to the bar's bathroom corridor. That route has no legitimate use. | Paula | v0.1 |
|
||||
|
||||
---
|
||||
|
||||
### 3. Gameplay Constraints
|
||||
*What the four gameplay loops require from the spatial layout.*
|
||||
|
||||
| # | Constraint | Source | Tag |
|
||||
|---|------------|--------|-----|
|
||||
| G-01 | District must support **four gameplay loops** simultaneously: investigation, social observation, smuggling, daily life. Each loop requires distinct spatial affordances that do not conflict. | Gestalt | template |
|
||||
| G-02 | **Minimum 4–6 social sites** in the district. Confirmed: Terminal (1), Bar (1), Gate cluster (to design, 1). Remaining 1–3 sites are unspecified. | Gestalt | template |
|
||||
| G-03 | **Minimum 2 NPC route convergence points** outside Terminal and Bar — locations where NPCs from different social sites share a path, enabling observation of cross-site relationships. | Gestalt | template |
|
||||
| G-04 | Gate cluster requires its own **social triangle** (≥3 NPCs with conflicting interests). This is not just a transit hub — it is a social site with investigation affordances. | Gestalt | template |
|
||||
| G-05 | **District entry = fork**. Player's first spatial decision is directional: left or right, Terminal or Bar side. Both directions are legitimate from moment one. No forced tutorial path. | Ozzie | template |
|
||||
| G-06 | Transition corridor crossing: **20–25 seconds** at Walk stance. Long enough to be a temporal beat; short enough not to become friction. At Walk (1 tile / 2 ticks, 10 tps) = 5 tiles per second = 100–125 tiles at 20–25s. The corridor as designed (~40m = 40 visual tiles) achieves ~8 seconds — **this is a gap that needs resolving in Round 2** (see Open Tensions T-08). | Ozzie, smuggling layout | v0.1 |
|
||||
| G-07 | Maintenance corridor is a **gradual unlock**, not a sudden discovery. Player should be able to infer its existence (sound, NPC behavior anomaly, spatial hint) before accessing it physically. | Ozzie | template |
|
||||
| G-08 | Every ring-associated location must **read as mundane on first pass**. "Nothing looks wrong" must be achievable without prior knowledge. The ring's spatial design principle (from smuggling layout): same physical space, different player understanding. | Ozzie, smuggling layout | template |
|
||||
| G-09 | Generator district skeleton = **social site positions + access tier topology + traffic routing + observation position set + chokepoint designation**. This is the minimum specification for a generated district to be functional for all four gameplay loops. | Gestalt | template |
|
||||
| G-10 | Zone-chunk alignment: audio zone transitions (D-073) and access tier transitions should coincide with chunk boundaries where possible, enabling the generator to reason about zones at chunk granularity. | Tyre | template |
|
||||
|
||||
---
|
||||
|
||||
### 4. Narrative Constraints
|
||||
*What the five triangle arcs require from the spatial layout.*
|
||||
|
||||
| # | Constraint | Source | Tag |
|
||||
|---|------------|--------|-----|
|
||||
| N-01 | Triangle 4 (Drin–System–Ring) **spans both buildings**. Sera Venn needs a Commission inspection presence at the Terminal — a legitimate reason to be there that doesn't read as suspicious. The spatial layout must provide a Commission inspection point in the Terminal district. | Paula | v0.1 |
|
||||
| N-02 | No private path between manifest processing and supervisor's office (see H-11). Reiterated here: the spatial cost of Maret choosing to act is the *visibility* of crossing the main corridor. | Paula | v0.1 |
|
||||
| N-03 | No second entrance to restricted storage (see H-07). The ring's chokepoint is architectural — not a character choice. | Paula | v0.1 |
|
||||
| N-04 | Bathroom corridor interior zero LOS from bar floor (see H-10). Private ring exchanges in the corridor must be invisible to observers on the bar floor. | Paula | v0.1 |
|
||||
| N-05 | Exactly two routes between Terminal and Bar (see H-09). The public corridor is the ring's exposure; the maintenance corridor is their bypass. A third route dissolves this asymmetry. | Paula | v0.1 |
|
||||
| N-06 | Back room alley exit to district edge (see S-08). Enables ring operational flow from bar side without re-crossing bar floor. | Paula | v0.1 |
|
||||
| N-07 | Voss stays in Terminal spatial zone (see S-09). Triangle 1 and Triangle 2 are Terminal dramas; Triangle 3 is a Bar drama. Voss's absence from the bar is what keeps them separate. | Paula | v0.1 |
|
||||
| N-08 | Maintenance corridor must be genuinely zero-traffic (see S-10). If workers ever had legitimate reason to use it, the ring's use would not be anomalous. | Paula | v0.1 |
|
||||
| N-09 | **Path B discovery** (exploration-heavy investigation path) requires the maintenance hatch to be **discoverable from the break room area**. The terminal layout shows break room (southwest) and restricted storage (south-center) as adjacent structures, but the hatch (M-HATCH-T) opens inside restricted storage, not the break room. This is Paula's key topological issue — see Open Tensions T-01. | Paula | v0.1 |
|
||||
|
||||
---
|
||||
|
||||
### 5. Player Experience Constraints
|
||||
*What feel and navigation require.*
|
||||
|
||||
| # | Constraint | Source | Tag |
|
||||
|---|------------|--------|-----|
|
||||
| P-01 | **Fork at district entry.** First decision is spatial. Terminal to the left, Bar to the right (or some equivalent directionality). The fork must be immediate and legible — no single entry corridor that forces the player through one building first. | Ozzie | template |
|
||||
| P-02 | Transition corridor is a **tonal between-space** — not dead space. Moving through it is a beat of reflection: leaving one social temperature (Terminal = institutional cool), entering another (Bar = warm amber). 20–25s at Walk stance is the target duration. | Ozzie | template |
|
||||
| P-03 | Main corridor at the Terminal must **feel dangerous**. Not literally — no combat threat — but the player should feel observed and out of place if they linger. NPC density, the supervisor's window, the chokepoint geometry all combine to produce this. | Ozzie | v0.1 |
|
||||
| P-04 | Corner booth in the Bar is the **primary investigation observation post**. It must maintain LOS to: entrance, news ticker cluster, bar counter, card table, and back room door. Confirmed by bar layout. Any district modification that disrupts this LOS set breaks the investigation hub. | Ozzie, bar layout | v0.1 |
|
||||
| P-05 | Maintenance corridor discovery should follow an **inference arc**: player hears something, notices NPC timing anomaly, finds the hatch, enters. Discovery should feel like a reveal, not a stumble. | Ozzie | template |
|
||||
| P-06 | The "nothing looks wrong" moment — when the player first realizes the mundane spaces are the criminal infrastructure — should emerge from **accumulated observation**, not a single clue. Spatial design must support layered discovery: visit 1 = normal, visit 2 = curious, visit 3 = understood. | Ozzie | template |
|
||||
|
||||
---
|
||||
|
||||
### 6. Visual / Spatial Constraints
|
||||
*What the tilemap, art direction, and zone palette require.*
|
||||
|
||||
| # | Constraint | Source | Tag |
|
||||
|---|------------|--------|-----|
|
||||
| V-01 | Terminal facade (44m wide) requires a **forecourt** — breathing space between the building face and the transition corridor or district spine. The facade cannot abut a corridor directly. | Araminta | v0.1 |
|
||||
| V-02 | Bar east extension (bathroom corridor, 6m) **faces north**. This fixes the bar's relative orientation: the bathroom corridor opens northward toward the main transit area. The alley exit (south door, row 20 in bar layout) faces the district edge. | Araminta | v0.1 |
|
||||
| V-03 | Transition corridor requires **widening zones** at both ends — spatial decompression before entering the Terminal forecourt and before entering the Bar entry. Narrow corridor expanding to wide forecourt reads as "arrival." | Araminta | template |
|
||||
| V-04 | Gate cluster zone palette: **coolest and newest** in district. Era 3 construction, Commission-grade maintenance. Visually distinct from Terminal (cool grey-navy) and Bar (warm amber). Exact palette TBD in Round 2. | Araminta | v0.1 |
|
||||
| V-05 | **Minimum corridor widths** by type (exact values to be specified in Round 2): maintenance corridor = 2m (confirmed, smuggling layout), transition corridor = 6m (confirmed, smuggling layout), internal building corridors and secondary public corridors = TBD. | Araminta | template |
|
||||
| V-06 | **LOS anchors every ~4 visual tiles** in open spaces. Large open areas (forecourt, cargo floor, bar main floor) need furniture, pillars, kiosks, or fixtures at regular intervals. These serve dual purpose: visual rhythm and gameplay cover/observation points. | Araminta | template |
|
||||
| V-07 | No additional **physical z-levels** unless gameplay-justified. Maintenance corridor on z=0, bar/terminal on z=1. Multi-floor structures require a gameplay reason (investigation access to a floor, combat routing). Cosmetic vertical variation is not sufficient justification. | Araminta, Tyre | both |
|
||||
| V-08 | Zone temperature tints (D-059) must be **distinct and non-overlapping at transition boundaries**. Crossfade handled by D-073 (1.5–2s audio tween at hard tile boundary). Visual fog tint transition should use the same boundary — player should not be in two zone temperatures simultaneously. | D-059, D-073 | template |
|
||||
| V-09 | Generator parameterization: minimum viable generator parameters for spatial layout include **facade width ratio** (building width : forecourt depth), **corridor width minimums by type**, **LOS anchor interval** (tiles between anchor objects in open spaces). | Araminta | template |
|
||||
|
||||
---
|
||||
|
||||
### 7. Open Tensions
|
||||
*Where constraints conflict or are underspecified — these are Round 2 discussion topics.*
|
||||
|
||||
---
|
||||
|
||||
#### T-01 — PRIORITY: Break Room Adjacency / Path B Discovery
|
||||
**What's at stake:** The detective's exploration-heavy investigation path (Path B from smuggling layout) begins with a floor worker in the break room mentioning Kael's odd hours, then the detective physically discovering the maintenance hatch. But the hatch (M-HATCH-T) opens inside *restricted storage*, not the break room. The break room (terminal layout rows 22–26, west) is southwest; restricted storage (rows 27–30, south-center) is south-center. They are adjacent but not connected.
|
||||
|
||||
**Paula's three options:**
|
||||
- **Option A — Shared wall with sound propagation.** Break room and restricted storage share an interior wall. A worker in the break room can hear sounds through the wall that hint at activity in the maintenance corridor (footsteps on grating). Player hears anomaly → investigates restricted storage → discovers hatch.
|
||||
- **Option B — Disused side passage.** A disused (non-traversable) side passage between break room and restricted storage area gives physical proximity to the hatch area without creating a second route to restricted storage.
|
||||
- **Option C — Path B starts on cargo floor.** A worker hears sounds near restricted storage while on the cargo floor (not the break room). Path B's starting location shifts from break room to cargo floor.
|
||||
|
||||
**Why it needs resolving before Round 3:** The option chosen affects the terminal layout (wall topology between zones) and the district layout (is the break room at the southwest corner, or does it need repositioning?). **This is the highest-priority Round 2 discussion item.**
|
||||
|
||||
**Generator implication [template]:** Which option generalizes? Option A (shared wall + sound propagation) is generalisable — it establishes "investigation paths can start with audio anomaly from adjacent zone." Options B and C are more specific to this layout.
|
||||
|
||||
---
|
||||
|
||||
#### T-02 — Gate Cluster Scope and Triangle
|
||||
**What's at stake:** Gestalt requires the gate cluster to be a full social site with its own triangle (≥3 NPCs). Miri requires freight/commuter flow separation at the gate. Together these imply the gate cluster is substantial — a fourth major location, not just a corridor junction. But no scope has been defined: how large? How many zones? What triangle roles?
|
||||
|
||||
**The tension:** A large gate cluster is more content work than a small one. The district tile budget has room, but the content authoring budget (triangles, NPC lines) may not. Round 2 needs a concrete proposal for gate cluster size and triangle composition, or a decision to scope it down.
|
||||
|
||||
**Generator implication [template]:** Gate cluster = entry node in district skeleton. Every freight district has one. The question is whether entry nodes always carry a social triangle or whether that's optional. If Q-036's district skeleton requires a triangle at every social site (Gestalt's minimum is 4–6 sites with triangles), entry nodes need triangle support.
|
||||
|
||||
---
|
||||
|
||||
#### T-03 — Commuter Transit Entry Point Placement
|
||||
**What's at stake:** Miri requires a separate commuter transit connection (not the span gate). This creates a second district entry point. Where it sits relative to the Terminal and Bar has large implications for NPC traffic patterns and ring exposure.
|
||||
|
||||
**Two principal options:**
|
||||
- **Option A — Gate-cluster-integrated.** Commuter transit is adjacent to the span gate — same spatial cluster, separate lanes. All inbound traffic (freight and passenger) arrives in the same zone, then fans out. This simplifies district topology but creates a single convergence point that's easier to monitor.
|
||||
- **Option B — Separate entry point, Bar-side.** Commuter transit hub is on the bar side of the district (near the bar, away from the terminal). Workers arrive near their social space, not their workplace. This produces two active entry zones and richer NPC traffic routing — but complicates ring exposure analysis.
|
||||
|
||||
**Generator implication [template]:** Two-vector entry is Miri's universal freight-district template. The generator needs to know whether the two vectors are co-located (same cluster) or distributed (separate district zones). This is a structural parameter.
|
||||
|
||||
---
|
||||
|
||||
#### T-04 — Remaining Social Sites (1–3 Unidentified)
|
||||
**What's at stake:** Gestalt requires 4–6 social sites. Confirmed: Terminal, Bar, gate cluster = 3. One to three more sites are unspecified.
|
||||
|
||||
**Candidates from existing documentation:**
|
||||
- Commuter transit hub (if Option B from T-03 — separate location with its own social dynamics)
|
||||
- Sector 3 (Miri — named area with ventilation issue; has a location but no defined social site yet)
|
||||
- Maintenance junction node (a secondary gathering point in the maintenance spine? Non-obvious)
|
||||
- A commissary, clinic, or administrative sub-office (generic service space with resident NPCs)
|
||||
|
||||
**What Round 2 needs:** Names and rough positions for the remaining social sites, or a decision to scope to 3 (Terminal + Bar + gate cluster) and justify why that satisfies Gestalt's minimum. Note: 3 may be sufficient if the gate cluster is large enough to function as 1.5 sites.
|
||||
|
||||
**Generator implication [template]:** Social site count is a generator parameter (minimum: 4). The template needs named social site types and their relationship requirements (which types must be adjacent, which must be separated).
|
||||
|
||||
---
|
||||
|
||||
#### T-05 — Transition Corridor Crossing Time
|
||||
**What's at stake:** Ozzie requires 20–25 seconds at Walk stance for the corridor crossing. At Walk (1 tile / 2 ticks, 10 tps = 5 tiles/sec), 20–25 seconds = 100–125 visual tiles. The transition corridor in the smuggling layout is ~40m = 40 visual tiles, achieving only ~8 seconds.
|
||||
|
||||
**Options:**
|
||||
- **Option A — Extend the corridor.** Make the physical transition corridor 100–125 tiles. This is very long (100–125m) and may not fit the station profile ("compact district, 800 population").
|
||||
- **Option B — Accept 8 seconds.** Adjust Ozzie's target to match the physical reality. 8 seconds at Walk is not nothing — it's still a beat. Ozzie's 20–25s may be aspirational rather than hard.
|
||||
- **Option C — Add intermediate spaces.** The transition route includes the forecourt (Terminal side) and entry zone (Bar side). If total route = corridor + forecourt + bar entry area, total walking distance may reach 60–80 tiles (~12–16 seconds). Closer to the target without an implausibly long corridor.
|
||||
|
||||
**Generator implication [template]:** Inter-site transit time is a gameplay parameter. The template should specify minimum/maximum transit time between major social sites, not corridor length directly.
|
||||
|
||||
---
|
||||
|
||||
#### T-06 — Maintenance Spine Route (Era 1 vs. Efficient Path)
|
||||
**What's at stake:** Miri says the maintenance spine predates the buildings (Era 1 construction). If the spine's route is fixed and buildings were placed around it, the maintenance corridor between Terminal and Bar may not run in the most direct path. But the smuggling layout shows a relatively direct corridor connection. Is the route direct (efficient) or wandering (Era 1 authentic)?
|
||||
|
||||
**The tension:** A wandering Era 1 corridor is setting-authentic but adds tile complexity and may not fit cleanly in the district layout. A direct corridor is simpler to lay out but slightly undermines Miri's historical rationale.
|
||||
|
||||
**Generator implication [template]:** Maintenance spine routing is a generator parameter. The template should specify whether maintenance corridors follow the shortest path or use a historically-layered routing algorithm.
|
||||
|
||||
---
|
||||
|
||||
#### T-07 — Corridor Width Minimums (Unspecified)
|
||||
**What's at stake:** Araminta flagged minimum corridor widths by type but did not provide values. Confirmed: maintenance corridor = 2m (2 visual tiles), transition corridor = 6m (6 visual tiles). Unspecified: internal building corridors, secondary public corridors, service alcoves.
|
||||
|
||||
**Round 2 needs:** Explicit minimum widths for each corridor type. These become V-05's specified values and feed into the generator's spatial layout rules.
|
||||
|
||||
---
|
||||
|
||||
#### T-08 — Gate Cluster Zone Temperature Tint
|
||||
**What's at stake:** D-059 assigns zone temperature tints to Terminal (cool dark), Bar (warm dark), corridors (neutral dark). The gate cluster is a fourth zone type requiring a tint. Araminta says it's the "coolest and newest" — suggesting a colder tint than the Terminal. But D-059 already uses "cool dark" for the Terminal. The gate cluster needs a distinct value.
|
||||
|
||||
**Options:** Very cool (near-white institutional), clinical blue-white, or a Commission-grey that reads as "newer" than Terminal's grey-navy.
|
||||
|
||||
**Generator implication [template]:** Zone temperature tint is a per-zone-type parameter. The template needs a tint for each of: logistics-hub, social-venue, transit-corridor, entry-gate. Currently only the first three are decided.
|
||||
|
||||
---
|
||||
|
||||
## §2 — ROUND 2: Cross-Examination and Lead Feedback
|
||||
|
||||
*Compiled by Qatux. Sources: 6 agent Round 2 contributions + lead feedback that resolved T-01 and T-05.*
|
||||
|
||||
---
|
||||
|
||||
### Lead Feedback (resolved before agents responded)
|
||||
|
||||
**Chunk size direction:** Lead prefers larger chunks with a sub-chunk quarter system. Chunks divide into 4 quarters that can merge into one edifice or remain separate. Large civic structures (train stations, government buildings) span multiple chunks. Generator must be top-down: geography → infrastructure → amenities → population → zoning → chunk generation → individual fill. Separate generator architecture workshop required — this is architectural precedent, not v0.1 configuration.
|
||||
|
||||
**Z-level PoC:** Lead mandates one building in the district with a staircase as z-level proof-of-concept. Gate cluster observation gallery selected by consensus — the only unconfirmed location, setting-authentic, investigation-valuable, clean implementation test case. Overrides V-07's "no z-levels without justification" — the PoC IS the justification.
|
||||
|
||||
**T-01 resolved — Option C:** "Hearing through walls is a flimsy core proposition. We don't build out of cardboard." Sound-through-walls is an exception, not a pattern. Detection toolkit is cameras, drones, bugs, maintenance shafts/vents/tunnels. Path B starts on the cargo floor, not the break room. Terminal layout #311 unchanged.
|
||||
|
||||
**T-05 resolved — Careful stance:** Confirmed. Full route at Careful (3.33 tiles/sec per D-053) ≈ 80 tiles = ~24 seconds. Constraint is stance-dependent, not corridor-length-dependent. No layout change needed.
|
||||
|
||||
**Gate/transport lore clarification (S-02 revision):** System gates serve only freight externally. "Commuter transit" = internal station transit (train/tram) from Residential Core. One external entry (span gate). One internal transit stop within the district. T-03 reframed as T-03b: where does the internal transit stop sit?
|
||||
|
||||
**Transport lore questions:** Captured as Q-040–Q-044. All assigned to Miri.
|
||||
|
||||
---
|
||||
|
||||
### Agent Round 2 Positions
|
||||
|
||||
**Ozzie (Player Experience)**
|
||||
- Confirmed T-05 can be satisfied by Careful stance measurement — accepts this resolution
|
||||
- "Strong yes" on G-11 (gate cluster observation gallery as player investigation vantage point)
|
||||
- Flagged V-06 exception: Terminal main corridor should be bare of LOS anchors — the exposure is the gameplay mechanic, not a design oversight. Open spaces that are *meant* to feel dangerous are exempt from the anchor rule
|
||||
- Requested G-08 receive an official name in the D-record (the "nothing looks wrong" principle — mundane face on criminal infrastructure)
|
||||
|
||||
**Gestalt (Systems Design)**
|
||||
- Gate cluster triangle composition: customs officer + freight forwarder + waiting commuter (3 NPCs, distinct interests, credible spatial conflict)
|
||||
- 4th social site = **transit hub** (bar-side internal transit stop has its own NPC population, social dynamics, and convergence function). This resolves T-04 — site count: Terminal + Bar + Gate cluster + Transit hub = 4, satisfying G-02 minimum
|
||||
- G-11 confirmed: observation gallery at z=2 is a valid **investigation vantage point** — qualifies as a gameplay-justified z-level (H-13 / V-07 condition satisfied by lead's PoC directive)
|
||||
|
||||
**Miri (Worldbuilding)**
|
||||
- [PENDING Round 3 — horizon station revision, T-04 position, Commission overlap resolution]
|
||||
- Internal transit stop: bar-side position accepted (workers commute to bar district, then walk to Terminal for shift)
|
||||
- Transport lore model submitted — see Q-040–Q-044 for captured questions
|
||||
|
||||
**Tyre (Technical)**
|
||||
- [PENDING Round 3 — chunk size technical confirmation at 64×64 visual, z-level validation, cross-z shadowcasting spec]
|
||||
|
||||
**Araminta (Visual/Spatial)**
|
||||
- **Corridor width minimums (V-05 now specified):**
|
||||
|
||||
| Corridor type | Width (visual tiles) | Width (meters) |
|
||||
|--------------|---------------------|----------------|
|
||||
| Maintenance corridor | 2 | 2m |
|
||||
| Internal building corridors | 2 | 2m |
|
||||
| Secondary public corridors | 4 | 4m |
|
||||
| Transition corridor | 6 | 6m |
|
||||
| Gate customs lanes | 2 | 2m (per lane) |
|
||||
| Gate concourse | 8 | 8m |
|
||||
| Service alcoves | 1 | 1m |
|
||||
|
||||
- **Gate cluster zone temperature tint:** `#0a1520` — deep institutional cold, distinct from Terminal's cool-grey-navy. Coldest zone in the district
|
||||
- **Gate observation gallery spec:** 4 tiles wide × lane-length, Commission grey-white palette. Gallery floor is z=2; same zone tint as gate cluster ground floor (elevation ≠ new zone)
|
||||
- **Outlier on chunk size:** Araminta prefers 64×64 sim (32×32 visual). Rationale: visual tile is the authoring unit; generator should reason at visual-tile scale. Noted as minority position
|
||||
|
||||
**Paula (Narrative)**
|
||||
- **Gate cluster triangle revised:** operations manager + senior freight handler + Commission inspector (3 NPCs). Rationale: Commission inspector is Sera Venn's institutional peer — this creates the Commission overlap (N-01) without requiring Sera to be permanently stationed at Terminal. Inspector visits = legitimate, scheduled, observable
|
||||
- **T-01 revised:** Path B via restricted storage door, not break room wall. The anomalous sound is footsteps on the maintenance grating, heard through the (imperfect) seal around the restricted storage access door on the cargo floor — not sound through a solid wall. The door is the weak point, not the wall. This preserves Option C without invoking cardboard-wall physics
|
||||
- **Path C confirmed:** A third investigation approach for the detective — the Commission inspection overlap. The Commission inspector (gate cluster triangle NPC) has access to the same manifest anomalies as Maret but reads them as institutional compliance failures, not criminal ones. Detective PC who befriends the inspector gets a different angle on the evidence: institutional rather than human
|
||||
- **Sector 3:** Supports Miri's framing — sub-area adjacent to maintenance spine, ventilation complaint is ambient NPC dialogue, not a full social site
|
||||
- **6 narrative constraints for D-record:** Commission inspector has access to gate cluster AND Terminal (inspection authority crosses building boundaries); inspector's Terminal visits are scheduled (visible, predictable — ring can route around them); Sera is a bar regular who knows the inspector professionally (Triangle 4 cross-link); no NPC in the gate cluster triangle has social connection to Voss (Terminal triangle separation maintained); transit hub NPCs are socially isolated from the Terminal workers (two distinct working cultures); back room alley exit connects to maintenance alley which connects to district edge, NOT to the transit hub service area
|
||||
|
||||
---
|
||||
|
||||
### Round 2 Tension Status
|
||||
|
||||
| Tension | Status after Round 2 | Resolution |
|
||||
|---------|---------------------|------------|
|
||||
| T-01 Break room adjacency | **RESOLVED** | Option C: cargo floor + restricted storage door acoustic gap |
|
||||
| T-02 Gate cluster scope | **RESOLVED** | Two competing triangle compositions (Gestalt vs. Paula) — Round 3 to pick one |
|
||||
| T-03b Transit stop placement | **RESOLVED** | Bar-side — consensus |
|
||||
| T-04 4th social site | **RESOLVED** | Transit hub (bar-side) |
|
||||
| T-05 Corridor crossing time | **RESOLVED** | Careful stance ~24s on ~80-tile route |
|
||||
| T-06 Maintenance spine routing | **RESOLVED** | Direct path for v0.1; wandering routing deferred to generator workshop |
|
||||
| T-07 Corridor widths | **RESOLVED** | Araminta's table (see above) |
|
||||
| T-08 Gate cluster tint | **RESOLVED** | `#0a1520` deep institutional cold |
|
||||
| NC-01 Transit stop placement | **RESOLVED** | Same as T-03b |
|
||||
| NC-02 G-10 chunk alignment | **PARTIALLY RESOLVED** | At 64×64 visual chunks, chunk ≈ zone; G-10 revised accordingly |
|
||||
| NC-03 Gallery tint (z=2) | **RESOLVED** | Same tint as gate cluster ground floor |
|
||||
| NC-04 Sector 3 / maintenance spine | **RESOLVED** | Adjacent; ventilation = ambient NPC dialogue |
|
||||
|
||||
**Remaining for Round 3:** Gate cluster triangle — pick Gestalt's composition or Paula's. Chunk size — confirm 64×64 visual (Tyre analysis pending). Miri's horizon station revision and Commission overlap resolution.
|
||||
|
||||
---
|
||||
|
||||
## §3 — ROUND 3: Convergence
|
||||
|
||||
*Compiled by Qatux. All 6 agents delivered Round 3. All tensions resolved. D-records filed: D-093, D-094, D-095.*
|
||||
|
||||
---
|
||||
|
||||
### Confirmed Consensus
|
||||
|
||||
**T-01 (break room / Path B):** Option C confirmed by lead. Sound anomaly at restricted storage door (cargo floor) → Path B. Paula's refinement accepted: acoustic gap is the door seal, not a wall. No modification to terminal layout #311.
|
||||
|
||||
**T-03b (transit stop):** Bar-side. Unanimous. Creates NPC convergence point at bar entry (satisfies G-03).
|
||||
|
||||
**T-04 (4th social site):** Sector 3 residential (Drin/Naia anchor). Miri's Round 3 revision supersedes the interim Transit Hub consensus. The transit platform (The Loop stop) is reclassified as an encounter node (bar-side convergence point). Site count = 4 (Terminal, Bar, Gate cluster, Sector 3 residential). G-02 minimum satisfied.
|
||||
|
||||
**T-05 (crossing time):** Walk ~13–14s on ~65–70 tile route; Careful stance ~24s. Ozzie confirms. No layout change.
|
||||
|
||||
**T-06 (maintenance spine routing):** Direct path for v0.1. Generator-level concern deferred.
|
||||
|
||||
**T-07 (corridor widths):** Araminta's table confirmed. Filed as V-05 specified values.
|
||||
|
||||
**T-08 (gate cluster tint):** `#0a1222` deep institutional cold (Araminta final). Gallery at z=2 shares ground-floor tint.
|
||||
|
||||
**G-11 (observation gallery as investigation vantage):** Confirmed by Gestalt + Ozzie. The gate cluster observation gallery at z=2 is a designated investigation position — the player can observe arriving cargo from elevation. This is the gameplay justification for the z-level PoC.
|
||||
|
||||
**G-08 naming:** Confirmed name: **"Invisible infrastructure principle"** — every ring location serves a mundane purpose; criminal function is only apparent if you know what to look for. This is the spatial design principle stated in the smuggling layout and now formally named.
|
||||
|
||||
**V-06 exception (main corridor):** The Terminal main corridor is explicitly exempt from the LOS anchor rule. The bare, unobstructed corridor is the gameplay mechanic — exposure IS the design. Araminta confirmed.
|
||||
|
||||
**Gate cluster triangle:** Paula's composition selected — operations manager + senior freight handler + Commission inspector. Rationale: Commission inspector creates the narrative bridge (N-01, Path C, Sera's institutional peer) that Gestalt's commuter-based composition cannot provide.
|
||||
|
||||
**Path C (Commission inspection overlap):** Confirmed. The Commission inspector at the gate cluster has institutional access to Terminal manifest data. Detective PC who builds relationship with inspector gains a third, institutional angle on the evidence. Non-confrontational. Sera Venn cannot access gate cluster customs records without a formal Commission request — separate institutional chains.
|
||||
|
||||
**Sector 3 residential (S-04):** Sub-area adjacent to maintenance spine. 4th confirmed social site (D-025). Anchor NPCs: Drin and Naia (ongoing ventilation dispute). Industrial Sector jurisdiction. Ambient dialogue provides local texture without plot relevance.
|
||||
|
||||
**Commission overlap resolution (N-01):** Gate cluster customs zone is Commission-jurisdictioned space. Commission inspector has scheduled inspection authority crossing into Terminal. This explains Sera's legitimate Terminal presence without requiring her to be stationed there.
|
||||
|
||||
**Chunk size (confirmed):** Chunk = 64×64 sim (32×32 visual, 32m) — streaming unit. Block = 128×128 sim (64×64 visual, 64m) — generator planning unit, 4 chunks. District = 4×4 blocks = 512×512 sim (256×256 visual, 256m). Quarter = 32×32 visual within a block (sub-block unit for generator fill). Araminta dissent (preferred 32×32 visual chunk) noted, overruled. Amends D-012; supersedes D-014 estimate. Filed as D-094.
|
||||
|
||||
**Z-level scheme (confirmed):** z=0 maintenance corridor (Era 1), z=1 all main district structures (Terminal, Bar, Gate cluster ground, transit platform), z=2 Gate cluster observation gallery only. Cross-z LOS: gallery rail = transparent low wall; player on z=2 sees z=1 below; z=1 cannot see upward unless at staircase. Confirmed by Tyre.
|
||||
|
||||
**Transport lore (Miri — confirmed):** Span gates: human-built, single aperture, dual-use windows (freight/passenger). Horizon stations: alien-built, 4–8 apertures, Oort-cloud distance, "The Ring" per-system. Sequential hop travel only. The Loop: 6-district internal tram, 4min Residential Core → Transit District. Station profile correction: Sova's horizon gates at The Ring, not Admin Hub. Q-040/Q-041/Q-043/Q-044 resolved → D-093/D-095. Q-042 partially resolved. Filed as D-095.
|
||||
|
||||
---
|
||||
|
||||
### Round 3 — All Items Resolved
|
||||
|
||||
All tensions and open items from Rounds 1 and 2 resolved. D-records filed: D-093 (`decisions/content.md`), D-094 (`decisions/architecture.md`), D-095 (`decisions/content.md`). No pending items.
|
||||
|
||||
---
|
||||
|
||||
## §4 — D-RECORDS FILED
|
||||
|
||||
*Filed by Qatux, 2026-02-25. D-093 (Sova Transit District spatial layout), D-094 (District spatial hierarchy), D-095 (Horizon stations and gate infrastructure). See `decisions/content.md` and `decisions/architecture.md`.*
|
||||
|
||||
---
|
||||
|
||||
### D-093: Sova Transit District — Spatial Layout and District Topology
|
||||
|
||||
**Decision file:** `decisions/content.md`
|
||||
**Date:** 2026-02-25
|
||||
**Source:** Station District Layout Workshop, Ticket #153 (Sprint 20)
|
||||
**Raised by:** Full team (Gestalt, Miri, Araminta, Tyre, Paula, Ozzie). Compiled by Qatux.
|
||||
**Dissent:** Araminta on chunk size (prefers 32×32 visual chunk; overruled by lead and team majority). No other dissent.
|
||||
|
||||
---
|
||||
|
||||
#### Decision
|
||||
|
||||
The Sova Transit District spatial layout is confirmed as follows.
|
||||
|
||||
---
|
||||
|
||||
#### 1. District Topology
|
||||
|
||||
```
|
||||
N (external — span gate, horizon station connection)
|
||||
↑
|
||||
┌─────────────────────────────────────────┐
|
||||
│ GATE CLUSTER │ ~40×32 visual tiles [T-02 est.]
|
||||
│ z=1: gate floor, customs lanes (2m ea),│ Tint: #0a1222 (institutional cold)
|
||||
│ concourse (8m), processing zone │ Access: PUBLIC (concourse)
|
||||
│ z=2: observation gallery (4 tiles wide)│ SEMI-PRIVATE (customs lanes)
|
||||
│ Commission grey-white palette │ PRIVATE (inspection booths)
|
||||
└────────────────┬────────────────────────┘
|
||||
│ forecourt (~8–10 tiles deep)
|
||||
┌────────────────┴────────────────────────┐
|
||||
│ TERMINAL (Sova Logistics Hub) │ 44×28 visual tiles (confirmed #311)
|
||||
│ z=1. Tint: cool grey-navy │ Access: PUBLIC (entry lobby)
|
||||
│ Entry lobby → scanner bays → │ SEMI-PUBLIC (main corridor)
|
||||
│ main corridor → cargo floor / │ SEMI-PRIVATE (cargo floor,
|
||||
│ manifest processing / break room → │ manifest proc., break room)
|
||||
│ restricted storage (coded, 1 door) │ PRIVATE (supervisor office,
|
||||
│ Supervisor office: [=] window south │ restricted storage)
|
||||
└────────────────┬────────────────────────┘
|
||||
│ ← maintenance corridor z=0 branches east here
|
||||
│ (restricted storage → M-HATCH-T → 40m → M-HATCH-B)
|
||||
│
|
||||
TRANSITION CORRIDOR
|
||||
~40m × 6m (6 visual tiles wide) Tint: neutral dark
|
||||
Surveillance camera at T=24m Access: SEMI-PUBLIC
|
||||
Widening zones at both ends
|
||||
(forecount N-side, bar entry S-side)
|
||||
│
|
||||
│ ← maintenance corridor z=0 terminates at M-HATCH-B
|
||||
│ (opens into bar bathroom corridor)
|
||||
┌────────────────┴────────────────────────┐
|
||||
│ TRANSIT PLATFORM (encounter node) │ [dimensions TBD — ~12×8 visual est.]
|
||||
│ z=1. The Loop tram stop (bar-side) │ Tint: neutral-warm (transitional)
|
||||
│ (tram from Residential Core) │ Access: PUBLIC
|
||||
│ NPC population: commuters, workers. │
|
||||
│ Convergence point #2 (G-03). │
|
||||
└────────────────┬────────────────────────┘
|
||||
│ (bar entry zone, ~5 tiles)
|
||||
┌────────────────┴────────────────────────┐
|
||||
│ BAR — THE LAST SHIFT │ 28×22 + 6m east extension visual (confirmed #312)
|
||||
│ z=1. Tint: warm dark amber │ Access: PUBLIC (main floor, card table)
|
||||
│ East extension (bathroom corridor) │ SEMI-PRIVATE (serving south,
|
||||
│ faces north toward transit hub. │ bathroom corridor)
|
||||
│ Alley door (south) → maintenance │ PRIVATE (back room)
|
||||
│ alley → district edge. │
|
||||
└─────────────────────────────────────────┘
|
||||
↓
|
||||
S (maintenance alley — district edge)
|
||||
|
||||
─────────────────────────────────────────────────────────────────────
|
||||
|
||||
MAINTENANCE CORRIDOR (z=0, runs parallel to transition corridor)
|
||||
|
||||
[RESTRICTED STORAGE north wall / cargo floor door]
|
||||
→ M-HATCH-T (locked hatch, restricted storage interior)
|
||||
→ EAST SERVICE SPINE (~15m, narrow, dim)
|
||||
→ JUNCTION-1 (DD-2 dead-drop location)
|
||||
→ [side branch west] SECTOR 3 RESIDENTIAL (~15×12 visual)
|
||||
Drin/Naia (ventilation dispute anchor)
|
||||
4th social site (D-025). Access: PUBLIC
|
||||
→ DISTRICT SPINE (~25m, grating floor)
|
||||
→ DD-3 "The Mark" (go/no-go signal, mid-corridor)
|
||||
→ M-HATCH-B (locked hatch, bar bathroom corridor)
|
||||
|
||||
Era 1 construction. No Meridian coverage. Grating floors.
|
||||
Zero LOS from all other spaces. Zero regular NPC traffic.
|
||||
Access: PRIVATE (ring members only in practice).
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 2. Zone Dimensions
|
||||
|
||||
| Zone | Visual tiles | Sim tiles | Notes |
|
||||
|------|-------------|-----------|-------|
|
||||
| Gate cluster (z=1) | 40×32 | 80×64 | Araminta confirmed — 7 zones (see §4.2 zone spec) |
|
||||
| Gate observation gallery (z=2) | 32×10 | 64×20 | Commission grey-white; Araminta confirmed |
|
||||
| Terminal | 44×28 | 88×56 | Confirmed (#311) |
|
||||
| Forecourt (Terminal N-face) | ~44×10 | ~88×20 | Estimate |
|
||||
| Transition corridor | ~40×6 | ~80×12 | Confirmed (#313) |
|
||||
| Transit platform | ~12×8 | ~24×16 | Encounter node — The Loop stop; bar-side |
|
||||
| Sector 3 residential | ~15×12 | ~30×24 | 4th social site (D-025); maintenance spine adjacent |
|
||||
| Bar entry zone | ~28×5 | ~56×10 | Estimate |
|
||||
| Bar (Last Shift) | 28×22 (+6m E ext.) | 56×44 (+12 E ext.) | Confirmed (#312) |
|
||||
| Maintenance corridor | ~40×2 | ~80×4 | Confirmed (#313) |
|
||||
| **Total district bounding box** | **~256×256 visual** | **~512×512 sim** | 4×4 blocks per D-094; D-014 estimate superseded |
|
||||
|
||||
---
|
||||
|
||||
#### 3. Access Topology
|
||||
|
||||
Sequential. No tier skipping (H-06).
|
||||
|
||||
```
|
||||
PUBLIC → SEMI-PUBLIC → SEMI-PRIVATE → PRIVATE
|
||||
────── ─────────── ──────────── ───────
|
||||
Gate concourse Transition corridor Terminal cargo floor Supervisor office
|
||||
Gate floor Terminal main corridor Terminal manifest proc. Restricted storage
|
||||
Terminal lobby Gate customs entry Terminal break room Maintenance corridor
|
||||
Transit hub Bar serving south Bar back room
|
||||
Bar main floor Bar bathroom corridor
|
||||
Bar card table
|
||||
Bar counter (cust. side)
|
||||
```
|
||||
|
||||
Movement across tiers requires: worker role (trivial), authority (badge → semi-private), access code (private), ring membership (maintenance corridor/restricted storage).
|
||||
|
||||
---
|
||||
|
||||
#### 4. Spatial Hierarchy (Confirmed Naming)
|
||||
|
||||
- **Chunk** = 64×64 sim (32×32 visual, 32m) — streaming and serialization unit
|
||||
- **Block** = 128×128 sim (64×64 visual, 64m) — generator planning unit; composed of 2×2 chunks (4 chunks per block)
|
||||
- **District** = 4×4 blocks = 512×512 sim (256×256 visual, 256m); 16 blocks, 64 chunks per z-level
|
||||
- **Chunk merge rules:** Adjacent chunks within a block can merge into one large edifice, remain separate (small buildings, gardens, cafes, shacks), or form L-shaped buildings across chunk boundaries
|
||||
- **Large civic structures:** Span multiple blocks (gate cluster, horizon station installations, stadiums, parks)
|
||||
- **District skeleton:** Each major social site occupies approximately one chunk (32×32 visual) within its block
|
||||
|
||||
**Generator architecture note:** The full top-down generator model (geography → infrastructure → amenities → population → zoning → block generation → chunk fill) requires a dedicated workshop brief. This chunk/block/district specification is the spatial primitive for that future system. Q-036 tracks district skeleton design.
|
||||
|
||||
---
|
||||
|
||||
#### 5. Z-Level Scheme
|
||||
|
||||
| Level | Contents | Notes |
|
||||
|-------|----------|-------|
|
||||
| z=0 | Maintenance corridor (Era 1) | Full district. Dim cold-white lighting. Grating floors. No Meridian. |
|
||||
| z=1 | All main district structures | Terminal, Bar, Gate cluster ground, transit platform, Transition corridor |
|
||||
| z=2 | Gate cluster observation gallery only | 4 tiles wide × lane-length. Commission grey-white palette. Access via staircase in gate cluster. |
|
||||
|
||||
**Cross-z LOS (confirmed, Tyre):** Vertical LOS propagates only through designated transparent floor/window tiles. Opaque floor tile = full LOS block. Gallery rail = transparent floor tile, designer-placed. Player on z=2 has LOS downward through transparent rail to gate floor (z=1); LOS does not propagate upward from z=1 except at staircase opening. Performance cost: ~100–150µs per additional FOV pass — trivial. Gallery observation capability is architecturally controlled by designer tile placement, not a special case.
|
||||
|
||||
**Inter-z sound (confirmed, Tyre):** Sound propagates across z-levels only through open hatches and designated vent tiles. The maintenance corridor (z=0) is inaudible from z=1 except at hatch locations (M-HATCH-T inside restricted storage, M-HATCH-B in bar bathroom corridor). This makes the acoustic gap at the cargo floor door (Path B) mechanically coherent — it is a z=1 surface feature, not a z=0 leak.
|
||||
|
||||
**Memory (confirmed, Tyre):** Three z-levels for this district = ~1.35MB. Trivial.
|
||||
|
||||
**Z-level PoC purpose:** The gate cluster observation gallery proves the z-level rendering stack (D-049) and cross-z shadowcasting (D-035) for all future development. This is the only z=2 space in v0.1.
|
||||
|
||||
---
|
||||
|
||||
#### 6. Key Sightline Relationships
|
||||
|
||||
*Across the full district:*
|
||||
|
||||
| From | To | LOS | Notes |
|
||||
|------|----|-----|-------|
|
||||
| Gate observation gallery (z=2) | Gate floor below (z=1) | ✓ downward | Player sees cargo off-loading from above |
|
||||
| Transition corridor (any position) | Terminal exterior / Bar exterior | ✗ | Buildings are opaque from outside |
|
||||
| Transit hub | Bar entry zone | ✓ | Convergence point — workers arriving see bar entrance |
|
||||
| Maintenance corridor | Anywhere | ✗ | Zero LOS in or out. Sound only. |
|
||||
| Maintenance hatch (M-HATCH-T) | Cargo floor | ✗ | Hatch opens inside restricted storage — only visible to someone already in restricted storage |
|
||||
|
||||
*Within Terminal (from #311 — unchanged):*
|
||||
- Main corridor → all four south-facing doors: ✓
|
||||
- Supervisor window [=] → cargo floor + restricted storage door: ✓
|
||||
- Break room → anything: ✗ (isolated)
|
||||
- Manifest processing → main corridor (door open): ✓
|
||||
|
||||
*Within Bar (from #312 — unchanged):*
|
||||
- Corner booth (NW deepest) → entrance, bar counter, card table, back room door: ✓
|
||||
- Bathroom corridor interior → main bar: ✗ (door only)
|
||||
|
||||
---
|
||||
|
||||
#### 7. NPC Routes and Convergence Points
|
||||
|
||||
Two confirmed convergence points outside Terminal and Bar (satisfies G-03):
|
||||
|
||||
1. **Terminal main corridor** — all Terminal workers cross here. Every person moving between scanner bays (north) and cargo floor / manifest processing / supervisor office (south) passes through. Semi-public; lingering is suspicious.
|
||||
|
||||
2. **Transit platform / bar entry zone** — workers arriving from Residential Core via The Loop tram. Some walk north to Terminal, some enter bar directly. NPCs from different social sites share this arrival space. Encounter node, not social site.
|
||||
|
||||
Additional convergence: **Transition corridor** — ring members and workers both use this corridor. The surveillance camera at T=24m records all transits. Pattern analysis reveals ring operational schedule.
|
||||
|
||||
---
|
||||
|
||||
#### 8. Investigation Paths (confirmed)
|
||||
|
||||
**Path A — Pattern Recognition (insert-heavy):**
|
||||
Corridor camera logs → Kael's deep-night transit pattern → manifest database access (manifest processing) → restricted storage access timing → ring identified.
|
||||
|
||||
**Path B — Physical Traversal (exploration-heavy):**
|
||||
Cargo floor: anomalous sound at restricted storage door (acoustic gap around coded door seal) → floor worker conversation → physical discovery of maintenance hatch → corridor traversal → dead-drops discovered. *Note: sound propagation through door seal, not wall. Door is the weak point.*
|
||||
|
||||
**Path C — Commission Inspection Overlap (institutional):**
|
||||
Detective builds relationship with Commission inspector (gate cluster triangle NPC) → inspector shares institutional read of manifest anomalies (compliance framing, not criminal) → detective gains third angle on same evidence. Non-confrontational. Complements Paths A and B.
|
||||
|
||||
---
|
||||
|
||||
#### 9. Transport Lore
|
||||
|
||||
*Captured from workshop discussion:*
|
||||
|
||||
**Span gates (Q-040 — resolved → D-093):** Human-built. Single aperture. Near-instantaneous transit. Scheduled dual-use windows: freight (bulk of operating hours) and passenger (scheduled slots). Physical layout reflected in gate cluster zone spec (§4.2).
|
||||
|
||||
**Horizon stations (Q-041 — resolved → D-095):** Alien-built (no identified builder species). Self-maintaining. 4–8 apertures per station. Located at Oort-cloud distance. Per-system canonical name: "The Ring." Sequential hop travel only (A→B→C through intermediate systems; no direct long-range transit). Per-system variation across 4 access tiers. Station Sova's horizon gates are at The Ring — Admin Hub contains booking offices only.
|
||||
|
||||
**Intra-system transport (Q-042 — partially resolved):** Span gates at star/planetary level plus horizon stations at Oort distance. Details of intra-system hab-to-hab transit remain open.
|
||||
|
||||
**Station internal transit (Q-043 — resolved → D-095):** "The Loop" — internal station tram, 6 districts, 4-minute Residential Core → Transit District run. Workers arrive at transit platform (bar-side) and disperse to Terminal or bar.
|
||||
|
||||
**Gate-train integration (Q-044 — resolved → D-093/D-095):** Arriving passengers exit span gate → aperture chamber → freight/passenger customs lanes → gate concourse (public) → transition corridor → transit platform (The Loop). No direct gate-to-tram connection; transition corridor is the linking space.
|
||||
|
||||
---
|
||||
|
||||
#### 10. Gate Cluster Social Triangle
|
||||
|
||||
**Composition:** Operations manager + senior freight handler + Commission inspector
|
||||
|
||||
**Spatial staging:**
|
||||
- Operations manager: works the gate floor (supervises cargo processing, knows every shipment)
|
||||
- Senior freight handler: works customs lanes (clears cargo for transit; knows what should and shouldn't be there)
|
||||
- Commission inspector: scheduled inspection visits (legitimate authority, reads anomalies institutionally)
|
||||
|
||||
**Triangle tension:** Operations manager and freight handler have an established working relationship — and a shared interest in not attracting Commission attention. Commission inspector is an outside force disrupting their equilibrium. Investigation opportunity: the inspector sees things the manager and handler want invisible.
|
||||
|
||||
**Commission overlap (N-01, Path C):** Inspector has institutional access to Terminal manifest data. Sera Venn's professional peer. Detective who builds relationship with inspector gains Path C.
|
||||
|
||||
---
|
||||
|
||||
#### 11. Invisible Infrastructure Principle (G-08, now named)
|
||||
|
||||
Every ring location serves a mundane purpose. Criminal function is only apparent if you know what to look for. The player (smuggler PC) knows. The detective PC can learn. An NPC observing in isolation sees nothing unusual. Same physical space — different player understanding.
|
||||
|
||||
This is the core spatial design principle for the district. All investigation paths are designed around it. The principle is generalisable: it applies to any district in the Reach that hosts a Tier 1 module.
|
||||
|
||||
---
|
||||
|
||||
#### 12. Narrative Constraints (Paula — all confirmed)
|
||||
|
||||
1. Commission inspector has inspection authority crossing gate cluster AND Terminal — schedules are publicly known
|
||||
2. Inspector's Terminal visits are scheduled and predictable — ring can route around them; gives ring temporal structure
|
||||
3. Sera Venn is a bar regular who knows the inspector professionally — Triangle 4 cross-link
|
||||
4. No NPC in the gate cluster triangle has social connection to Voss — Terminal and gate cluster triangle separation maintained
|
||||
5. Transit hub NPCs are socially isolated from Terminal workers — two distinct working cultures; they share the transition corridor but not social space
|
||||
6. Back room alley exit connects to maintenance alley → district edge; does NOT connect to transit hub service area
|
||||
|
||||
---
|
||||
|
||||
#### Rationale
|
||||
|
||||
The district layout emerged from three rounds of cross-domain synthesis:
|
||||
- Round 1 established 52 constraints across 6 domains and 8 open tensions
|
||||
- Round 2 resolved 10 of 11 tensions; lead feedback resolved T-01 (cargo floor) and T-05 (Careful stance)
|
||||
- Round 3 achieved consensus on all remaining items
|
||||
|
||||
The layout satisfies: D-025 (social sites as atomic template units), D-036 (Sova Transit District setting), D-054 (tile-based movement), D-059 (fog zone temperature tints), D-066 (dual-scale grid), D-011 (fog of perception), D-018 (sound model), D-027 (vertical slice success criteria).
|
||||
|
||||
The chunk/quarter system and top-down generator model establish architectural precedent for Q-036 (district skeleton as generator output).
|
||||
|
||||
---
|
||||
|
||||
#### Cross-References
|
||||
|
||||
- Terminal layout: `docs/design/spatial-layout-terminal-v01.md` (#311)
|
||||
- Bar layout: `docs/design/spatial-layout-bar-v01.md` (#312)
|
||||
- Smuggling corridors: `docs/design/spatial-layout-smuggling-corridors-v01.md` (#313)
|
||||
- Station profile: `docs/design/sova-station-profile.md` (#320)
|
||||
- Gate cluster layout: `docs/design/spatial-layout-gate-v01.md` (to be authored — blocks #157)
|
||||
- Transit hub layout: TBD (new ticket required)
|
||||
- Transport lore open questions: Q-040–Q-044
|
||||
- Generator architecture workshop: pending brief
|
||||
|
||||
---
|
||||
|
||||
*D-records filed: D-093 (decisions/content.md), D-094 (decisions/architecture.md), D-095 (decisions/content.md). Gate cluster full layout to be authored as #157. District bounding box (256×256 visual) supersedes D-014 estimate per D-094.*
|
||||
@@ -0,0 +1,115 @@
|
||||
# Sprint 20: Shape — Client Tasks
|
||||
|
||||
**Goal:** The social site template system gains its foundational schema; triangles become generatable and observable as escalating tensions; the client gains save/load UI and code quality improvements.
|
||||
|
||||
**Branch:** `client`
|
||||
**Agents:** Stig (UI/rendering), Tyre (architecture), Hoshe (QA)
|
||||
|
||||
## Carry-over from Sprint 19
|
||||
|
||||
None — Sprint 19 complete. #554 (save/load client UI) is finishing in Sprint 19.
|
||||
|
||||
## New Tickets
|
||||
|
||||
| # | Title | Blocked by |
|
||||
|---|-------|------------|
|
||||
| #557 | Refactor: game_state.gd derived state in apply_snapshot() | — |
|
||||
| #558 | Refactor: dialogue_box.gd direct GameState mutation and AudioManager coupling | — |
|
||||
| #559 | Refactor: main.gd god coordinator — extract SnapshotEventRouter | — |
|
||||
| #560 | Refactor: unify duplicate YAML parsers | — |
|
||||
|
||||
Use `db/connectors/ticket show <id>` for full details.
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- `decisions/architecture.md` — D-020 (Godot is a pure renderer: no game logic in GDScript; GameState reflects server-authoritative data, not derived behavior), D-085 (per-game save directory structure: `user://saves/<timestamp>-<seed>/`, F5=quicksave, F6=quickload, loading screen lists dirs by last-modified), D-088 (3-state pause system: client sends pause requests, server is authoritative)
|
||||
- `decisions/scope.md` — D-027 (vertical slice success criteria: game session must be resumable for 30-min playthroughs)
|
||||
|
||||
## Notes
|
||||
|
||||
### #557 — Refactor: game_state.gd derived state in apply_snapshot()
|
||||
|
||||
Code review finding: `apply_snapshot()` in `client/scripts/autoloads/game_state.gd` computes two derived values inline:
|
||||
- `stationary_ticks` (increments when player position hasn't changed) — line 99 / ~line 131-133
|
||||
- `current_zone_id` (derived from tile iteration) — line 105 / ~line 286
|
||||
|
||||
Per D-020, the Godot client is a pure renderer. Behavior-driving computations (stationary tick counting, zone identification) belong in the server, not in `apply_snapshot()`. The server already sends `zone_id` per tile — the client should read it directly rather than re-deriving it.
|
||||
|
||||
What this ticket must deliver:
|
||||
- Move `stationary_ticks` accumulation out of `apply_snapshot()`. The server sends `stationary_ticks` (or equivalent) in the snapshot — if not yet present, add the field to `ObserverSnapshot` in `client/scripts/protocol/protocol.gd` and mark with a TODO for the server team to populate it. Client reads the server value directly.
|
||||
- Move `current_zone_id` resolution to a simple property read from the snapshot (`player_tile.zone_id`), removing the tile iteration loop from `apply_snapshot()`.
|
||||
- After: `apply_snapshot()` contains only direct field assignments from the snapshot dictionary — no conditional logic, no accumulation.
|
||||
- Add a comment citing D-020 on each removed computation to document the rationale.
|
||||
- Unit tests: `apply_snapshot()` with a snapshot missing the new fields should degrade gracefully (default values, no crash).
|
||||
|
||||
Integration points: `client/scripts/autoloads/game_state.gd` only. Protocol fields may need a minor extension in `client/scripts/protocol/protocol.gd` — coordinate with server team if new snapshot fields are required.
|
||||
|
||||
Gotcha: `stationary_ticks` drives `ListeningFocus` (D-071) — confirm the server already tracks and sends this value before removing client-side accumulation. If the server does not yet send it, add a feature-flagged fallback that keeps the old behavior with a deprecation comment.
|
||||
|
||||
### #558 — Refactor: dialogue_box.gd direct GameState mutation and AudioManager coupling
|
||||
|
||||
Code review finding: `client/ui/dialogue_box.gd` directly mutates `GameState.dialogue_active` at 3 call sites (lines ~289, ~321, ~334) and calls `AudioManager.apply_dip()` / `AudioManager.clear_dip()` directly.
|
||||
|
||||
Per D-020, UI components should not mutate shared state or call sibling autoloads directly — they should emit signals and let a coordinator (main.gd or a future SnapshotEventRouter) manage cross-component state.
|
||||
|
||||
What this ticket must deliver:
|
||||
- Replace the 3 `GameState.dialogue_active = true/false` assignments with a signal: `signal dialogue_state_changed(active: bool)`. `main.gd` connects to this signal and updates `GameState.dialogue_active`.
|
||||
- Replace `AudioManager.apply_dip("dialogue")` and `AudioManager.apply_dip("confrontation")` / `AudioManager.clear_dip()` calls with signals: `signal audio_dip_requested(profile: String)` and `signal audio_dip_cleared()`. `main.gd` connects to these and calls `AudioManager`.
|
||||
- Result: `dialogue_box.gd` has zero references to `GameState` or `AudioManager`.
|
||||
- Unit tests: mock signal receivers capture the emitted signals with correct arguments; no direct autoload calls remain.
|
||||
|
||||
Integration points: `client/ui/dialogue_box.gd` (source), `client/scripts/main.gd` (connects to new signals in `_ready()`). No server changes.
|
||||
|
||||
Gotcha: `InputMapper` checks `GameState.dialogue_active` to suppress movement. The signal path adds one frame of latency — verify that the signal fires synchronously within the same frame (use `call_immediate` or connect with `CONNECT_DEFERRED` depending on timing requirements). The `is_dialogue_active()` method on `dialogue_box.gd` (line 337) can remain as a local query without touching `GameState`.
|
||||
|
||||
### #559 — Refactor: main.gd god coordinator — extract SnapshotEventRouter
|
||||
|
||||
Code review finding: `client/scripts/main.gd` is 517 lines and dispatches to 15+ child nodes through a set of `consume_*` methods that all follow the same pattern: read field from snapshot, call method on child node.
|
||||
|
||||
What this ticket must deliver:
|
||||
- Extract a `SnapshotEventRouter` class (`client/scripts/snapshot_event_router.gd`): takes the snapshot dictionary and routes each field to the correct child node via a registered handler map.
|
||||
- Registration pattern: `router.register("monologue", monologue_display.consume_monologue)` — callable-based dispatch. Handlers are registered in `main.gd`'s `_ready()`.
|
||||
- `main.gd` `_process()` calls `router.dispatch(snapshot)` instead of 15+ individual `if snapshot.has("X"): child.consume_X()` blocks.
|
||||
- `main.gd` retains scene tree ownership (`@onready` node references), camera logic, and input handling — the router only handles snapshot dispatch.
|
||||
- After: `main.gd` should be under 350 lines.
|
||||
- Unit tests: construct a `SnapshotEventRouter` with mock handlers, dispatch a snapshot, assert each handler received the correct field value.
|
||||
|
||||
Integration points: `client/scripts/main.gd` (refactor target), new file `client/scripts/snapshot_event_router.gd`. No server changes, no protocol changes.
|
||||
|
||||
Gotcha: Some consume methods in `main.gd` have cross-field dependencies (e.g., camera position depends on both `player_position` and `_camera_anchored` state). Identify these upfront and keep them in `main.gd` directly — only pure per-field dispatch moves to the router. Do not force all logic into the router pattern.
|
||||
|
||||
### #560 — Refactor: unify duplicate YAML parsers
|
||||
|
||||
Code review finding: `client/scripts/checklist/checklist_evaluator.gd` contains its own YAML parser that partially duplicates `client/scripts/autoloads/ui_strings.gd`'s `_parse_yaml()` method.
|
||||
|
||||
What this ticket must deliver:
|
||||
- Extract a shared `YamlParser` utility class at `client/scripts/util/yaml_parser.gd` (create the `util/` directory).
|
||||
- `YamlParser` exposes a static method `parse(text: String) -> Dictionary` that handles the common subset of YAML used across both call sites (key: value pairs, nested maps, arrays).
|
||||
- Replace `checklist_evaluator.gd`'s inline parser with `YamlParser.parse()`.
|
||||
- Replace `ui_strings.gd`'s `_parse_yaml()` with `YamlParser.parse()` (or delegate to it, keeping the method signature stable).
|
||||
- Unit tests: parse a sample YAML string with nested keys, arrays, and string values; assert round-trip correctness.
|
||||
|
||||
Integration points: `client/scripts/checklist/checklist_evaluator.gd`, `client/scripts/autoloads/ui_strings.gd`, new `client/scripts/util/yaml_parser.gd`. No server changes.
|
||||
|
||||
Gotcha: The two existing parsers may handle edge cases differently. Write the unit tests first against both parsers to document their current behavior, then unify. Prioritize correctness for existing content files (`client/data/ui-strings.yaml` and any checklist YAML files) — do not break live content.
|
||||
|
||||
## Dependency Chain
|
||||
|
||||
```
|
||||
#557 (game_state derived state) ─┐
|
||||
#558 (dialogue_box coupling) ├─ all parallel, no inter-dependency
|
||||
#559 (main.gd SnapshotEventRouter)│ #558 feeds into #559 (signal wiring in main.gd)
|
||||
#560 (unify YAML parsers) ─┘
|
||||
```
|
||||
|
||||
#558 should complete before #559 so that the new signals from dialogue_box are wired into `main.gd` as part of the router work, not as a separate pass. Otherwise all four tickets run in parallel.
|
||||
|
||||
## PR Workflow
|
||||
|
||||
When ready to submit, create a PR with the `tea` CLI. All flags are required to avoid TTY prompts:
|
||||
|
||||
```bash
|
||||
tea pr create --repo jpmschweitzer/settled-reach --login schweitz \
|
||||
--title "feat(client): save/load UI and code quality refactors" \
|
||||
--description "body" --base main --head client
|
||||
```
|
||||
@@ -0,0 +1,74 @@
|
||||
# Sprint 20: Shape — Joint Coordination
|
||||
|
||||
**Goal:** The social site template system gains its foundational schema; triangles become generatable and observable as escalating tensions; the client gains save/load UI and code quality improvements.
|
||||
|
||||
## Pre-Sprint Decisions
|
||||
|
||||
No blocking pre-sprint decisions are required. All selected tickets have their upstream decisions confirmed.
|
||||
|
||||
| Decision | Status | Impact |
|
||||
|----------|--------|--------|
|
||||
| D-087 (v0.1 triangle configuration) | Confirmed | Server #106/#107/#250 must produce T1-T5 triangle types |
|
||||
| D-089 (self-contained forks, no cascade) | Confirmed | No cross-triangle state in `TriangleDef` or `TriangleState` |
|
||||
| D-025 (social site as atomic template unit) | Confirmed | Server #163/#164/#165 schema shapes |
|
||||
| D-020 (Godot = pure renderer) | Confirmed | Client refactors #557-#560 are motivated by this |
|
||||
|
||||
**One open question to monitor:**
|
||||
- **Q-028 (line ID collision)** — resolved by D-084 (dual-namespace scheme), but the `RoleCounter` implementation is referenced in D-084 as a requirement for `server/src/content/npc_slug.rs`. Ticket #163 (role definition schema) should create the `server/data/templates/` directory structure; confirm with server team whether the slug counter module belongs in this sprint or the next.
|
||||
|
||||
## Sprint Completion Proof
|
||||
|
||||
The sprint is done when all of the following are observable:
|
||||
|
||||
1. **Template schema compiles and round-trips**: `cargo test -p server -- template` passes. A YAML file at `server/data/templates/sample_role.yaml` deserializes cleanly into a `RoleSchema` struct and re-serializes with identical content.
|
||||
|
||||
2. **Triangle generation produces valid state**: `cargo test -p server -- triangle` passes. A 4-NPC test world with 2 `TriangleDef` entries produces 2 `TriangleState` components with valid role assignments and tension values within the configured range.
|
||||
|
||||
3. **Triangle escalation fires events**: A unit test simulates 60 ticks on a triangle configured to escalate at tick 50, asserts `TriangleCrisisEvent` was emitted at the correct tick.
|
||||
|
||||
4. **Single-ownership model serializes**: A world with 2 templates and a cross-reference survives a save/load round-trip: `TemplateOwnership` components and `TemplateReferenceMap` entries are identical before and after.
|
||||
|
||||
5. **Refactors don't regress tests**: `make ci-client` passes with #557-#560 merged. `game_state.apply_snapshot()` contains no conditional accumulation logic. `dialogue_box.gd` has zero direct references to `GameState` or `AudioManager`. `main.gd` is under 350 lines.
|
||||
|
||||
6. **District layout design decided**: #153 produces a confirmed D-record specifying: complete district topology (how terminal, bar, smuggling corridors, and gate connect), tile dimensions per zone, access topology, and sightline constraints. Unblocks #155 (hand-crafted location authoring) and #188 (triangle instantiation).
|
||||
|
||||
## Test Plan Alignment (D-030)
|
||||
|
||||
Sprint 20 is Phase 3+ territory (D-030 sub-decision #8: Phase 3 = sprint 5+: CauseChain verification + divergent snapshots). The new template/triangle system introduces the first simulation structures that will eventually require CauseChain verification.
|
||||
|
||||
| Ticket | Test scope | Priority |
|
||||
|--------|------------|----------|
|
||||
| #163 | Unit: YAML round-trip, constraint validation | High |
|
||||
| #164 | Unit: spec validation, tile count range | High |
|
||||
| #165 | Unit: ownership component, reference map | High |
|
||||
| #106 | Unit: triangle YAML round-trip, conflict validation | High |
|
||||
| #107 | Unit: constraint satisfaction, minimum 2 triangles | High |
|
||||
| #250 | Unit: escalation timing, crisis event emission, resolve command | High |
|
||||
| #557-#560 | Regression: existing test suite must remain green | Medium |
|
||||
| #153 | Design discussion: district layout confirmed as D-record | High |
|
||||
|
||||
The triangle system's `TriangleCrisisEvent` is the first event candidate for CauseChain integration. Do not wire CauseChain this sprint — but structure the event type so it can carry a `CauseChain` field in a future sprint without breaking callsites.
|
||||
|
||||
## Cross-Team Integration Points
|
||||
|
||||
| Server ticket | Client dependency | Notes |
|
||||
|---------------|-------------------|-------|
|
||||
| #165 (`TemplateOwnership` serialization) | None this sprint | Adds fields to `SaveStateV1` — no client protocol change needed until template data is rendered |
|
||||
| #250 (`TriangleCrisisEvent` in `ObserverSnapshot`) | None this sprint | Event added to snapshot schema as stub — client rendering of triangle state is Sprint 21+ |
|
||||
|
||||
| Planning ticket | Downstream impact | Notes |
|
||||
|-----------------|-------------------|-------|
|
||||
| #153 (district layout design) | Unblocks #155, #188 | Layout decisions feed into Sprint 21 hand-crafted location authoring and triangle instantiation |
|
||||
|
||||
No live cross-team protocol dependencies this sprint. Server and client work in parallel.
|
||||
|
||||
## Deferred to Sprint 21
|
||||
|
||||
The following tickets are natural Sprint 21 candidates once this sprint's foundation lands:
|
||||
|
||||
- **#166** (Template-to-instance mapping) — instantiate templates into the world; requires #163+#164+#165
|
||||
- **#161** (Template instantiation engine) — full NPC spawn from template; requires #163+#164+#165+#166
|
||||
- **#159** (Tier 2 template definition format) — YAML schema for the full template document; blocked by #163
|
||||
- **#108** (Cross-template triangle generation) — requires #106+#107
|
||||
- **#109** (Triangle validation) — quality checks on generated triangles; requires #107
|
||||
- **#155** (Hand-crafted location authoring) — requires #153 (station district layout design), which is being resolved this sprint on the planning branch
|
||||
@@ -0,0 +1,82 @@
|
||||
# Sprint 20: Shape — Planning Tasks
|
||||
|
||||
**Goal:** Resolve the station district layout design through structured discussion, producing a confirmed D-record that unblocks Sprint 21 location authoring and triangle instantiation.
|
||||
|
||||
**Branch:** `planning`
|
||||
**Agents:** Gestalt (systems design), Miri (worldbuilding), Araminta (visual/spatial), Tyre (technical feasibility), Paula (narrative), Ozzie (player experience), Qatux (documenter), SI (project manager)
|
||||
|
||||
## Tickets
|
||||
|
||||
| # | Title | Type | Blocks |
|
||||
|---|-------|------|--------|
|
||||
| #153 | Station district layout design | design discussion | #155, #188 |
|
||||
|
||||
## Discussion Format
|
||||
|
||||
Ticket #153 is a **design discussion** — workshop-style, run on the planning branch. The output is a confirmed decision record (D-record) in `decisions/content.md` or `decisions/architecture.md`.
|
||||
|
||||
### Context: What Already Exists
|
||||
|
||||
Three spatial layouts have been authored (all by Araminta, Sprint 17):
|
||||
- **The Terminal** (logistics hub): `docs/design/spatial-layout-terminal-v01.md` — 44×28 tiles, cool grey-navy
|
||||
- **The Last Shift** (bar): `docs/design/spatial-layout-bar-v01.md` — 28×22 tiles, warm dark amber
|
||||
- **Smuggling corridors**: `docs/design/spatial-layout-smuggling-corridors-v01.md` — overlay on terminal + bar + maintenance corridors
|
||||
|
||||
Station profile: `docs/design/sova-station-profile.md` — defines 6 districts, only Transit District is playable in v0.1.
|
||||
|
||||
Key decisions already confirmed:
|
||||
- D-025: Social site / functional cluster as atomic template unit
|
||||
- D-036: Sova Transit District / Krenn System as v0.1 setting
|
||||
- D-050: Velen naming and climate
|
||||
|
||||
Open question: Q-036 (district skeleton as generator output) — relevant but not blocking; the v0.1 district is hand-authored.
|
||||
|
||||
### What #153 Must Decide
|
||||
|
||||
The individual locations exist as standalone layouts. What's missing is **how they connect** — the district as a whole:
|
||||
|
||||
1. **District topology**: How do the terminal, bar, gate corridor cluster, and smuggling hideout spaces relate spatially? What corridors connect them? What's the walking distance/time between key locations?
|
||||
|
||||
2. **Gate corridor cluster** (#157): The span gate area — customs, cargo staging, commuter flow. This is the district's entry point and a social chokepoint. Needs spatial spec at the same fidelity as the terminal and bar.
|
||||
|
||||
3. **Access topology**: Public → semi-restricted → restricted zones. How does the access gradient map across the whole district? Where are the boundaries the player must navigate?
|
||||
|
||||
4. **Sightline constraints**: Which locations have line-of-sight to which? This is gameplay-critical — the player's observation opportunities depend on where they can see from where.
|
||||
|
||||
5. **NPC traffic patterns**: How do NPCs flow through the district? Shift changes, commuter routes, social gathering patterns. The spatial layout determines what the player can observe by being in the right place at the right time.
|
||||
|
||||
6. **Total district dimensions**: What's the bounding box? How does tile count affect performance (server spatial queries, client rendering)?
|
||||
|
||||
### Discussion Rounds
|
||||
|
||||
**Round 1 — Inventory and constraints**
|
||||
Each agent reviews the existing layouts and states what their domain requires from the district layout. Gestalt: gameplay loops that need spatial support. Miri: setting consistency, what the station profile implies. Araminta: visual continuity across zones, tilemap feasibility. Tyre: performance constraints, tilemap size limits. Paula: narrative beats that need specific spatial staging. Ozzie: navigation feel, does the district feel explorable and readable.
|
||||
|
||||
**Round 2 — Topology proposals**
|
||||
Propose concrete district maps (ASCII or description). How do the existing layouts connect? Where does the gate corridor go? What fills the space between authored locations?
|
||||
|
||||
**Round 3 — Convergence**
|
||||
Resolve conflicts, pick a topology, specify dimensions. Draft the D-record.
|
||||
|
||||
### Output
|
||||
|
||||
- A confirmed D-record specifying:
|
||||
- District topology diagram (which locations connect to which, via what corridors)
|
||||
- Approximate tile dimensions per zone and total district
|
||||
- Access topology (public/semi-restricted/restricted gradient)
|
||||
- Key sightline relationships
|
||||
- Gate corridor cluster spatial spec (or a separate ticket if too large)
|
||||
- Updated `decisions/` domain file
|
||||
- Gate corridor layout doc at `docs/design/spatial-layout-gate-v01.md` if produced
|
||||
|
||||
### Reference Files
|
||||
|
||||
Read before starting:
|
||||
- `docs/design/spatial-layout-terminal-v01.md`
|
||||
- `docs/design/spatial-layout-bar-v01.md`
|
||||
- `docs/design/spatial-layout-smuggling-corridors-v01.md`
|
||||
- `docs/design/sova-station-profile.md`
|
||||
- `decisions/content.md` — D-025 (social sites), D-036 (Sova setting)
|
||||
- `decisions/architecture.md` — D-014 (tile-based movement)
|
||||
- `decisions/perception.md` — D-059 (fog layers, zone temperature)
|
||||
- `decisions/questions.md` — Q-036 (district skeleton as generator output)
|
||||
@@ -0,0 +1,147 @@
|
||||
# Sprint 20: Shape — Server Tasks
|
||||
|
||||
**Goal:** The social site template system gains its foundational schema; triangles become generatable and observable as escalating tensions; the client gains save/load UI and code quality improvements.
|
||||
|
||||
**Branch:** `server`
|
||||
**Agents:** Dudley (simulation), Tyre (architecture), Hoshe (QA)
|
||||
|
||||
## Carry-over from Sprint 19
|
||||
|
||||
None. Sprint 19 treated as complete.
|
||||
|
||||
## New Tickets
|
||||
|
||||
| # | Title | Blocked by |
|
||||
|---|-------|------------|
|
||||
| #163 | Role definition schema | — |
|
||||
| #164 | Spatial requirement specification | — |
|
||||
| #165 | Single-ownership model | — |
|
||||
| #106 | Triangle definition schema | — |
|
||||
| #107 | Intra-template triangle generation | — |
|
||||
| #250 | Triangle escalation system | — (#103, #105 done) |
|
||||
|
||||
Use `db/connectors/ticket show <id>` for full details.
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- `decisions/content.md` — D-023 (three-tier content model: Tier 1 drama modules, Tier 2 templates, Tier 3 procedural), D-024 (NPC generation model: 10 axes, triangles as atomic social unit — 2 per template minimum), D-025 (social site / functional cluster as atomic template unit: 4-8 NPCs, 15-40 tiles, single-ownership with reference links), D-029 (population entanglement ratio: 30/50/20 — triangles are the 50% mundane layer)
|
||||
- `decisions/scope.md` — D-087 (v0.1 triangle configuration: T1 Kael-Smuggler-Ring, T2 Sera-Detective-Commission, T4 Drin-System-Ring as active forks; T3 and T5 as passive tensions), D-089 (self-contained triangle forks for v0.1, no cross-triangle cascade)
|
||||
- `decisions/architecture.md` — D-010 (deterministic simulation: BTreeMap for all collections, no HashMap), D-026 (simulation tiers: Active-tier NPCs are fully simulated; template instantiation populates Active tier), D-041 (KnowledgeGraph: per-entity component — template instantiation must assign KnowledgeGraph to each spawned NPC)
|
||||
|
||||
## Notes
|
||||
|
||||
### #163 — Role definition schema
|
||||
|
||||
The `RoleDefinition` struct already exists in `server/src/npc/generate.rs` as a procedural generation input — it defines `name`, location pool entries, and per-axis ranges. This ticket extends that to become the canonical Tier 2 role schema.
|
||||
|
||||
What this ticket must deliver:
|
||||
- A `RoleSchema` type (new, distinct from `RoleDefinition`) in a new `server/src/content/template/` module (or `server/src/content/types.rs` extended). Fields: `role_id: RoleId` (newtype over String), `required_traits: Vec<PersonalityTrait>`, `skill_focus: Vec<Skill>`, `relationship_constraints: Vec<RelationshipConstraint>`, `routine_template: Vec<RoutineEntry>` — these are constraints fed into the NPC generator, not hardcoded values.
|
||||
- A `RelationshipConstraint` type: `{ with_role: RoleId, kind: RelationshipKind, required_trust: TrustRange }`. Constrains who this role must be in relationship with within the same template.
|
||||
- YAML deserialization via `serde`. Schema files will live at `server/data/templates/` (create the directory).
|
||||
- Unit tests: round-trip YAML serialize/deserialize a sample role schema. Validate constraint logic (no self-referential constraints, no duplicate role_id within a template).
|
||||
|
||||
Integration points: `server/src/npc/generate.rs` (`RoleDefinition` → becomes a builder derived from `RoleSchema`), `server/src/content/types.rs` (existing content type infrastructure), `server/src/knowledge/types.rs` (`StableId`, `RelationshipKind`).
|
||||
|
||||
Gotcha: `RoleId` must be stable across save/load — it's a string slug, not a bevy `Entity`. Keep it a newtype over `String` so it serializes cleanly with `StableId`.
|
||||
|
||||
### #164 — Spatial requirement specification
|
||||
|
||||
No existing spatial specification type exists. This is greenfield within the template system.
|
||||
|
||||
What this ticket must deliver:
|
||||
- A `SpaceSpec` type: `{ tile_count_min: u32, tile_count_max: u32, sightline_zones: Vec<SightlineZone>, privacy_level: PrivacyLevel, traffic_pattern: TrafficPattern }`.
|
||||
- `SightlineZone`: a named sub-area with a coverage radius in sim tiles (0.5m each, per D-066). Example: `{ name: "bar_counter", radius: 4 }` — 4 sim tiles = 2m clear sightline.
|
||||
- `PrivacyLevel` enum: `Public`, `SemiPrivate`, `Private`. Governs NPC behavior (NPCs are less likely to disclose secrets in Public spaces).
|
||||
- `TrafficPattern` enum: `Thoroughfare`, `Destination`, `Restricted`. Governs procedural NPC routine routing through this space.
|
||||
- YAML deserialization. Schema files co-locate with role schemas at `server/data/templates/`.
|
||||
- Unit tests: sample spec round-trip, validation that min <= max tile count.
|
||||
|
||||
Integration points: `server/src/content/template/` (new module or extended `server/src/content/types.rs`), future chunk generation (`server/src/simulation/` — spatial specs will inform where templates are placed in the map). No simulation code changes needed this sprint — spec types only.
|
||||
|
||||
Gotcha: Tile counts are in sim tiles (0.5m). A 15-40 visual tile space (per D-025) = 30-80 sim tiles. Document this conversion explicitly in code comments to prevent future confusion.
|
||||
|
||||
### #165 — Single-ownership model
|
||||
|
||||
NPCs are owned by exactly one template, with reference links to others (D-025). No existing ownership component exists.
|
||||
|
||||
What this ticket must deliver:
|
||||
- A `TemplateOwnership` ECS component: `{ template_id: TemplateId, role_id: RoleId }`. Assigned at template instantiation, never reassigned.
|
||||
- A `TemplateId` newtype over `u64` — deterministic from world seed + template slug hash.
|
||||
- A `TemplateReference` struct: `{ from_template: TemplateId, to_template: TemplateId, via_role: RoleId, relationship_metadata: RelationshipKind }`. Stored in a `TemplateReferenceMap` resource (a `BTreeMap<TemplateId, Vec<TemplateReference>>`).
|
||||
- Logic for lifecycle coordination: when a template is unloaded (NPC tier drops to State-saved or Ungenerated per D-026), `TemplateReference` links are preserved in the serialized state, not destroyed.
|
||||
- Unit tests: spawn two templates with cross-references, verify `TemplateReferenceMap` entries, verify `TemplateOwnership` components.
|
||||
|
||||
Integration points: `server/src/simulation/tier.rs` (tier transitions must preserve `TemplateOwnership`), `server/src/simulation/save_state.rs` (serialize `TemplateOwnership` and `TemplateReferenceMap` as part of `SaveStateV1` — add fields), `server/src/npc/generate.rs` (generator receives `TemplateId` + `RoleId` at spawn time).
|
||||
|
||||
Gotcha: `TemplateId` from seed + slug hash must be deterministic across save/load — use `StdHasher` is prohibited (non-deterministic), use a seeded hash (e.g., `std::hash::Hasher` from a fixed algorithm) or simply hash the slug string bytes with a fixed polynomial. Log the `TemplateId` computed value in tests for debugging.
|
||||
|
||||
### #106 — Triangle definition schema
|
||||
|
||||
The D-024 spec says triangles are the atomic unit of social intrigue — 2 per template minimum, 1 cross-template. No `TriangleDef` type exists anywhere in the codebase.
|
||||
|
||||
What this ticket must deliver:
|
||||
- A `TriangleDef` type: `{ triangle_id: TriangleId, roles: [RoleId; 3], conflict_type: ConflictType, interest_axes: [NpcAxis; 3], relationship_constraints: Vec<RelationshipConstraint> }`. Three roles, each with a conflicting axis (Want, Secret, Tolerance, etc.).
|
||||
- `ConflictType` enum based on D-087 active fork patterns: `ResourceCompetition`, `LoyaltyConflict`, `SecretExposure`, `AuthorityChallenge`. Passive tensions use `LatentTension` variant.
|
||||
- `TriangleId` newtype over `u64` — deterministic from template seed + role triple.
|
||||
- Validation: all three roles must be distinct within the template; the conflict type must map to at least one axis divergence (no conflict on identical axis values).
|
||||
- YAML deserialization. Triangle definitions are authored as part of a template file or as a standalone `triangles.yaml` per template — Dudley to decide the co-location approach.
|
||||
- Unit tests: sample triangle round-trip, validation for duplicate roles, validation for self-consistent conflict.
|
||||
|
||||
Integration points: `server/src/content/template/` (lives alongside `RoleSchema` and `SpaceSpec`), `server/src/npc/generate.rs` (the generator will consume `TriangleDef` in #107 to assign axis values that produce the desired conflict), `decisions/content.md` D-087 (v0.1 triangles T1-T5 should be expressible in this schema).
|
||||
|
||||
Gotcha: D-089 — self-contained triangles for v0.1, no cross-triangle cascade. Do not add cross-triangle state fields to `TriangleDef`. Cross-template triangles are expressed by a `TriangleDef` that references a `RoleId` from a different `TemplateId` — the cross-template link is in the role, not a special triangle type.
|
||||
|
||||
### #107 — Intra-template triangle generation
|
||||
|
||||
The template system can now describe triangles (#106). This ticket generates them from the description.
|
||||
|
||||
What this ticket must deliver:
|
||||
- A `generate_intra_template_triangles(world: &mut World, template_id: TemplateId, defs: &[TriangleDef], rng: &mut SimRng) -> Vec<TriangleState>` function.
|
||||
- `TriangleState` ECS component: `{ triangle_id: TriangleId, role_assignments: BTreeMap<RoleId, StableId>, tension: u8, phase: TrianglePhase }`. `tension` starts at a seeded value within a configured range. `TrianglePhase` enum: `Dormant`, `Simmering`, `Active`, `Resolved`.
|
||||
- Constraint satisfaction: for each `TriangleDef`, assign generated NPCs (by `StableId`) to the three roles. Validate that the NPC's axis values satisfy the conflict (e.g., for a `LoyaltyConflict`, the NPC filling the `loyalty_torn` role must have a Relationships axis with entries for both of the other two roles).
|
||||
- Minimum 2 triangles per template — emit an error (not a panic) if the template definition provides fewer than 2 `TriangleDef` entries.
|
||||
- Unit tests: spawn a 4-NPC template, generate 2 triangles, assert `TriangleState` components exist and role assignments are valid, assert constraint satisfaction.
|
||||
|
||||
Integration points: `server/src/npc/generate.rs` (NPC generation runs first; triangle generation consumes the generated NPCs' axis values), `server/src/content/template/` (#106 types), `server/src/simulation/rng.rs` (`SimRng` for determinism).
|
||||
|
||||
Gotcha: Constraint satisfaction can fail if the NPC pool doesn't provide a suitable candidate for a role. Implement a fallback: if no NPC satisfies the strict constraint, pick the closest match and log a warning. Do not panic — world generation must be robust to imperfect seeds.
|
||||
|
||||
### #250 — Triangle escalation system
|
||||
|
||||
Blockers #103 (relationship dynamics) and #105 (tolerance threshold triggers) are done. `TriangleState` from #107 is available this sprint.
|
||||
|
||||
What this ticket must deliver:
|
||||
- An ECS system `tick_triangle_escalation` that runs once per game-minute (every 10 ticks per D-031). For each `TriangleState` in `Simmering` or `Active` phase: increment `tension` by a seeded per-triangle rate (drawn from `SimRng` at world-gen time, stored on `TriangleState`). When `tension` exceeds the lowest `ToleranceThreshold` among the triangle's three NPCs, transition `phase` from `Simmering` to `Active`.
|
||||
- Observable events: when a triangle enters `Active`, emit a `TriangleCrisisEvent` (new event type) containing `triangle_id`, `role_assignments`, and `trigger_npc: StableId`. The monologue system and knowledge system can subscribe to this event — but do not wire those subscribers this sprint. Emit the event; downstream consumption is future work.
|
||||
- `Resolved` transition: when the player resolves an active fork (mechanism TBD — stub a `ResolveTriangle(TriangleId)` command for now), set `phase = Resolved`. D-089: resolution does not cascade.
|
||||
- Unit tests: simulate 60 ticks on a triangle with a known tension rate, assert `Active` transition at the expected tick. Test `Resolved` command sets phase correctly.
|
||||
|
||||
Integration points: `server/src/simulation/tier.rs` (`tick_triangle_escalation` only runs on Active-tier NPCs per D-026), `server/src/simulation/time.rs` (game-minute scheduler — 10-tick interval), `server/src/npc/tolerance.rs` (`ToleranceThreshold` component), `server/src/npc/relationships.rs` (`RelationshipGraph` — tension rate influenced by relationship stress), `server/src/bridge/types.rs` (add `TriangleCrisisEvent` to `ObserverSnapshot` for future client rendering).
|
||||
|
||||
Gotcha: Different seeds produce different tolerance thresholds — the same triangle template can escalate in 5 minutes or 30 minutes depending on the seed. This is intentional (D-087). Do not hardcode a tension rate — it must come from `SimRng` at world-gen time and be stored on the component.
|
||||
|
||||
## Dependency Chain
|
||||
|
||||
```
|
||||
#163 (Role definition schema) ─┐
|
||||
#164 (Spatial requirement spec) ├─ parallel, no inter-dependency
|
||||
#165 (Single-ownership model) ─┘
|
||||
│
|
||||
└─ feeds #166 (Template-to-instance mapping, Sprint 21)
|
||||
|
||||
#106 (Triangle definition schema) ──► #107 (Intra-template generation) ──► #250 (Escalation system)
|
||||
│
|
||||
└─ feeds #108 (Cross-template generation, Sprint 21)
|
||||
```
|
||||
|
||||
#163/#164/#165 and #106/#107/#250 are two parallel tracks. All six tickets can begin in week 1; #107 and #250 gate on #106 completing first.
|
||||
|
||||
## PR Workflow
|
||||
|
||||
When ready to submit, create a PR with the `tea` CLI. All flags are required to avoid TTY prompts:
|
||||
|
||||
```bash
|
||||
tea pr create --repo jpmschweitzer/settled-reach --login schweitz \
|
||||
--title "feat(simulation): social site template schema and triangle system" \
|
||||
--description "body" --base main --head server
|
||||
```
|
||||
@@ -0,0 +1,49 @@
|
||||
# Sprint 21: Instantiate — CI Tasks
|
||||
|
||||
**Goal:** The template system becomes executable — templates spawn NPCs, assign triangles, and place them in world space; cross-template triangles link social sites; the client gains save/load game flow; and the generator pipeline gets its architectural design.
|
||||
|
||||
**Branch:** `ci`
|
||||
**Agents:** Justine (build/deploy)
|
||||
|
||||
## New Tickets
|
||||
|
||||
| # | Title | Blocked by |
|
||||
|---|-------|------------|
|
||||
| #274 | Move connector scripts from db/connectors/ to tooling/db/ | — |
|
||||
|
||||
Use `db/connectors/ticket show <id>` for full details.
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- `decisions/process.md` — tooling conventions and project structure
|
||||
|
||||
## Notes
|
||||
|
||||
**#274 — Move connector scripts from db/connectors/ to tooling/db/**
|
||||
- Current location: `db/connectors/` — contains `sqlite_connector.py`, `qdrant_connector.py`, `config.json`, and all wrapper scripts (`sqlite-query`, `sqlite-exec`, `sqlite-init`, `sqlite-seed`, `qdrant-search`, `qdrant-index`, `qdrant-health`, `qdrant-count`, `ticket`, `sprint`, `decision`).
|
||||
- Target location: `tooling/db/` — consolidates all tooling under `tooling/` per project structure conventions. The `db/` directory retains schema and seed data only.
|
||||
- Steps:
|
||||
1. Create `tooling/db/` directory.
|
||||
2. Move all scripts and `config.json`. Preserve executable bits (`chmod +x` on wrapper scripts).
|
||||
3. Update `CLAUDE.md` table ("CLI tools" section) to reference new paths.
|
||||
4. Update `docs/DEVOPS.md` if it references `db/connectors/` paths.
|
||||
5. Update any `Makefile` targets that call `db/connectors/` directly.
|
||||
6. Update agent briefings and skill files that reference `db/connectors/` paths — check `.claude/skills/` and `.claude/agents/`.
|
||||
7. Leave a `db/connectors/` stub or symlink pointing to `tooling/db/` if any external scripts depend on the old path. Remove after one sprint.
|
||||
- Do NOT move `db/schema.sql`, `db/seed.sql`, or `settledreach.db` — those stay in `db/`.
|
||||
- The `settledreach.db` lives in the parent directory (`../settledreach.db` relative to the worktree root) and is not tracked in git — no change needed there.
|
||||
- Acceptance: `make ci` passes. `db/connectors/ticket list` either works via symlink or has been replaced by `tooling/db/ticket list` everywhere it is referenced.
|
||||
|
||||
## Dependency Chain
|
||||
|
||||
```
|
||||
#274 (connector script move) — standalone
|
||||
```
|
||||
|
||||
## PR Workflow
|
||||
|
||||
When ready to submit, create a PR with `tea` CLI:
|
||||
|
||||
```bash
|
||||
tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "chore(ci): move db/connectors/ to tooling/db/" --description "body" --base main --head ci
|
||||
```
|
||||
@@ -0,0 +1,56 @@
|
||||
# Sprint 21: Instantiate — Client Tasks
|
||||
|
||||
**Goal:** The template system becomes executable — templates spawn NPCs, assign triangles, and place them in world space; cross-template triangles link social sites; the client gains save/load game flow; and the generator pipeline gets its architectural design.
|
||||
|
||||
**Branch:** `client`
|
||||
**Agents:** Stig (dev), Hoshe (QA)
|
||||
|
||||
## New Tickets
|
||||
|
||||
| # | Title | Blocked by |
|
||||
|---|-------|------------|
|
||||
| #257 | Save/load game flow | — (#256 done) |
|
||||
| #561 | Housekeeping: move debug_overlay.gd to ui/ directory | — |
|
||||
|
||||
Use `tooling/db/ticket show <id>` for full details on any ticket.
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- `decisions/architecture.md` — D-020 (Godot = pure renderer, no game logic in GDScript), D-085 (per-game save directories under `user://saves/<game-id>/`), D-088 (3-state pause: Normal/Overlay/Paused, server-authoritative)
|
||||
- `decisions/scope.md` — D-027 (vertical slice: smuggler + detective, two-character proof)
|
||||
|
||||
## Notes
|
||||
|
||||
**#257 — Save/load game flow**
|
||||
- Server-side serialization (`SaveStateV1`, `SaveLoadCommand`) landed in Sprint 19 (#553, #553). The client `SessionManager` autoload (`client/scripts/autoloads/session_manager.gd`) already creates per-game directories and tracks `current_game_id`. The `input.rs` server-side hook for `SaveLoadCommand::Save` and `SaveLoadCommand::Load` is in place.
|
||||
- What's missing: the client UI flow — save-to-file and load-from-file screens, and F5/F6 quicksave/quickload keybinds wired to `PlayerInput`.
|
||||
- `client/ui/main_menu.gd` exists. Add a "Load Game" screen that calls `SessionManager.list_game_dirs()` and lets the player select a save.
|
||||
- F5 quicksave flow: send `PlayerInput { action: QuickSave }` → server responds with serialised save data → client writes to `user://saves/<game-id>/quicksave.sav`. F6 quickload: reverse.
|
||||
- Loading screen: a minimal full-screen overlay ("Resuming...") during the round-trip to prevent input during load. No elaborate animation needed for v0.1.
|
||||
- D-020 constraint: no game logic in client. The client never constructs save data — it only sends the command and receives the file bytes from the server.
|
||||
- Existing stub in `session_manager.gd` line 71 notes: "The actual F5 save will be wired here once server supports SaveCommand." Server supports it now — wire it.
|
||||
- Acceptance: (1) F5 in-game triggers quicksave, file appears at correct path. (2) F6 reloads it, player position and NPC state match save. (3) Main menu "Load Game" lists existing saves sorted by date.
|
||||
|
||||
**#561 — Housekeeping: move debug_overlay.gd to ui/ directory**
|
||||
- `client/scripts/ui/debug_overlay.gd` is the odd one out — all 17 other UI components live in `client/ui/`. This was flagged in a code review.
|
||||
- Steps: move `client/scripts/ui/debug_overlay.gd` (and its `.uid` file) to `client/ui/debug_overlay.gd`. Update any `preload()` or `load()` references. Update the `.tscn` that instances it if one exists.
|
||||
- Check `client/scripts/rendering/` and `client/scripts/autoloads/` for any imports of the old path.
|
||||
- If moving would break more than 3 references and the distinction is intentional (debug overlay is a script, not a scene-based UI), document the distinction in a comment at the top of the file instead, and close the ticket as "documented not moved."
|
||||
- Acceptance: `make ci-client` passes with the file at its new location, or the distinction is documented in-file.
|
||||
|
||||
## Dependency Chain
|
||||
|
||||
```
|
||||
#257 (save/load game flow) — standalone
|
||||
#561 (debug_overlay housekeeping) — standalone, parallel
|
||||
```
|
||||
|
||||
Both tickets are independent and can be developed in parallel.
|
||||
|
||||
## PR Workflow
|
||||
|
||||
When ready to submit, create a PR with `tea` CLI:
|
||||
|
||||
```bash
|
||||
tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(client): Sprint 21 save/load game flow" --description "body" --base main --head client
|
||||
```
|
||||
@@ -0,0 +1,116 @@
|
||||
# Sprint 21: Instantiate — Joint Coordination
|
||||
|
||||
**Goal:** The template system becomes executable — templates spawn NPCs, assign triangles, and place them in world space; cross-template triangles link social sites; the client gains save/load game flow; and the generator pipeline gets its architectural design.
|
||||
|
||||
## Pre-Sprint Decisions
|
||||
|
||||
No blocking pre-sprint decisions are required. All selected implementation tickets have their upstream decisions confirmed.
|
||||
|
||||
| Decision | Status | Impact |
|
||||
|----------|--------|--------|
|
||||
| D-025 (social site as atomic template unit) | Confirmed | Server #159/#166/#161 schema and instantiation shapes |
|
||||
| D-024 (NPC 10-axis model, 2 triangles minimum per template, 1 cross-template) | Confirmed | Server #108/#109 triangle requirements |
|
||||
| D-087 (v0.1 triangle config: 3 active forks, 2 passive tensions) | Confirmed | #108 must produce triangle types consistent with T1-T5 fork taxonomy |
|
||||
| D-089 (self-contained forks, no cross-triangle cascade) | Confirmed | #108 cross-template triangle uses reference links, not shared state |
|
||||
| D-085 (per-game save directories) | Confirmed | Client #257 save/load flow writes to `user://saves/<game-id>/` |
|
||||
| D-020 (Godot = pure renderer) | Confirmed | Client #257 never constructs save data — sends command, receives file bytes |
|
||||
| D-059 (fog: five layers, shader-based) | Confirmed | Visual #564 tuning target — alpha values must match D-059 layer specs |
|
||||
| D-066 (dual-scale grid, 6-8 sim tile fog gradient) | Confirmed | Visual #564 must not alter `CLEAR_THRESHOLD`/`PERIPHERAL_LOW` constants that define gradient width |
|
||||
|
||||
**One open question to monitor:**
|
||||
- **Q-036 (district skeleton as generator output)** — actively being resolved in #562 this sprint. Resolution will gate #144 (Chunk generation system) and shape the Sprint 22 map pipeline tickets. SI will create follow-up tickets once #562 closes.
|
||||
|
||||
## Planning Ticket
|
||||
|
||||
| # | Title | Team | Agents |
|
||||
|---|-------|------|--------|
|
||||
| #562 | Generator architecture workshop — district/block/chunk pipeline | planning | Gestalt, Tyre, Miri, Araminta, Nigel, Qatux, SI |
|
||||
|
||||
**#562 — Generator architecture workshop**
|
||||
|
||||
Workshop brief: `docs/workshops/generator-architecture/workshop-brief.md`
|
||||
|
||||
**Purpose:** Establish the top-down procedural generator pipeline architecture — from geography down to individual chunk fill — that will power the 300-world model. The v0.1 Transit District is hand-authored; this workshop defines what the generator must be able to reproduce and what stub interfaces v0.1 must leave behind.
|
||||
|
||||
**Context documents to read before the discussion:**
|
||||
- `decisions/content.md` — D-025 (social site template as atomic unit)
|
||||
- `decisions/scope.md` — D-012 (chunk-based map system), D-036 (Sova as v0.1 setting)
|
||||
- `decisions/questions.md` — Q-036 (district skeleton as generator output), Q-037 (generator pipeline phases), Q-039 (gate topology generation)
|
||||
- `docs/design/sova-station-profile.md` — district types and 6-district layout
|
||||
- `docs/design/spatial-layout-terminal-v01.md`, `docs/design/spatial-layout-bar-v01.md` — hand-authored chunk cluster examples
|
||||
- `decisions/architecture.md` — D-093/D-094 (Sova spatial hierarchy: chunk/block/district naming and sizes)
|
||||
|
||||
**Three rounds:**
|
||||
1. **Domain Inventory** — each participant states what their domain requires from the generator (Gestalt: gameplay loop guarantees; Tyre: technical constraints on chunk size and hierarchy depth; Miri: cultural/economic variation inputs for 300 worlds; Araminta: visual coherence constraints on chunk fill and sub-chunk quarter system; Nigel: variation and replayability guarantees).
|
||||
2. **Pipeline Proposals** — propose pipeline stages, name the spatial hierarchy levels with tile dimensions, describe the district skeleton data structure.
|
||||
3. **Convergence** — resolve conflicts, lock spatial hierarchy, define district skeleton output format, set v0.1/generator boundary, draft D-record.
|
||||
|
||||
**Required outputs:**
|
||||
- D-record in `decisions/architecture.md`: pipeline stages, spatial hierarchy, sub-chunk quarter rules, multi-block reservation protocol, district skeleton data structure, v0.1/generator boundary
|
||||
- Resolution of Q-036 (district skeleton as atomic output — yes/no + formal definition)
|
||||
- Resolution of Q-037 scope (which phases land in which version window)
|
||||
- Follow-up implementation tickets: chunk data structure update (#143), district skeleton schema, zoning pass stub, block generation stub
|
||||
|
||||
**SI role in this workshop:** Create follow-up tickets from the D-record outputs and assign to Sprint 22 candidates. Update Q-036 and Q-037 status in `decisions/questions.md`.
|
||||
|
||||
## Sprint Completion Proof
|
||||
|
||||
The sprint is done when all of the following are observable:
|
||||
|
||||
1. **Template instantiation pipeline end-to-end:** Load a Tier 2 YAML from `server/data/templates/`, call the instantiation engine, assert NPCs spawn with correct roles, `TemplateOwnership` set, and 2+ `TriangleState` components generated. `cargo test -p server -- instantiation` passes.
|
||||
|
||||
2. **Cross-template triangle produced:** A test world with two instantiated templates (logistics hub + bar) generates exactly 1 cross-template `TriangleState` with role assignments spanning both templates. `cargo test -p server -- cross_template_triangle` passes.
|
||||
|
||||
3. **Triangle validation catches bad inputs:** Unit tests confirm that a triangle failing conflict viability, relationship coherence, or interest divergence returns a typed `ValidationError`, not a panic.
|
||||
|
||||
4. **Save/load round-trip works end-to-end:** F5 in-game writes a quicksave file to `user://saves/<game-id>/quicksave.sav`. F6 reloads it. Player position and NPC state (including open doors from #246) match the save. `make ci-client` passes.
|
||||
|
||||
5. **Environmental interaction:** A Door entity in a test world toggles walkability on player interaction. An Examinable entity returns examine text. Door state survives a save/load round-trip (open_doors persists in `SaveStateV1`).
|
||||
|
||||
6. **Error handling does not crash:** Sending a malformed IPC message mid-session produces a structured `SimError` response and leaves the server running. Integration test asserts this.
|
||||
|
||||
7. **Fog shader tuned:** Fog Theater gauntlet room — entities in peripheral zone are visibly dimmed but not opaque; deep fog shows a readable zone temperature tint; vision cone edge is soft. `make ci-client` passes.
|
||||
|
||||
8. **Generator architecture decided:** #562 closes with a confirmed D-record, Q-036 marked resolved, Q-037 scope defined. SI creates Sprint 22 candidate tickets for the chunk/district pipeline implementation.
|
||||
|
||||
## Test Plan Alignment (D-030)
|
||||
|
||||
Sprint 21 is Phase 3+ territory. The template instantiation system introduces the first simulation structures that compose content from YAML definitions into live ECS entities.
|
||||
|
||||
| Ticket | Test scope | Priority |
|
||||
|--------|------------|----------|
|
||||
| #159 | Unit: YAML round-trip, full Tier 2 document | High |
|
||||
| #166 | Unit: spawn + TemplateOwnership, TemplateReferenceMap entries | High |
|
||||
| #161 | Integration: YAML → instantiation engine → ECS entities + triangles | High |
|
||||
| #108 | Unit: cross-template role assignment, reference link creation | High |
|
||||
| #109 | Unit: all three validation failure modes + passing case | High |
|
||||
| #85 | Integration: malformed input → SimError, server survives | High |
|
||||
| #246 | Unit: Door walkability toggle, examine text return | High |
|
||||
| #257 | End-to-end: F5 save → F6 load → state match | High |
|
||||
| #561 | Regression: `make ci-client` passes at new file path | Medium |
|
||||
| #564 | Visual: Fog Theater gauntlet room manual check | High |
|
||||
| #274 | Regression: `make ci` passes with scripts at new paths | High |
|
||||
|
||||
## Cross-Team Integration Points
|
||||
|
||||
| Server ticket | Client dependency | Notes |
|
||||
|---------------|-------------------|-------|
|
||||
| #246 (`open_doors` in `SaveStateV1`) | #257 (save/load round-trip) | Server must add `open_doors: Vec<StableId>` to save struct before client can verify state restored correctly |
|
||||
| #85 (`SimError` message type) | #257 (loading screen) | Loading screen should handle `SimError` gracefully — show error state, not spinner forever |
|
||||
|
||||
| Planning ticket | Downstream impact | Notes |
|
||||
|-----------------|-------------------|-------|
|
||||
| #562 (generator architecture) | #143, #144 (chunk system) | D-record output gates Sprint 22 chunk pipeline work |
|
||||
|
||||
The #246/#257 dependency is soft — client can stub the door state verification in save/load tests until #246 lands.
|
||||
|
||||
## Deferred to Sprint 22
|
||||
|
||||
Natural Sprint 22 candidates once this sprint's foundation lands:
|
||||
|
||||
- **#155** (Hand-crafted location authoring) — build the v0.1 Transit District locations in Godot tilemap; requires #153 (done) and the district topology from that D-record
|
||||
- **#188** (Triangle instantiation in v0.1 content) — wire the 5 v0.1 triangles into the instantiation engine; requires #161
|
||||
- **#162** (Storyteller module activation) — draw Tier 1 modules from pool at game start; requires #161
|
||||
- **#143** (Chunk data structure) — blocked until #562 workshop defines the spatial hierarchy
|
||||
- **#176** (NPC pool generation: flat/mundane/entangled ratio) — requires #161 instantiation engine
|
||||
- Generator pipeline tickets — created by SI from #562 D-record outputs
|
||||
@@ -0,0 +1,91 @@
|
||||
# Sprint 21: Instantiate — Server Tasks
|
||||
|
||||
**Goal:** The template system becomes executable — templates spawn NPCs, assign triangles, and place them in world space; cross-template triangles link social sites; the client gains save/load game flow; and the generator pipeline gets its architectural design.
|
||||
|
||||
**Branch:** `server`
|
||||
**Agents:** Dudley (simulation dev), Tyre (arch), Hoshe (QA)
|
||||
|
||||
## New Tickets
|
||||
|
||||
| # | Title | Blocked by |
|
||||
|---|-------|------------|
|
||||
| #159 | Tier 2 template definition format | — (#158 done) |
|
||||
| #166 | Template-to-instance mapping | — (#163, #164, #165 done) |
|
||||
| #161 | Template instantiation engine | #166 |
|
||||
| #108 | Cross-template triangle generation | — (#106, #107 done) |
|
||||
| #109 | Triangle validation | — (#107 done) |
|
||||
| #85 | Error handling & recovery | — |
|
||||
| #246 | Basic environmental interaction | — |
|
||||
|
||||
Use `tooling/db/ticket show <id>` for full details on any ticket.
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- `decisions/content.md` — D-023 (three-tier content model), D-024 (NPC generation model, 10 axes), D-025 (social site as atomic template unit), D-028 (dialogue tagged pools), D-029 (population entanglement ratio)
|
||||
- `decisions/architecture.md` — D-020 (Godot + Rust IPC, ObserverSnapshot), D-010 (deterministic simulation), D-087 (v0.1 triangle configuration), D-088 (3-state pause, server-authoritative), D-089 (self-contained forks, no cascade)
|
||||
|
||||
## Notes
|
||||
|
||||
**#159 — Tier 2 template definition format**
|
||||
- `server/src/content/template.rs` already defines `RoleSchema`, `SpaceSpec`, `TriangleDef` (Sprint 20). `#158` (Tier 1 drama module schema) is done — use it as a reference for YAML conventions.
|
||||
- This ticket extends that to the full Tier 2 document: roles, spaces, triangles, dialogue pool references, NPC routines, and spatial spec in one YAML document.
|
||||
- `server/data/templates/` directory exists (created by #385). Place the canonical Tier 2 schema definition and at least one authored example template here.
|
||||
- Acceptance: a complete Tier 2 template YAML round-trips cleanly through `RoleSchema` / `SpaceSpec` deserialization.
|
||||
|
||||
**#166 — Template-to-instance mapping**
|
||||
- Depends on the schema from #159, but #159 is partially stubbed already — Dudley can start here in parallel if schema is stabilising.
|
||||
- Core task: given a loaded `TemplateOwnership` + `SpaceSpec`, spawn NPC entities for each role slot, assign relationships, and record the mapping in `TemplateReferenceMap`.
|
||||
- Existing hook: `server/src/content/spawn.rs`. The `TemplateOwnership` component (Sprint 20) already tracks which template owns an entity. This ticket wires spawning to that system.
|
||||
- Instance lifecycle: entities spawned from a template must be tagged such that they can be despawned/reset cleanly (gauntlet room reset pattern in `server/src/test_world/` is a reference).
|
||||
- Acceptance: unit test spawns a 4-NPC template, asserts all role slots filled, `TemplateOwnership` set correctly on each entity, `TemplateReferenceMap` entries present.
|
||||
|
||||
**#161 — Template instantiation engine**
|
||||
- Blocked by #166. Once mapping works, this ticket wires the full pipeline: load YAML → deserialize → call spawn → generate triangles via `server/src/content/template.rs:assign_triangle_roles()` → register ownership.
|
||||
- Instance lifecycle management: track active instances, support unloading (for zone transitions and save/load).
|
||||
- Integration point with #166: the instantiation engine calls the mapping layer, not the raw spawn functions.
|
||||
- Acceptance: end-to-end test — load `server/data/templates/` logistics_hub YAML, instantiate it, assert NPCs exist with correct roles and 2+ `TriangleState` components generated.
|
||||
|
||||
**#108 — Cross-template triangle generation**
|
||||
- Sprint 20 landed intra-template triangles (#107). This ticket adds the 1 cross-template triangle required by D-024 ("2 per template minimum, 1 cross-template").
|
||||
- The existing `assign_triangle_roles()` in `server/src/content/template.rs` takes `&[TriangleDef]` — extend to accept role slots from two different `TemplateOwnership` sources.
|
||||
- D-025 ownership model: NPCs are owned by one template but can hold reference roles in another. The cross-template triangle uses `TemplateReferenceMap` reference links (carrying relationship metadata) not direct ownership links.
|
||||
- Acceptance: test world with two instantiated templates (logistics hub + bar) produces 1 cross-template `TriangleState` with role assignments spanning both templates.
|
||||
|
||||
**#109 — Triangle validation**
|
||||
- Quality checks on generated triangles. Three checks minimum: (1) conflict viability — the three role slots have at least one opposing Want axis, (2) relationship coherence — at least one Relationships entry links the three roles, (3) interest divergence — no two roles share identical Want+Secret combination.
|
||||
- Validation runs at instantiation time (not a separate pass). Return `Result<Vec<TriangleState>, ValidationError>` from `assign_triangle_roles()`.
|
||||
- Hoshe: unit tests for each failure mode — triangle that fails conflict viability, triangle that fails coherence, triangle that fails divergence.
|
||||
- Acceptance: `cargo test -p server -- triangle_validation` passes, covering all three failure modes plus a valid triangle that passes all checks.
|
||||
|
||||
**#85 — Error handling & recovery**
|
||||
- Handle three categories: (1) simulation panics / process crashes, (2) protocol deserialization errors, (3) desync detection between client state and server state.
|
||||
- Server side: `server/src/bridge/local.rs` is the IPC entry point. Add a supervision layer that catches panics from the tick loop and sends a structured `SimError` message to the client before dying, rather than an abrupt disconnect.
|
||||
- Protocol errors: `server/src/bridge/types.rs` — ensure malformed input returns a typed error response, not a panic. The existing `malformed_input_in_batch_rejects_entire_batch` test (#479, done) is the baseline.
|
||||
- Desync: add a `state_hash` field to `ObserverSnapshot` (a fast hash of key mutable state — player position, NPC count, tick number). Client logs hash mismatches for debugging. No automatic recovery in v0.1 — detect and report only.
|
||||
- Acceptance: integration test sends a deliberately malformed message mid-session, asserts the server emits a `SimError` message and continues running (does not exit).
|
||||
|
||||
**#246 — Basic environmental interaction**
|
||||
- `ObjectType` enum is already in `server/src/bridge/types.rs` (extended by #421, #422). `Interactable` component is in `server/src/simulation/interaction.rs`.
|
||||
- Currently `ObjectType::Door`, `ObjectType::Terminal`, `ObjectType::Readable`, `ObjectType::Container`, `ObjectType::Furniture` exist with verb sets.
|
||||
- This ticket: implement the _behaviour_ behind Door (toggle `walkable` on the blocking tile(s), emit a zone-crossable notification), Examinable objects (return examine text from content), and usable Terminals (trigger a `TerminalInteracted` event for future dialogue hook).
|
||||
- Door state must be tracked in `SaveStateV1` (currently a field gap — add `open_doors: Vec<StableId>` to the save struct in `server/src/simulation/save_state.rs`).
|
||||
- Acceptance: unit test — player interacts with a Door entity, asserts walkability flips; interacts again, asserts it flips back. Examine on a Readable entity returns non-empty text.
|
||||
|
||||
## Dependency Chain
|
||||
|
||||
```
|
||||
#159 (Tier 2 template format) → #166 (template-to-instance mapping) → #161 (instantiation engine)
|
||||
#107 (done: intra-template triangles) → #108 (cross-template triangles) → #109 (triangle validation)
|
||||
#85 (error handling) — standalone
|
||||
#246 (environmental interaction) — standalone
|
||||
```
|
||||
|
||||
Parallel tracks: #159→#166→#161 and #108→#109 can run concurrently. #85 and #246 are independent.
|
||||
|
||||
## PR Workflow
|
||||
|
||||
When ready to submit, create a PR with `tea` CLI:
|
||||
|
||||
```bash
|
||||
tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(simulation): Sprint 21 template instantiation" --description "body" --base main --head server
|
||||
```
|
||||
@@ -0,0 +1,66 @@
|
||||
# Sprint 21: Instantiate — Visual Tasks
|
||||
|
||||
**Goal:** The template system becomes executable — templates spawn NPCs, assign triangles, and place them in world space; cross-template triangles link social sites; the client gains save/load game flow; and the generator pipeline gets its architectural design.
|
||||
|
||||
**Branch:** `visual`
|
||||
**Agents:** Araminta (art direction)
|
||||
|
||||
## New Tickets
|
||||
|
||||
| # | Title | Blocked by |
|
||||
|---|-------|------------|
|
||||
| #564 | Fog shader too opaque — tune alpha for semi-transparent layers per D-059 | — |
|
||||
|
||||
Use `tooling/db/ticket show <id>` for full details.
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- `decisions/perception.md` — D-059 (fog: shader-based, five layers, knowledge-graph-driven), D-011 (fog of perception non-negotiable, same system for NPCs and player)
|
||||
- `decisions/architecture.md` — D-066 (dual-scale grid: 0.5m sim tiles, 1m visual tiles; fog gradient edge = 6-8 sim tiles = 3-4 visual tiles), D-043 (visual style: "functional warmth", Godot Light2D pipeline, sprites are shape templates the lighting completes)
|
||||
|
||||
## Notes
|
||||
|
||||
**#564 — Fog shader too opaque — tune alpha for semi-transparent layers per D-059**
|
||||
|
||||
The fog shader is implemented and structurally correct. The issue is that the alpha values for Layer 2 (peripheral) and Layer 3 (deep fog) are heavier than D-059 specifies, making the fog feel like a dark wall rather than limited visibility.
|
||||
|
||||
**File to edit:** `client/shaders/fog.gdshader`
|
||||
|
||||
**Current alpha values (reference fog_shader.gd for context):**
|
||||
- Layer 2 (peripheral, `vis` between `PERIPHERAL_LOW=0.55` and `CLEAR_THRESHOLD=0.85`): alpha `mix(0.55, 0.25, coverage) + noise * 0.1` — peaks at 0.55–0.65 at the peripheral edge
|
||||
- Layer 3 (deep fog, `explored > 0.3`): alpha `mix(0.78, 0.90, noise_val)` — near-fully opaque
|
||||
|
||||
**D-059 intent:**
|
||||
- Layer 2 (light fog / peripheral): "desaturated 40–50%, brightness -30%". This implies entities and world geometry are still partially visible — something in the 30–45% alpha range at the peripheral boundary, fading smoothly toward clear.
|
||||
- Layer 3 (deep fog / previously explored): "near-monochrome with ~10% zone temperature tint". The zone tint (`zone_tint_tex`) must be readable through the fog. The current 0.78–0.90 alpha buries it. Targeting 0.60–0.75 range should let tint breathe without revealing too much detail.
|
||||
- Layer 1 (clear, vision cone): the soft gradient edge should span 6-8 sim tiles = 3-4 visual tiles (D-066). Verify the `smoothstep(CLEAR_THRESHOLD, 1.0, vis)` range still produces this after alpha changes. `CLEAR_THRESHOLD = 0.85` and `PERIPHERAL_LOW = 0.55` are the knobs — do not change these unless the gradient edge width breaks.
|
||||
|
||||
**What to tune:**
|
||||
1. Layer 2: reduce the heavy-end alpha from 0.55 → ~0.38, keep the light-end at 0.25. Adjust noise contribution proportionally. Entities behind peripheral fog should be dimmed and desaturated, but recognisable in silhouette.
|
||||
2. Layer 3: reduce the alpha range from `mix(0.78, 0.90, noise_val)` → `mix(0.62, 0.76, noise_val)`. Zone tint (10% contribution via `mix(vec3(0.04), zone_tint, 0.1)`) should now be faintly visible as a colour cast. The "fog breathes" effect is preserved — keep the noise animation as-is.
|
||||
3. Verify that Layer 5 (unexplored, no maps, `#12141a`, alpha 1.0) remains fully opaque — information zero, no change.
|
||||
|
||||
**How to test:**
|
||||
- The Fog Theater gauntlet room (accessible via the Gauntlet hub in the running game) exercises all five fog layers in a single space.
|
||||
- Observable pass criteria:
|
||||
1. An NPC standing in the peripheral zone (Layer 2) is visibly dimmed and slightly desaturated — their D-033 relationship colour is still readable.
|
||||
2. A previously-explored room (Layer 3) shows a faint zone temperature tint (bar zone = warm, hub zone = cool, corridor = neutral) rather than uniform near-black.
|
||||
3. The vision cone edge is soft — no visible hard line between clear and peripheral.
|
||||
4. Unexplored tiles remain fully black.
|
||||
- Also run `make ci-client` to confirm no shader compilation regressions.
|
||||
|
||||
**Note:** `client/scripts/rendering/fog_shader.gd` (the GDScript controller) does not need changes — it sets uniforms, not alpha values. `client/scripts/autoloads/fog_state.gd` generates the textures fed to the shader — verify the zone tint texture is being populated correctly if Layer 3 tint still doesn't show after alpha reduction.
|
||||
|
||||
## Dependency Chain
|
||||
|
||||
```
|
||||
#564 (fog alpha tuning) — standalone
|
||||
```
|
||||
|
||||
## PR Workflow
|
||||
|
||||
When ready to submit, create a PR with `tea` CLI:
|
||||
|
||||
```bash
|
||||
tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "fix(visual): tune fog shader alpha per D-059" --description "body" --base main --head visual
|
||||
```
|
||||
@@ -119,7 +119,7 @@ None. All design decisions confirmed. If blockers arise during implementation, e
|
||||
|
||||
## Files Written
|
||||
|
||||
- /var/home/jeroenschweitzer/Projects/settled-reach/main/docs/sprints/sprint-8/server.md
|
||||
- /var/home/jeroenschweitzer/Projects/settled-reach/main/docs/sprints/sprint-8/client.md
|
||||
- /var/home/jeroenschweitzer/Projects/settled-reach/main/docs/sprints/sprint-8/audio.md
|
||||
- /var/home/jeroenschweitzer/Projects/settled-reach/main/docs/sprints/sprint-8/joint.md
|
||||
- /var/mnt/data/projects/settled-reach/main/docs/sprints/sprint-8/server.md
|
||||
- /var/mnt/data/projects/settled-reach/main/docs/sprints/sprint-8/client.md
|
||||
- /var/mnt/data/projects/settled-reach/main/docs/sprints/sprint-8/audio.md
|
||||
- /var/mnt/data/projects/settled-reach/main/docs/sprints/sprint-8/joint.md
|
||||
|
||||
@@ -17,12 +17,12 @@ The core protocol implementation is sound and test coverage is strong for the ha
|
||||
## Files Reviewed
|
||||
|
||||
### Core Implementation
|
||||
- `/var/home/jeroenschweitzer/Projects/settled-reach/main/client/scripts/protocol/protocol.gd` (new, 114 lines)
|
||||
- `/var/home/jeroenschweitzer/Projects/settled-reach/main/client/scripts/autoloads/sim_bridge.gd` (modified, +51 lines)
|
||||
- `/var/home/jeroenschweitzer/Projects/settled-reach/main/server/tests/gen_fixtures.rs` (new, 63 lines)
|
||||
- `/var/mnt/data/projects/settled-reach/main/client/scripts/protocol/protocol.gd` (new, 114 lines)
|
||||
- `/var/mnt/data/projects/settled-reach/main/client/scripts/autoloads/sim_bridge.gd` (modified, +51 lines)
|
||||
- `/var/mnt/data/projects/settled-reach/main/server/tests/gen_fixtures.rs` (new, 63 lines)
|
||||
|
||||
### Tests
|
||||
- `/var/home/jeroenschweitzer/Projects/settled-reach/main/client/tests/test_protocol.gd` (new, 137 lines, 9 tests)
|
||||
- `/var/mnt/data/projects/settled-reach/main/client/tests/test_protocol.gd` (new, 137 lines, 9 tests)
|
||||
|
||||
### Vendor Library (noted, not reviewed)
|
||||
- `client/addons/messagepack/messagepack.gd` (368 lines, third-party)
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
# Generator Architecture Workshop — Round 1: Araminta
|
||||
## Visual Coherence Constraints for Chunk Fill
|
||||
|
||||
**Author:** Araminta (Visual Designer)
|
||||
**Date:** 2026-02-27
|
||||
**Workshop:** Generator Architecture (Ticket #562)
|
||||
**Source documents:** visual-grammar-v01.md, spatial-layout-terminal-v01.md, spatial-layout-bar-v01.md, spatial-layout-gate-v01.md, spatial-layout-smuggling-corridors-v01.md, decisions/content.md (#153 D-record = D-093/D-094), decisions/architecture.md
|
||||
|
||||
---
|
||||
|
||||
## What the Visual Domain Requires from the Generator
|
||||
|
||||
The visual grammar (visual-grammar-v01.md) establishes a system where the **environment communicates location, not drama** (D-045). This places a strong constraint on the generator: every piece of generated content must resolve to a zone palette, a zone era, and an access tier. Without those three anchors, generated chunks will look random regardless of how technically correct the spatial math is.
|
||||
|
||||
My domain requirement is simple: **the generator must produce chunks that a player can read at a glance**. Read = know where they are, know what tier of access they're in, know where the walls and cover are, and know where people might be watching from. Everything below serves that requirement.
|
||||
|
||||
---
|
||||
|
||||
## 1. Visual Coherence Constraints for Chunk Fill
|
||||
|
||||
### 1.1 Zone Palette Is the Primary Coherence Anchor
|
||||
|
||||
Each chunk inherits a single zone palette. Zone boundaries are set at block level (64×64 visual tiles), not chunk level. A chunk does not have a mixed palette. If two zones meet at a block boundary, the visual transition happens between chunks, never within a chunk.
|
||||
|
||||
**Locked zone palettes (D-093, visual grammar §1):**
|
||||
|
||||
| Zone | Floor hex | Ambient | Fixture color | Era |
|
||||
|------|-----------|---------|---------------|-----|
|
||||
| Gate cluster | `#b8bec4` (surface) | `#0a1222` fog tint | `#f2f4ff` cold LED | Era 3 |
|
||||
| Terminal | `#1a1e24` | `#0d1520` | `#c8d8f0` cool institutional | Era 1/2 |
|
||||
| Bar | `#1e1912` | `#200c04` | `#f0b840` amber | Era 3 |
|
||||
| Maintenance | `#181818` | `#101214` | `#d0d8e0` cold dim | Era 1 |
|
||||
| Transition corridor | `#181818` (neutral industrial) → `#1e1912` gradient over 40m | varies | sparse cool | Era 1 |
|
||||
|
||||
**Rule:** The generator applies the zone palette to every floor tile and wall face in the chunk. It cannot introduce off-palette materials without an "era modification" flag (see §1.3). An institutional zone chunk with warm amber flooring is a visual error.
|
||||
|
||||
### 1.2 Lighting Fixture Placement Determines Room Scale
|
||||
|
||||
This is the most underappreciated visual constraint: **room width is functionally constrained by fixture radius**.
|
||||
|
||||
From the visual grammar:
|
||||
- Terminal (institutional): fixture radius 8–10 visual tiles. A room wider than ~20 visual tiles needs a second fixture row.
|
||||
- Bar (social): fixture radius 5–6 visual tiles. Rooms wider than ~12 visual tiles go dark between pools — which is intentional for private zones, but not for public ones.
|
||||
- Maintenance: fixture radius 4–5 visual tiles. Gaps between pools are expected and desirable (abandoned, sparse aesthetic).
|
||||
|
||||
**Generator rule:** Chunk fill must place at minimum 1 lighting fixture per zone-appropriate coverage radius². Rooms that exceed 2× coverage radius without a fixture read as broken infrastructure in institutional zones and as suspicious in any zone. The generator's room-dimension choices must respect fixture-coverage budgets for the zone.
|
||||
|
||||
### 1.3 Era Tags Produce Material Variation Without Palette Violation
|
||||
|
||||
Buildings within a block share an era tag. The era tag modifies surface materials while staying within the zone palette range:
|
||||
|
||||
- **Era 1:** Base palette. Older composite panels, original construction.
|
||||
- **Era 2:** Retrofit elements. Same floor tile base; surface-mounted conduits, junction boxes, modified partitions in `#6d7178` (slightly warmer than Era 1's `#7a7f85`, different production run). Visible as overlay elements on layer 4.
|
||||
- **Era 3:** Newer construction. Cleaner materials, Commission-grade or commercial finish. Gate cluster is pure Era 3: `#b8bec4` surfaces, uniform LED-white overhead. Bar is Era 3 built by an individual (Lera): same era code, but density of accumulated modifications distinguishes it from institutional Era 3.
|
||||
|
||||
**Generator rule:** Assign era at block generation time, not chunk fill time. Chunk fill inherits era from block. Adjacent blocks can have different eras; the visual transition manifests at block boundary chunks via setbacks, service alleys, or material seams.
|
||||
|
||||
### 1.4 Saturation Hierarchy Must Be Preserved
|
||||
|
||||
D-044 visual hierarchy: entity (40–60% saturation) > objects (15–30%) > structure (5–15%).
|
||||
|
||||
The generator cannot introduce decorative or accent materials that approach entity saturation. Generated floor markings, zone signage, and accent walls must stay in the 5–15% structure tier. If the generator places anything in the 15–30% object tier as environmental decoration (painted walls, colored service doors), it must not exceed D-052 favorite color saturation rules.
|
||||
|
||||
**Hard constraint:** No generated structural or decorative element uses a hex color with saturation above 30%. This is not a style preference — it's a system requirement. If the environment reaches entity saturation levels, the player loses the ability to read NPCs by color (D-033 system breaks).
|
||||
|
||||
### 1.5 Outline Consistency Is Absolute
|
||||
|
||||
`#333340` is the universal outline for all sprites, all zones, all layers. Non-negotiable. Generated content inherits this. The generator does not produce zone-specific outlines or context-specific outlines. Consistency is what makes the visual grammar feel like a coherent world rather than a patchwork of assets.
|
||||
|
||||
### 1.6 LOS Anchor Interval Constraint
|
||||
|
||||
From locked spatial rules (V-05, Workshop #153): structural breaks (walls, pillar clusters) must occur at maximum 16 visual tile intervals in open spaces, with quarter boundaries as the primary anchor points.
|
||||
|
||||
**Why this matters for chunk fill:** Without LOS anchor placement rules, the generator can produce open-floor chunks that are visually incoherent and gameplay-broken simultaneously. A 32×32 visual tile chunk filled with open floor provides no cover, no investigation positions, no spatial drama. The LOS anchor rule is both a visual coherence rule (prevents visual emptiness) and a gameplay rule (prevents cover-free spaces).
|
||||
|
||||
**Generator rule:** Every quarter (16×16 visual tiles) must contain at minimum one structural break that interrupts east-west or north-south LOS across the quarter. This can be a wall partition, a pillar cluster, a furniture arrangement, or a zone transition boundary. The break does not need to be at the exact quarter boundary — it can be within 4 visual tiles of it.
|
||||
|
||||
---
|
||||
|
||||
## 2. How the Sub-Chunk Quarter System Produces Plausible Streetscapes
|
||||
|
||||
### 2.1 Quarter Dimensions and Their Spatial Meaning
|
||||
|
||||
A chunk is 32×32 visual tiles (32m). Divided into 4 quarters: each quarter is 16×16 visual tiles (16m).
|
||||
|
||||
16m × 16m is a meaningful spatial unit:
|
||||
- A small shop or office fits in one quarter (with walls and a narrow corridor)
|
||||
- The Terminal's scanner bay cluster (rows 6–9, ~16×4 visual tiles) is a functional sub-quarter
|
||||
- The bar's corner booth zone (NW, rows 1–5, ~8×5) occupies roughly one-third of a quarter
|
||||
|
||||
The quarter is the minimum viable building unit. A single-quarter building has room for one social zone, one access entry, and minimal interior subdivision. Two-quarter buildings have room for a public face and a semi-private back. Four-quarter buildings (full chunk) support the complexity of the Terminal or Gate Cluster.
|
||||
|
||||
### 2.2 Street Generation Must Precede Quarter Fill
|
||||
|
||||
Streetscapes are coherent only if streets are determined before buildings. The generator sequence for visual coherence:
|
||||
|
||||
1. **Block level:** Determine street network (which block edges are street-facing, which are back-of-block)
|
||||
2. **Chunk level:** Determine which chunks within the block contain buildings vs. open space vs. street continuation
|
||||
3. **Quarter level:** Determine which quarters are filled (building footprint) vs. empty (courtyard, alley, service access)
|
||||
4. **Fill level:** Place zone-appropriate floor, wall, furniture, and overhead elements within filled quarters
|
||||
|
||||
**Rule:** Building facades must face the nearest street edge. The generator never places a primary building entrance on the back-of-block side. This is what makes a block look like a block rather than a random cluster of structures.
|
||||
|
||||
### 2.3 Facade Rhythm Along a Block Face
|
||||
|
||||
Adjacent filled quarters on a street-facing block edge cannot have identical facade treatments. The visual grammar requires variation without chaos:
|
||||
|
||||
- Vary doorway position (west side / center / east side of facade) between adjacent buildings
|
||||
- Vary facade depth: some buildings set back 2–3 visual tiles from the block edge, others flush
|
||||
- Alternate overhead element density (pipes, signage) between adjacent quarters
|
||||
|
||||
**What the hand-authored examples tell us:**
|
||||
- The Terminal's entry facade (row 01): two doorways spread across 44 visual tiles, with solid wall between them. Rhythm: solid | door | long solid | door | solid.
|
||||
- The Gate Cluster's concourse south wall (row 33): the primary district-facing facade is essentially unbroken, with the district entry at ground level. Scale communicates institutional weight.
|
||||
- The Bar's main entrance (row 01): one primary door (west) + one emergency exit (east). Asymmetric, which reads as organic (functional warmth).
|
||||
|
||||
**Generator rule:** Randomly placing doorways produces facades that look like generated content. The generator should select from a small set of facade templates per zone/era/access-tier combination, then apply allowed variation parameters (doorway count, setback, overhead density). Do not treat facade generation as free parameter space.
|
||||
|
||||
### 2.4 Access Tier Gradient Is Spatial, Not Random
|
||||
|
||||
Hand-authored locations consistently follow a gradient: public street face → semi-public entry zone → semi-private interior → private back zones. This is not just a gameplay rule; it's what produces plausible architecture.
|
||||
|
||||
Terminal: Entry lobby (PUBLIC) → Scanner bays (PUBLIC MONITORED) → Main corridor (SEMI-PUBLIC) → Work zones (SEMI-PRIVATE/PRIVATE)
|
||||
Bar: Entry (PUBLIC) → Main tables (PUBLIC) → Bar counter zone (SEMI-PRIVATE) → Back room (PRIVATE)
|
||||
Gate cluster: Concourse (PUBLIC) → Customs lanes (SEMI-PRIVATE) → Staging (PRIVATE) → Aperture (RESTRICTED)
|
||||
|
||||
**Generator rule:** The access tier gradient runs north-south or from the street-facing edge inward. The public face is always the face with the most exterior exposure. The private back is always the face furthest from public circulation. Quarters are tagged with access tiers before fill content is selected. Fill content must be appropriate to the tier (no open seating in private zones, no locked doors in public zones without authority-gated reason).
|
||||
|
||||
---
|
||||
|
||||
## 3. Quarter Merge Rules: What Decides Fill vs. Empty, and Shape
|
||||
|
||||
### 3.1 Three Merge Types and Their Visual Logic
|
||||
|
||||
**2×2 merge (full chunk = 32×32 visual tiles):**
|
||||
Used for major institutions: primary logistics hubs, government facilities, large commercial buildings. The Terminal is roughly this scale (44×28 — slightly larger than a single chunk, suggesting it spans into a second chunk or uses a non-standard block configuration). The Gate Cluster (40×32) is essentially a full-chunk structure.
|
||||
Visual requirement: 2×2 merges need a legible building boundary on all four sides. The generator must place a clear facade (wall + doorway treatment) on each exposed edge, not just the street-facing side.
|
||||
|
||||
**1×2 merge (half chunk = 16×32 or 32×16 visual tiles):**
|
||||
Used for medium-scale spaces: medium offices, workshops, larger commercial venues. This is the most common merge type in a working-class district.
|
||||
Visual requirement: The long edge of a 1×2 merge is the primary facade. The short edges are either party walls (shared with adjacent building, no exterior treatment) or service edges (minimal, utilitarian). The generator must determine which axis is the long facade axis from the street orientation.
|
||||
|
||||
**L-shape merge (3 quarters):**
|
||||
This is the "functional warmth" shape — the building that grew organically. The Bar's bathroom corridor extension is a real-world example of L-shape emergence: original rectangular structure + later addition that breaks the rectangle.
|
||||
Visual requirement: L-shapes need a visual explanation for the notch. Options:
|
||||
- The notch is a service alley (narrow, maintenance-access, darker floor)
|
||||
- The notch is a courtyard or outdoor area (open, possibly furniture)
|
||||
- The notch is a later-era addition seam (visible material change at the join)
|
||||
|
||||
The generator cannot produce L-shapes where the notch is simply empty floor with no visual or functional justification. The notch must be assigned a purpose type.
|
||||
|
||||
### 3.2 Empty Quarter Types
|
||||
|
||||
An empty quarter is not just "no building here." It must be one of:
|
||||
|
||||
| Empty type | Visual treatment | Lighting | Access tier |
|
||||
|------------|-----------------|----------|-------------|
|
||||
| Open plaza | Open floor, zone palette, possibly benches | Zone-appropriate fixtures | Public |
|
||||
| Service alley | Narrow (4–6 visual tiles wide), dark floor, minimal fixtures | Sparse, cold | Semi-private to restricted |
|
||||
| Courtyard / garden | Open floor, overhead vegetation (layer 4), possibly planters | Natural or warm ambient | Semi-private (enclosed), public (open) |
|
||||
| Vehicle / cargo staging | Open floor with [CT]-type dock markers, sparse overhead | Industrial, zone palette | Semi-private |
|
||||
| Structural gap (undeveloped) | Bare floor, no furniture, possibly temporary barriers | No fixtures = very dark | Restricted by default |
|
||||
|
||||
**Generator rule:** Assign empty type at quarter planning time. Use zone and access tier to constrain the available empty types. A gate cluster zone cannot have a courtyard/garden (wrong era, wrong function). A residential zone cannot have cargo staging.
|
||||
|
||||
### 3.3 Fill vs. Empty Ratio by District Density
|
||||
|
||||
The generator needs a "density" parameter per block, derived from zoning and economic tier inputs:
|
||||
|
||||
| Density | Filled quarters per block | Expected visual character |
|
||||
|---------|--------------------------|--------------------------|
|
||||
| High (industrial/commercial core) | 12–16 of 16 | Dense block faces, few gaps, tall overhead layers |
|
||||
| Medium (mixed-use, working class) | 8–12 of 16 | Regular gaps (alleys, small plazas), varied building scales |
|
||||
| Low (residential fringe, transitional) | 4–8 of 16 | Open courtyards, gardens, undeveloped quarters common |
|
||||
|
||||
**No block should have all 16 quarters filled.** There is always at least one empty quarter per block for service access. This is the generator's hard floor rule: a block with no alleys is architecturally implausible and breaks NPC routing for maintenance-tier characters.
|
||||
|
||||
---
|
||||
|
||||
## 4. How Multi-Block Structures Read at Their Edges
|
||||
|
||||
Multi-block structures (gate terminals, horizon station access points, government complexes, stadiums, parks) span multiple chunks and must resolve their edges differently from single-chunk buildings.
|
||||
|
||||
### 4.1 The Scale Communication Problem
|
||||
|
||||
A multi-block structure must communicate its scale before the player reaches it. At a glance, it must read as "larger than a single building." The mechanisms:
|
||||
|
||||
1. **Unbroken facade length:** A single-chunk building facade is max 32 visual tiles wide. A multi-block structure has a facade spanning 64, 96, or 128 visual tiles. This length reads as institutional weight. The generator must not break this facade with building-scale interruptions (separate doorways that read as separate buildings). Long facades with wide-spaced feature doorways.
|
||||
|
||||
2. **Elevated overhead layer (layer 4):** Multi-block structures can have overhead elements that span chunk boundaries — roof structures, elevated walkways, ducts — that signal continuity. A single-chunk building has overhead elements bounded by its chunk. A multi-block structure's overhead layer crosses chunk seams.
|
||||
|
||||
3. **Setback buffer zone:** Multi-block structures almost always have a setback from the street — a cleared zone, a plaza, or a service perimeter. This setback is part of the block reservation at the zoning pass. The setback communicates "this building doesn't need to compete for street frontage."
|
||||
|
||||
### 4.2 Edge Chunk Treatment
|
||||
|
||||
The chunks at the edges of a multi-block structure face a specific visual challenge: they're part of a large building but they're adjacent to the district circulation system.
|
||||
|
||||
**Required edge treatments:**
|
||||
|
||||
- **Corner chunks:** Special facade treatment. Not a flat wall turning a 90° corner. Options: angled setback, corner feature (pillar, commission emblem, material accent in zone palette), or service access recessed into the corner.
|
||||
|
||||
- **Entry chunks:** The chunk containing the primary entrance to a multi-block structure. Entry facade must be ≥ 3× the width of the connecting corridor (from V-05). For a district street connecting at 4–6 visual tiles wide, the entry facade must be 12–18+ visual tiles wide with at minimum 2 access doors. The Gate Cluster's concourse south wall (40 visual tiles wide with 2+ entry points) demonstrates this correctly.
|
||||
|
||||
- **Party wall chunks:** Chunks on the boundary between a multi-block structure and adjacent single-chunk buildings. The multi-block structure's wall on this edge needs no decorative treatment — it reads as a party wall, which is architecturally correct and visually clean.
|
||||
|
||||
- **Back-of-building chunks:** Maintenance access for multi-block structures. These chunks face the service spine or maintenance corridors. Visual treatment: utilitarian, dark, Era 1 base materials, minimal lighting. Do not apply the public-facing material treatment to service edges.
|
||||
|
||||
### 4.3 Zone Continuity Across Chunk Boundaries
|
||||
|
||||
A multi-block structure has one zone palette for its entire footprint. The generator must not allow zone-palette variation between the chunks of a single multi-block structure. The Gate Cluster is uniformly `#b8bec4` / cold LED throughout all 40×32 visual tiles. No amber warmth creeps in from the adjacent transit corridor.
|
||||
|
||||
**Hard rule:** Chunk seams within a multi-block structure are invisible at the visual grammar level. Floor tiles, wall materials, and lighting temperature are continuous. The only things that can change at a chunk seam within a building are: functional zones (staging vs. customs vs. concourse) with their associated furniture and overhead elements, not base materials.
|
||||
|
||||
---
|
||||
|
||||
## 5. Visual Consistency Rules to Prevent Districts from Looking Random
|
||||
|
||||
### 5.1 The Three Consistency Anchors
|
||||
|
||||
A generated district avoids looking random if every chunk can be traced back to three consistent sources: its **zone palette**, its **era**, and its **access tier gradient**. These three together determine:
|
||||
- What materials appear on the floor and walls
|
||||
- What modifications and overlays exist
|
||||
- What kind of furniture and overhead elements are placed
|
||||
- How light is distributed
|
||||
|
||||
If the generator maintains consistency on these three anchors across all chunks in a district, visual coherence follows automatically. Randomness appears when chunks are filled without reference to these anchors.
|
||||
|
||||
### 5.2 Street Network as Visual Spine
|
||||
|
||||
The district's street network is the visual skeleton. Every building facades toward it; every service access routes away from it. The street network determines orientation for the entire district.
|
||||
|
||||
Visual rules for generated streets:
|
||||
- Street floor tile uses a dedicated transitional material (not the adjacent building's floor palette — streets are shared infrastructure, not zone-specific)
|
||||
- Street width by type: maintenance corridor 2vt, internal building 2–4vt, district street 4vt, transition corridor 6vt, gate concourse 8vt (locked in V-05)
|
||||
- Street lighting: zone-appropriate fixtures placed at regular intervals (every 8–12 visual tiles for primary streets, every 16–20 for secondary)
|
||||
- Street intersection treatment: clear visual signal that two streets meet (material change at the crossing tile, or a pillar/feature at the corner)
|
||||
|
||||
### 5.3 Landmark Anchors and District Readability
|
||||
|
||||
Hand-authored districts have visual landmarks that orient the player: the terminal's institutional facade, the bar's amber light spill, the gate cluster's administered cold white. Generated districts need the same.
|
||||
|
||||
**Generator rule:** Each district should have minimum 1 landmark structure per block cluster (4 blocks = 1 district quadrant). A landmark structure is a multi-block structure or a building with distinctive visual treatment. The generator reserves landmark slots at the district planning pass, before block generation. Landmark structures get the full multi-block edge treatment (§4).
|
||||
|
||||
Without landmark anchors, generated districts produce visual monotony where every block looks like every other block. The landmark is not about decoration — it's about navigation and spatial orientation.
|
||||
|
||||
### 5.4 Facade Variation Budget
|
||||
|
||||
Adjacent buildings on the same block face must vary in at least 2 of these 5 parameters:
|
||||
1. Primary entry position (west / center / east of facade)
|
||||
2. Facade depth (flush / 1–2vt setback / 3–4vt setback)
|
||||
3. Overhead element density (sparse / moderate / dense)
|
||||
4. Building height expression (single overhead layer / double overhead layer with structural bridge)
|
||||
5. Era modification markers (none / minor / heavy)
|
||||
|
||||
If adjacent buildings vary in only 1 or 0 parameters, the block face looks copy-pasted. The generator's facade parameter selection must check the adjacent quarter's choices before committing.
|
||||
|
||||
### 5.5 Lighting Temperature Is Zone-Specific, Not Building-Specific
|
||||
|
||||
The lighting temperature in the visual grammar is set by zone, not by individual buildings. A bar inside an institutional zone gets institutional lighting, not amber. The only exception is player-facing hand-authored social sites where lighting is an authored decision (The Last Shift's amber is Lera's decision, not the zone's character).
|
||||
|
||||
**Generator rule:** Generated buildings inherit their lighting temperature from the zone palette. The generator does not assign lighting temperatures at the building level. Lighting is a zone property.
|
||||
|
||||
This is important because the palette gradient rule (locked in Workshop #153) creates visual territory:
|
||||
- Gate/official: cool white → cargo/functional: grey-navy → transit neutral: dark → social warm: amber
|
||||
|
||||
A player walking through a generated district should feel the temperature gradient shift as they move from institutional zones into residential or social zones. This gradient is the district's visual fingerprint.
|
||||
|
||||
### 5.6 The "Settled" Principle — Placement Density Communicates Character
|
||||
|
||||
D-051 ("settling is placement"): spaces feel inhabited when they have accumulated objects, not when they have large open areas. Empty space reads as abandoned or transitional. Filled space reads as active.
|
||||
|
||||
For the generator, this translates to a **placed-object density** budget per quarter:
|
||||
|
||||
| Zone type | Objects per quarter (typical) | Notes |
|
||||
|-----------|-------------------------------|-------|
|
||||
| Institutional (terminal, gate) | High: 8–14 large objects (terminals, cargo containers, desks) | Functional accumulation |
|
||||
| Social (bar, market) | Medium-high: 6–10 objects (tables, seating, service equipment) | Personal accumulation |
|
||||
| Residential | Medium: 4–8 objects (furniture, personal items) | Domestic accumulation |
|
||||
| Maintenance / service | Low: 1–4 objects (utility equipment, sparse) | Functional minimum |
|
||||
| Transit / corridor | Very low: 0–2 objects (signage, benches only) | Movement spaces stay clear |
|
||||
|
||||
**The generator must not produce empty rooms.** An empty room is an authored decision (the restricted storage room is sparse by design — contraband doesn't advertise itself). A generated empty room is an unfinished room. If the zone/access tier combination doesn't justify a sparse object count, the generator adds zone-appropriate clutter.
|
||||
|
||||
---
|
||||
|
||||
## Summary: What the Generator Must Guarantee from a Visual Perspective
|
||||
|
||||
The following visual properties are non-negotiable outputs of any generator pipeline:
|
||||
|
||||
1. **Every chunk belongs to exactly one zone palette.** No mixed-palette chunks. Zone boundaries are block-level decisions.
|
||||
|
||||
2. **Every block has an era tag.** Era modifies surface materials within zone palette bounds. Adjacent blocks may have different eras; the visual transition is handled at block boundary chunks.
|
||||
|
||||
3. **Access tier gradient runs from street face inward.** Public front, private back. This determines facade treatment, interior subdivision, and furniture selection.
|
||||
|
||||
4. **Corridor widths are enforced by type** (V-05): maintenance 2vt, internal building 2–4vt, district street 4vt, transition 6vt, gate concourse 8vt. The generator cannot produce corridors narrower than these minimums.
|
||||
|
||||
5. **LOS anchors exist at max 16vt intervals.** Every quarter contains at minimum one structural break.
|
||||
|
||||
6. **No generated element exceeds structure-tier saturation (15%).** Entity visual hierarchy is inviolable.
|
||||
|
||||
7. **All outlines are `#333340`.** No exceptions.
|
||||
|
||||
8. **Empty quarters have assigned types.** Empty is not null — it is plaza, alley, courtyard, staging, or undeveloped, each with specific visual treatment.
|
||||
|
||||
9. **Multi-block structure facades are unbroken and wide.** Entry facade ≥ 3× connecting corridor width, minimum 2 access doors.
|
||||
|
||||
10. **Lighting temperature is zone-assigned, not building-assigned.** The zone palette determines fixture color temperature. Buildings do not override this.
|
||||
|
||||
11. **Adjacent filled quarters on a street face vary on at minimum 2 facade parameters.** Facade variation is enforced, not random.
|
||||
|
||||
12. **Every district quadrant has at minimum 1 visual landmark.** Landmark slots are reserved at district planning pass before block generation.
|
||||
|
||||
---
|
||||
|
||||
*These constraints are derived from the hand-authored v0.1 content (Terminal, Bar, Gate Cluster, Smuggling Corridors), the confirmed visual grammar (visual-grammar-v01.md), the locked spatial rules from Workshop #153 (V-05), and the D-records cited throughout. Any generator proposal that violates these constraints will produce visually incoherent output regardless of how correct the spatial math is.*
|
||||
@@ -0,0 +1,468 @@
|
||||
# Generator Architecture Workshop — Round 2: Araminta
|
||||
## Visual Coherence for Edge Bleed and Non-Urban Terrain
|
||||
|
||||
**Author:** Araminta (Visual Designer)
|
||||
**Date:** 2026-02-27
|
||||
**Workshop:** Generator Architecture (Ticket #562)
|
||||
**Responding to:** Round 1 notes (Qatux), Nigel-round1.md (OQ-8 on flavor vocabulary), Ozzie-round1.md (anti-grid, historical palimpsest), Lead Directive (edge bleed, non-urban terrain, multi-playstyle support)
|
||||
|
||||
---
|
||||
|
||||
## Acknowledging the Lead Directive
|
||||
|
||||
The lead says this is a game about **the inherent asymmetry of human awareness** — not specifically a detective game. I want to say directly: the visual grammar I've built already serves this. Entity colors (D-033) encode the player's *subjective* relationship to every NPC, not the NPC's objective status. Access tier gradients encode social topology, not investigation routes. Lighting temperature communicates "who inhabits this space," not "who the suspect is."
|
||||
|
||||
What I need to do in Round 2 is make explicit that these visual systems serve **all playstyles simultaneously**:
|
||||
- The investigator reads access tiers as information about where evidence could be hidden
|
||||
- The career-builder reads access tiers as information about where their workplace authority extends
|
||||
- The relationship-seeker reads lighting temperature as information about where warmth is
|
||||
- The explorer reads LOS asymmetry as information about what's worth investigating
|
||||
- The trader reads object density as information about economic activity
|
||||
|
||||
The grammar doesn't favor the detective. It reads *space as social reality*. That already supports all playstyles. What changes in Round 2 is that the grammar needs to extend to district boundaries and natural terrain — both of which the current spec treats as edge cases.
|
||||
|
||||
---
|
||||
|
||||
## 1. District Edge Bleed — Gradient Rules and Visual Ambiguity
|
||||
|
||||
### The Core Problem
|
||||
|
||||
A hard district boundary at the 256×256 visual tile line would produce visible seams. A player crossing from an institutional district into a residential one would see the palette snap. That's the grid made visible at district scale — exactly what the lead directive prohibits.
|
||||
|
||||
### The Transitional Block System
|
||||
|
||||
**Rule:** The outermost 1-block ring (64×64 visual tiles wide, the entire perimeter) of every district is a **transitional zone**. It is not fully committed to either the district's zone palette or the adjacent district's zone palette. It blends.
|
||||
|
||||
The blend is not random — it is **directional and material-specific**:
|
||||
|
||||
| Element | Transition rule |
|
||||
|---------|----------------|
|
||||
| Floor tiles | Interpolate toward adjacent district over the 64vt block width. At block center (32vt), halfway blend. |
|
||||
| Wall materials | Do NOT interpolate — walls remain in the building's home district palette. Walls are structural; inconsistent wall materials read as a construction error, not a cultural gradient. |
|
||||
| Lighting fixture color | Interpolate toward adjacent district temperature over the 64vt block. Fixtures in the edge block use a temperature midway between zone A and zone B. |
|
||||
| Ambient (CanvasModulate) | Interpolate: the screen-space ambient at the district boundary is the average of both zones' ambient values. |
|
||||
| Overhead elements (layer 4) | Follow the building's home palette — no interpolation. The overhead layer is attached to structures, not geography. |
|
||||
|
||||
**Example:** Terminal (cool grey `#1a1e24`, fixture `#c8d8f0`) meets Bar zone (warm `#1e1912`, fixture `#f0b840`). The transitional block between them gets floor `#1c1b1b` (average) and fixture temperature `#e0c090` (average — a neutral warm-cool white). The player feels themselves moving between temperature zones. They don't see a line.
|
||||
|
||||
### Shared Infrastructure as Grid Dissolvers
|
||||
|
||||
Infrastructure doesn't respect district lines. The maintenance spine continues across boundaries. Street network continues across boundaries. Power conduits continue.
|
||||
|
||||
**Visual rule:** Any infrastructure element that crosses a district boundary maintains its own visual identity regardless of which zone's palette it passes through. A maintenance corridor that runs through both zones is uniformly `#181818` floor and `#d0d8e0` dim fixtures throughout — the zone doesn't color the infrastructure.
|
||||
|
||||
This produces a specific effect: the player navigating via maintenance corridors and service spines experiences district transitions as gradual warmth or coolness changes in the main spaces they pass through, not as clear demarcation. The maintenance route is the same everywhere; what changes is the ambient leaking through doorways.
|
||||
|
||||
### Palette Gradient Sequence (The Visual Spine of a Planet-Side District)
|
||||
|
||||
From my memory — and now formalized: the palette gradient rule should operate at district scale, not just within a district.
|
||||
|
||||
**The canonical gradient for a settlement:**
|
||||
|
||||
```
|
||||
Gate/Official (cool white, high Meridian)
|
||||
→ Cargo/Functional (grey-navy, medium Meridian)
|
||||
→ Transit/Neutral (dark, degraded Meridian)
|
||||
→ Social/Warm (amber, minimal Meridian)
|
||||
→ Residential (deeper warm, very sparse)
|
||||
→ Industrial periphery (cool-neutral, Era 1)
|
||||
→ Agricultural edge / Wilderness (natural ambient)
|
||||
```
|
||||
|
||||
A player moving from the gate cluster outward should feel this sequence of temperature changes. Each district boundary is a step along the gradient. No step should be abrupt. The transitional block system produces smooth steps.
|
||||
|
||||
### Visual Ambiguity at the Boundary — The Test
|
||||
|
||||
**Test:** A player who has walked into a space and stopped moving should not be able to say with certainty "I'm in District A" vs. "I'm in District B." They should be able to say "I'm somewhere between institutional and residential." That ambiguity is correct. District boundaries are social constructs; they should feel like zones of contested identity.
|
||||
|
||||
**Mechanism:** Beyond the transitional block palette blend, the boundary zone should contain:
|
||||
- At least one building whose aesthetic reads ambiguously (a residential building with institutional materials — "this was built during the corporate ownership period")
|
||||
- At least one infrastructure element that belongs to neither palette (a public bench, a civic planter, a notice board — neutral civic-grey)
|
||||
- Faction presence that differs from either zone's dominant faction (the boundary is where power is ambiguous)
|
||||
|
||||
---
|
||||
|
||||
## 2. Non-Urban Terrain Visual Grammar
|
||||
|
||||
### The Problem with the Current Visual Grammar
|
||||
|
||||
My Round 1 grammar assumed fixture-based lighting throughout. Natural terrain has no light fixtures — it has ambient daylight, weather, and time-of-day. The zone palette system needs a natural terrain extension.
|
||||
|
||||
The key difference is not the palette — it's the **lighting model**. Urban zones use `PointLight2D` with defined fixture radii and specific color temperatures. Natural terrain uses **global ambient light** (CanvasModulate adjusted by time-of-day and weather) with local shadow patches (tree canopy, rock shadows, terrain occluders) rather than light pools.
|
||||
|
||||
This is a different visual regime, but it's compatible with the same grammar: it just uses different sources for the same properties (floor color, ambient, lighting temperature, LOS anchors).
|
||||
|
||||
### Natural Zone Palettes
|
||||
|
||||
#### Farmland
|
||||
|
||||
Character: human-worked earth, seasonal rhythm, functional machinery. Warm earth tones crossed with metal and worn wood.
|
||||
|
||||
| Element | Hex | Notes |
|
||||
|---------|-----|-------|
|
||||
| Floor — open soil | `#1a1510` | Dark warm brown, turned earth |
|
||||
| Floor — crop cover (summer) | `#0e1408` | Deep green-dark, living crops overhead |
|
||||
| Floor — crop cover (dormant) | `#1c1610` | Pale straw-brown, cut stalks |
|
||||
| Surface structures | `#2a2010` | Dried wood, weathered metal, fence posts |
|
||||
| Ambient (daylight) | `#0e0c08` | Warm near-black ground shadow |
|
||||
| Ambient (dusk) | `#120810` | Deep dusty purple-rose |
|
||||
| Lighting (nocturnal) | `#f0b840` (amber, very sparse) | Oil lamp, generator-powered work light |
|
||||
| Overhead (canopy) | `#0a1006` at 60% opacity | Crop overhead layer — partial occlusion |
|
||||
|
||||
LOS anchors in farmland: tree lines, fencing, equipment rows, barn structures, irrigation channels. Interval still max 16vt — the open field is broken by these.
|
||||
|
||||
**Object density:** 4–8 per quarter. Crops are floor elements (z=0/1), not objects. Objects are equipment (tractors, plows, silos), structures (barns, sheds, irrigation heads), and markers (fence posts, gate structures).
|
||||
|
||||
#### Wilderness / Forest
|
||||
|
||||
Character: ambient darkness, dense occlusion, no human pattern. This is where both Ozzie's "place I'm not supposed to be" fantasy and the stealth/exploration loop live.
|
||||
|
||||
| Element | Hex | Notes |
|
||||
|---------|-----|-------|
|
||||
| Floor — forest floor | `#0e120e` | Dark organic green-brown |
|
||||
| Floor — undergrowth | `#141a14` | Slightly lighter, mossy texture |
|
||||
| Floor — clearings | `#181c14` | Open patches, warmer |
|
||||
| Surface (exposed rock) | `#141618` | Cool grey-blue stone |
|
||||
| Ambient | `#080a08` | Near-black, very dark |
|
||||
| Canopy overhead (z=4) | `#0a1008` at 55–75% opacity | Dense forest blocks: variable opacity by canopy thickness |
|
||||
| Lighting (sparse, nocturnal) | `#c0d8e8` (moonlight) | Cold ambient, from gaps in canopy, not fixture-based |
|
||||
|
||||
LOS in forest: extremely broken. 3–5 visual tile clear vision before a tree line interrupts. The forest is a zone where the player has naturally degraded visibility — not from the fog shader, from dense overhead layer occlusion.
|
||||
|
||||
**Key visual rule for wilderness:** The overhead layer (z=4) does the work that walls do in urban spaces. Dense forest canopy at 70% opacity is the wilderness equivalent of a building interior. The player sees ground-level detail but loses the mid-range LOS that urban spaces provide.
|
||||
|
||||
**Object density:** 0–3 per quarter. Terrain features (fallen logs, rock outcroppings), occasional structures (an abandoned hut, a collapsed wall remnant), natural water features. Never furniture or organized equipment.
|
||||
|
||||
#### Ocean / Coastal Water
|
||||
|
||||
Character: open, reflective, dark, directional light from surface.
|
||||
|
||||
| Element | Hex | Notes |
|
||||
|---------|-----|-------|
|
||||
| Deep water floor | `#060c14` | Near-black deep blue |
|
||||
| Shallow coastal | `#0e1820` | Slightly lighter, sand visible through water |
|
||||
| Tidal zone | `#181c1a` | Wet rock, dark neutral |
|
||||
| Ambient | `#060810` | Cold near-black |
|
||||
| Surface reflection | `#c0c8d8` (specular at 20% opacity, animated) | Starlight/sunlight reflection — visual grammar's water treatment |
|
||||
|
||||
For ocean shores: the "shore" is a transitional band 4–12 visual tiles wide between the natural-water floor palette and the land/beach palette.
|
||||
|
||||
**LOS in water:** Characters on water have dramatically extended LOS (no urban occlusion). Characters observing *from land looking at water* have similar extension — open water is a surveillance dead zone for urban investigations but a clear field for coastal ones. A player on a dock watching boats has long-range LOS that urban environments never permit.
|
||||
|
||||
#### Beach / Coastal
|
||||
|
||||
Character: warm sand tones, open flat, tidal variation.
|
||||
|
||||
| Element | Hex | Notes |
|
||||
|---------|-----|-------|
|
||||
| Dry sand | `#2a2218` | Dark warm tan |
|
||||
| Wet sand | `#201a10` | Darker, where tide has been |
|
||||
| Dune vegetation | `#121610` | Sparse dark grass |
|
||||
| Ambient | `#0c0a06` | Warm near-black |
|
||||
| Lighting | Global ambient, no fixtures in beach zones | Daylight is the light source |
|
||||
|
||||
**Object density:** 0–3 per quarter. Drift material (logs, seaweed, debris), tidal structures (rock pools, seawall sections), human presence markers where applicable (a boat mooring, a net-drying frame). Beach is one of the lowest-density natural zones.
|
||||
|
||||
#### Mountain / High Terrain / Snow
|
||||
|
||||
Character: cold, bright surfaces, compressed atmospheric light, vertically dramatic.
|
||||
|
||||
| Element | Hex | Notes |
|
||||
|---------|-----|-------|
|
||||
| Rock face | `#181c22` | Dark blue-grey stone |
|
||||
| Snow surface | `#c8d8e8` | Pale blue-white — deliberately HIGH brightness contrast |
|
||||
| Ice | `#aab8c8` | Slightly darker than snow, more specular |
|
||||
| Ambient | `#10141a` | Cold dark blue-grey |
|
||||
| Lighting | Global ambient, blue-white shifted (`#d0e4f8` at dawn, `#a8c0e0` at dusk) | Mountain light is directional and cold |
|
||||
|
||||
**Visual grammar challenge:** Snow is bright. It's the only natural terrain type where the floor tile is significantly lighter than the ambient — which inverts the typical relationship (dark floor, lighter fixture pools). This needs special handling: snow tiles have an inherent luminosity value that the ambient doesn't reduce to black. They glow passively.
|
||||
|
||||
**LOS in mountain terrain:** Extremely variable. Cliff faces create absolute LOS walls. Ridgelines create elevation-differential LOS (attacker on ridge sees down; defender below cannot see up). The vertical surprise that Ozzie specifically named as a key spatial experience is native to this terrain type.
|
||||
|
||||
#### Secluded Town / Rural Settlement
|
||||
|
||||
Character: warm residential, low institutional density, personal accumulation (functional warmth at architectural scale).
|
||||
|
||||
This is the planet-side analog of the Bar's aesthetic: human habitation that accumulated rather than was planned. Zone palette:
|
||||
|
||||
| Element | Hex | Notes |
|
||||
|---------|-----|-------|
|
||||
| Floor | `#1a1612` | Dark warm brown-grey, worn stone/composite |
|
||||
| Wall face | `#28201a` | Warm brown — between maintenance and bar zone |
|
||||
| Ambient | `#0e0c0a` | Warm near-black |
|
||||
| Lighting | `#f0b840` amber (social, local) + `#d8c890` neutral-warm (commercial, streets) | Mix of fixture temperatures by building type |
|
||||
| Overhead | Very sparse institutional; high personal accumulated objects | Signs, laundry lines, planters, awnings |
|
||||
|
||||
Secluded towns have higher personal overhead density than any urban zone. Individual character expressed through what's placed on z=4: awnings, potted plants on window ledges, a sign written by hand, a rope stretched between buildings. The institutional overhead layer (pipes, ducts, service equipment) is rare. This is the visual grammar of *individual choices at human scale*.
|
||||
|
||||
### Natural Terrain LOS Anchors
|
||||
|
||||
In natural terrain, the max 16vt LOS anchor interval rule still applies, but anchors are:
|
||||
|
||||
| Anchor type | Visual tile width | Notes |
|
||||
|-------------|-------------------|-------|
|
||||
| Single tree | 1–2vt (trunk) | Canopy extends 3–5vt on z=4 |
|
||||
| Tree cluster | 4–8vt | Full LOS break at floor level |
|
||||
| Rock formation | 2–4vt | Hard LOS break, permanent |
|
||||
| Terrain elevation change | 0vt (invisible at floor) | Creates vertical LOS asymmetry |
|
||||
| Fence/wall | 1vt | Partial cover; human-placed |
|
||||
| Building | 4–32vt | Full LOS break |
|
||||
| Water feature (river, stream) | 2–6vt | Not a LOS block but a movement constraint |
|
||||
|
||||
The generator must place natural LOS anchors at the same interval rules as structural elements. A forest clearing that's 30+ visual tiles wide with no trees in it is both visually wrong (natural clearings have fallen logs, undergrowth, isolated trees) and gameplay wrong (no cover in either direction for 30m).
|
||||
|
||||
---
|
||||
|
||||
## 3. Anti-Grid Techniques
|
||||
|
||||
Ozzie is right: "the grid will show." The block grid is 64×64 visual tiles. Even with quarter variation inside blocks, if block edges always align and streets always run at 90°, the skeleton is perceptible. Here are the visual techniques that break it.
|
||||
|
||||
### Technique 1: Diagonal Connectors
|
||||
|
||||
Streets don't have to run perpendicular. A 45° diagonal connector between two otherwise-gridded streets reads as older than the grid (it predates the block layout, it follows a natural path).
|
||||
|
||||
Visual constraints on diagonals:
|
||||
- Must be at minimum 4vt wide (same as district street minimum, to handle tile-based movement)
|
||||
- Floor material is distinct from both connected street systems — diagonal connectors use the transitional corridor palette (`#181818` neutral dark) regardless of what they connect, because they read as "infrastructure that predates the current layout"
|
||||
- Cannot exceed 45° from grid axis — steeper angles produce tile-movement awkwardness
|
||||
- LOS along a diagonal is blocked at the two ends by the street network it connects — the diagonal is a slot, not an open approach
|
||||
|
||||
One diagonal connector per 4–6 blocks is sufficient. Too many diagonals produce a different regularity.
|
||||
|
||||
### Technique 2: Irregular Setbacks at Block Faces
|
||||
|
||||
Buildings don't need to be flush with the block edge. Setback variation per building along a block face:
|
||||
|
||||
| Setback | Visual result | What it communicates |
|
||||
|---------|--------------|----------------------|
|
||||
| 0vt (flush) | Building wall at block edge | Institutional, dense, planned |
|
||||
| 1–2vt | Narrow threshold space | Semi-private front threshold, step/stoop |
|
||||
| 3–4vt | Small garden/forecourt | Residential, some space claimed outside |
|
||||
| 5–8vt | Significant forecourt | Commercial frontage, civic |
|
||||
| Recessed door | Door 2–3vt inside flush facade | Industrial — the approach is exposed |
|
||||
|
||||
**Rule:** No two adjacent buildings on the same block face should have identical setbacks. The variation is not random — it's driven by access tier (public buildings set back more) and era (Era 1 buildings are flush, Era 3 buildings have planned setbacks). But within those constraints, the setback varies.
|
||||
|
||||
The aggregated effect of setback variation is a block face that is not a straight wall. The building line is irregular. The player's visual experience of the block's edge is broken into a series of small spatial events rather than a continuous surface.
|
||||
|
||||
### Technique 3: Overhead Extension Past Block Boundaries
|
||||
|
||||
On z=4 (overhead layer), buildings can extend past their ground-floor footprint:
|
||||
- Awnings and overhangs: 1–3vt extension into the street
|
||||
- External staircases: 2–4vt extension, adds vertical element
|
||||
- Second-story or gallery bridges: structural extensions that visually connect two adjacent buildings across the street or alley between them
|
||||
|
||||
Overhead extensions do not block movement (the player moves on z=1 floor). But they change the visual experience of the street: it is partially covered, has variable ceiling height, and — critically — it makes the block edge ambiguous. The building's overhead presence is larger than its footprint.
|
||||
|
||||
**At block seams:** An awning or overhead element that spans a block seam reads as a single building structure regardless of which block generates it. The block seam disappears beneath the visual overhead.
|
||||
|
||||
### Technique 4: Infrastructure Routing at Angle to Grid
|
||||
|
||||
A power conduit, a water pipe, an old rail line that pre-dates the current block layout — if it runs at a slight angle to the street grid, it reads as older than the layout it crosses.
|
||||
|
||||
Visual treatment:
|
||||
- Infrastructure that runs at angle to grid uses the maintenance corridor palette (`#181818` + `#4e5054` markers)
|
||||
- It appears on z=4 where it crosses buildings (overhead routing), z=0 where it's underground
|
||||
- Where it emerges at z=1, it creates small visual events: a junction box, a pressure valve, an access hatch
|
||||
|
||||
The diagonal infrastructure is the generator's primary tool for producing "historical palimpsest" in urban districts. It encodes a simple history: "this predates the current layout." The player doesn't need to be told — they see the conduit cutting diagonally across three blocks and understand that something was here before the current plan.
|
||||
|
||||
### Technique 5: Light Territories vs. Grid Territories
|
||||
|
||||
Light pools from `PointLight2D` are circular. They don't respect block edges. A fixture placed near a block edge casts light into both blocks. The player reads the lit area as a single spatial unit regardless of which block generates it.
|
||||
|
||||
**Exploiting this:** Place fixtures deliberately near block edges, particularly at street intersections, to create light territories that span blocks. The bright zone at an intersection reads as a town square, a gathering point, a visible moment — even if it's just two block corners meeting.
|
||||
|
||||
The grid says: this is the corner of Block 3 and Block 7. The light says: this is *the corner*, a single social fact with meaning. Players navigate by light, not by block IDs.
|
||||
|
||||
### Technique 6: Vegetation Overflow and Organic Intrusion
|
||||
|
||||
In planet-side settings, natural elements can cross block boundaries:
|
||||
- A tree planted at the edge of a courtyard extends its canopy (z=4) into the adjacent street
|
||||
- A drainage channel follows gravity rather than block edges — it cuts diagonally through two blocks
|
||||
- Ivy or climbing vegetation on a building facade extends toward an adjacent building
|
||||
|
||||
These organic elements are generation-time decisions: the generator tags certain building edge quarters as "vegetation boundary permitted" and places the appropriate z=4 elements. The visual result is plant matter that doesn't respect the invisible block lines — exactly what makes planet-side settlements feel like they've been there a while.
|
||||
|
||||
### Technique 7: Street Width Variation
|
||||
|
||||
Streets don't have to be uniform width. The same street can narrow where buildings press in and widen where they set back. This produces the visual impression of a street that *evolved* — some merchants built closer to the edge, others further.
|
||||
|
||||
Implementing within the minimum corridor width rules:
|
||||
- Minimum 4vt for district streets (V-05) — this is the floor
|
||||
- Maximum is unconstrained upward — a street can be 8, 12, or 16vt wide at plazas
|
||||
- The width changes happen at building boundaries (where one building's facade gives way to another's)
|
||||
|
||||
A street that varies from 4vt to 8vt to 6vt along its length reads as human-built. A street that is consistently 4vt everywhere reads as designed.
|
||||
|
||||
---
|
||||
|
||||
## 4. Unified Empty Quarter Taxonomy
|
||||
|
||||
Qatux correctly flagged that my Round 1 taxonomy (spatial types) and Nigel's taxonomy (content categories) are at different abstraction levels. Here's the unified version.
|
||||
|
||||
### The Two-Layer Model
|
||||
|
||||
Every empty/unclaimed quarter gets:
|
||||
1. **A spatial type** — the physical geometry and access tier of the space
|
||||
2. **A content category** — the social/economic activity that inhabits it
|
||||
|
||||
These are assigned independently but have compatibility constraints.
|
||||
|
||||
### Layer 1: Spatial Types (5 canonical)
|
||||
|
||||
| Spatial type | Floor width | Access tier | Ambient character |
|
||||
|-------------|-------------|-------------|-------------------|
|
||||
| **Open plaza** | Full quarter (16×16) | Public | Zone palette floor, fixture density normal |
|
||||
| **Service alley** | 2–6vt wide, full quarter depth | Semi-private → restricted | Dark, sparse fixtures, maintenance palette |
|
||||
| **Courtyard** | Full quarter interior (enclosed by adjacent buildings) | Semi-private | Reduced ambient, personal-scale objects |
|
||||
| **Staging ground** | Full quarter, some permanent markers | Semi-private → private | Industrial floor markings, cargo markers |
|
||||
| **Undeveloped gap** | Any width | Restricted by default | Bare floor, no fixtures, very dark |
|
||||
|
||||
### Layer 2: Content Categories (Nigel's 4 + structural baseline)
|
||||
|
||||
| Content category | Who placed it | Economic signal | Faction signal |
|
||||
|----------------|---------------|-----------------|----------------|
|
||||
| **Civic baseline** | No one — it's just maintained infrastructure | Neutral | Neutral (Commission maintains public space) |
|
||||
| **Informal economy** | Individuals, self-organized | Active trade, self-reliance | Low faction control or contested |
|
||||
| **Settlement** | Community, long-term residents | Community investment, stability | High community cohesion, low institutional |
|
||||
| **Economic stress** | Necessity, not choice | Insufficient formal economy | Institutional failure or neglect |
|
||||
| **Faction presence** | Institutional actor | Faction extending influence | High faction control, normalizing presence |
|
||||
|
||||
### Compatibility Matrix
|
||||
|
||||
| | Open plaza | Service alley | Courtyard | Staging ground | Undeveloped gap |
|
||||
|---|-----------|--------------|-----------|----------------|-----------------|
|
||||
| Civic baseline | ✓ primary | ✓ (access infrastructure) | ✓ | ✗ | ✗ |
|
||||
| Informal economy | ✓ primary | ✓ (side-alley stalls) | ✓ secondary | ✗ | ✓ (squatter use) |
|
||||
| Settlement | ✓ (public garden) | ✗ | ✓ primary | ✗ | ✓ (shacks) |
|
||||
| Economic stress | ✓ (abandoned plaza) | ✓ (unauthorized storage) | ✓ | ✓ (mothballed staging) | ✓ primary |
|
||||
| Faction presence | ✓ primary | ✗ | ✗ | ✓ (checkpoint infrastructure) | ✗ |
|
||||
|
||||
### The Full Unified Taxonomy
|
||||
|
||||
Combining both layers produces the specific fill elements:
|
||||
|
||||
**Open Plaza + Civic baseline:** Benches, news ticker (if near transit), waste receptacles, civic signage. Neutral floor (zone palette). Normal fixture density. Zone: public face of a district.
|
||||
|
||||
**Open Plaza + Informal economy:** Market stalls (temporary awning structures, z=4), vendor cart positions, cluster of seating near central stall. Floor markings from stall activity. Slightly warmer fixture temperature than zone baseline (vendors bring their own lights). Access: nominally public but stall arrangement creates semi-private pockets.
|
||||
|
||||
**Open Plaza + Settlement:** Community garden planters, improvised seating clusters (salvaged furniture, not purchased), a shrine or memorial marker (small z=2 object). Warm, low-tech. The garden has overhead crop layer at z=4. Lighting: minimal, personal-scale.
|
||||
|
||||
**Open Plaza + Economic stress:** Abandoned stalls (awning frames without cloth), cracked floor (visual floor tile variant with cracks — same palette, different texture), no functioning fixtures (dark), possibly a temporary windbreak (partial barrier). Reads as: there used to be activity here.
|
||||
|
||||
**Open Plaza + Faction presence:** Commission kiosk (small structure, institutional-grey palette, z=4 signage in neutral Michroma labels), checkpoint barrier positions (can be raised or lowered), public notice board with official announcements. Cold LED lighting regardless of zone.
|
||||
|
||||
**Service alley + Civic baseline:** Drainage channel, access hatches to z=0 maintenance. Narrow, dark, maintenance palette. Objects: conduit runs, junction boxes.
|
||||
|
||||
**Service alley + Informal economy:** Vendor carts staged here between market hours. Informal repair shop set into an alcove (small bench, tools, spare parts on shelving). Lighting: one additional fixture hung by the vendor, slightly warmer than alley baseline.
|
||||
|
||||
**Service alley + Economic stress:** Unauthorized storage — cargo containers pushed against one wall, none of them labeled correctly. Possibly a temporary shelter structure. Very dark. Access: technically public but social convention says otherwise.
|
||||
|
||||
**Courtyard + Settlement:** Private gardens, seating built into the courtyard walls, personal decorations on the surrounding building faces (z=4 neighbor additions: window boxes, hanging fabric, a clothesline). This is the warmest non-social-site space the generator produces. Enclosed, personal, warm.
|
||||
|
||||
**Courtyard + Informal economy:** Small informal workshop at one end (tools, a bench), goods laid out for inspection or trade. Semi-regular social gathering space — these become recurring NPC locations.
|
||||
|
||||
**Staging ground + Civic baseline:** Vehicle bay positions (dock marker lines on floor), cargo transporter docking points, maintained by the district authority. Clean industrial palette.
|
||||
|
||||
**Staging ground + Economic stress:** Mothballed staging. Old dock points, defunct equipment left in place, no active use. The generators are off — no lighting except ambient bleed from adjacent zones.
|
||||
|
||||
**Staging ground + Faction presence:** Faction-controlled logistics checkpoint. Corporate branded equipment, manifest scanners, branded service vehicles. If Commission-aligned: grey and cold LED. If Syndic-aligned: corporate colors (within saturation rules — muted versions of faction identity).
|
||||
|
||||
**Undeveloped gap + Economic stress:** Primary use. Shack structures (makeshift, Era 0 materials — salvaged, pre-palette), unauthorized occupation. Narrow footprint. Reads as: the gap between buildings that someone decided to live in.
|
||||
|
||||
**Undeveloped gap + Informal economy:** Squatter market — the gap between two buildings has become a covered passage lined with informal trade. Narrow, overhead covered with salvaged material (z=4), dim personal lighting.
|
||||
|
||||
---
|
||||
|
||||
## 5. Visual Vocabulary for Nigel's Flavor Categories
|
||||
|
||||
Nigel asked specifically for the visual vocabulary that distinguishes his four flavor categories. Here it is — the visual signal the player reads at a glance, before they're close enough to see detail.
|
||||
|
||||
### Category 1: Informal Economy Indicators
|
||||
|
||||
**At-a-glance signal:** Warm irregular lighting against the zone baseline. Awning structures on z=4 that don't align with building facades — they're temporary additions. Goods on the ground (floor-level objects, z=2) in clusters that suggest display, not storage.
|
||||
|
||||
**Visual vocabulary:**
|
||||
- **Awning material:** Worn fabric or salvaged panel on z=4, at 65% opacity (semi-transparent — you can see through worn fabric). Color: zone-warm variant (amber or dusty orange-brown, staying below 20% saturation).
|
||||
- **Goods display:** Small objects in regular low-grid patterns (z=2), 0.5–1vt spacing, muted warm-adjacent colors (dried goods, second-hand items — nothing vivid).
|
||||
- **Vendor lighting:** One additional small PointLight2D per stall, radius 3–4vt, temperature `#f0d090` (warm yellow — warmer than zone fixtures). Creates warm pooling that doesn't match zone's fixture grid.
|
||||
- **Floor wear:** A visually-distinct floor tile variant in front of each stall — foot-traffic-worn version of the zone palette floor, slightly lighter/warmer. Marks where people stand.
|
||||
- **Sound indicators (visual grammar §317):** High foot traffic = dense sound ping patterns at market hours.
|
||||
|
||||
**Distinction from faction commercial:** Informal economy has no signage (or handwritten signage in environmental text spec). Faction commercial has printed, standardized signage.
|
||||
|
||||
### Category 2: Settlement Indicators
|
||||
|
||||
**At-a-glance signal:** Organic overhead elements where no overhead elements should be (container gardens on z=4, non-institutional). Seating that's clearly brought from somewhere else (non-matching furniture). The zone palette floor is correct but something on z=4 is soft, plant-based, or personal.
|
||||
|
||||
**Visual vocabulary:**
|
||||
- **Container gardens:** Rectangular planter objects on z=2 (1×1 or 2×1 visual tiles), with dark soil `#0e1008` top and trailing vegetation on z=4 (`#0a1006` leaf cluster at 60% opacity). Distinctly organic among industrial/institutional surroundings.
|
||||
- **Improvised seating clusters:** Mismatched furniture objects on z=2. In a zone where all furniture is institutional (same material, same design), non-matching furniture is immediately readable. Warmer material tone than zone standard.
|
||||
- **Shrine/memorial:** Small z=2 object with accumulated small items around it (the accumulation is visual — a cluster of tiny objects with personal-scale z=4 elements above). No lighting fixture — lit by candle (tiny PointLight2D, radius 1vt, `#ffb040` very warm, dim).
|
||||
- **Overall ambient:** Warmer than zone baseline in this quarter — the accumulated human presence and personal lighting offsets the institutional zone character.
|
||||
|
||||
**Distinction from informal economy:** Settlement indicators are about residence and community, not trade. No awnings. No goods-on-display floor patterns. The warmth comes from plants and personal objects, not from commercial activity.
|
||||
|
||||
### Category 3: Economic Stress Indicators
|
||||
|
||||
**At-a-glance signal:** Lower lighting than zone baseline (fixtures removed or failed). Structural elements in worse condition (visual tile variants with damage or decay). Objects in wrong positions — cargo pushed against a wall, equipment abandoned mid-task.
|
||||
|
||||
**Visual vocabulary:**
|
||||
- **Failed/missing fixtures:** Where the zone expects a fixture, there is none (or only a stub — a mounting bracket with no lamp). The area around the gap is darker than zone baseline. This is the clearest single signal of economic stress: lights are out.
|
||||
- **Damaged floor tile variant:** Same hex values as zone floor, but with crack pattern overlay (z=1 layer, 50% opacity crack texture). The palette is correct; the *condition* is wrong.
|
||||
- **Abandoned equipment:** Objects in positions that suggest mid-task abandonment — a cargo loader parked at an angle, not docked; a door propped open with a crate; tools left on a workbench with no NPC. Object placement is irregular relative to how the zone normally functions.
|
||||
- **Temporary shelter construction:** Makeshift wall sections (z=2, non-palette materials — warm salvaged brown or neutral salvaged grey, outside normal zone material set) forming a partial enclosure within the quarter.
|
||||
- **Sparse foot-traffic floor wear:** Fewer wear marks than zone standard — fewer people use this space than its design intended.
|
||||
|
||||
**Distinction from undeveloped gap:** Economic stress has attempted use. The undeveloped gap has no attempted use — it's raw material. Stress indicators show a space that was used and is now failing.
|
||||
|
||||
### Category 4: Faction Presence Indicators
|
||||
|
||||
**At-a-glance signal:** Standardized signage (printed, institutional) where informal signage would otherwise be. Cold LED lighting regardless of zone temperature. Objects that look like they belong to a larger system — they have the same aesthetic as other faction objects elsewhere.
|
||||
|
||||
**Visual vocabulary:**
|
||||
- **Commission presence:** Cold institutional grey objects (`#b8bec4` surface material, same as gate cluster). Cold LED fixture on a mounted post (`#f2f4ff` temperature). A notice board with official announcements (environmental text in Michroma, 11px, 70% opacity, properly aligned). The kiosk/checkpoint structure is clearly manufactured, not improvised.
|
||||
- **Corporate/Syndic presence:** Branded objects — the material is still within zone palette range (corporate entities use zone-appropriate materials but with branded applications). Corporate marking appears as a subtle logo on z=4 or as branded signage. Lighting: slightly cooler and more uniform than zone standard (corporate spaces are *maintained*).
|
||||
- **Union hall / labor presence:** Notice board with text (layer 4, higher-opacity than Commission notices — these have been posted by people who want them read). Seating arranged for meeting (chairs in a deliberate cluster, not scattered). Informal but organized.
|
||||
- **Absence of faction presence (readable as absence):** A quarter where there WERE Commission markers and they've been removed — stub mount points visible on z=4, blank walls where signage was. The absence of faction presence is as readable as presence.
|
||||
|
||||
**Distinction from civic baseline:** Civic baseline is neutral, maintained, no faction signage. Faction presence is cold and standardized (Commission) or branded (corporate) or organized-informal (labor). If you see a sign, you're in faction territory.
|
||||
|
||||
---
|
||||
|
||||
## 6. Responding to OQ-3: Do Quarters Have Social Meaning?
|
||||
|
||||
Ozzie asked whether the choice of quarter fill content has downstream social consequences. The answer from a visual perspective: **yes, and the visual grammar is what makes those consequences legible.**
|
||||
|
||||
When a quarter is assigned "Settlement — Container gardens," the visual output is:
|
||||
- Container planters in a semi-private courtyard
|
||||
- Slightly warmer ambient than zone baseline
|
||||
- Non-institutional overhead elements
|
||||
|
||||
But the *social meaning* that Ozzie wants requires the visual to *communicate* something about who lives here and what kind of space this is. Here's what the visual grammar tells a player who reads it:
|
||||
|
||||
- **Container gardens in an industrial district** → people have been here long enough to invest in food independence. This is a mature community. Slow trust, deep roots.
|
||||
- **Abandoned equipment (economic stress)** → something changed. People were here, now less so. Ask why.
|
||||
- **Commission kiosk in a residential zone** → surveillance normalizing in a space it didn't previously reach. Something triggered this expansion.
|
||||
- **Informal market stalls in a previously-staged zone** → the official function of this space has been displaced by informal economy. Power is contested here.
|
||||
|
||||
These readings are available to any player — not just the investigator. The relationship-seeker reads the container gardens as "warm community, worth investing in." The trader reads the market stalls as "economic activity, possible contacts." The explorer reads the abandoned equipment as "something happened here, worth investigating."
|
||||
|
||||
The visual grammar doesn't point the way. It describes the social reality. The player applies their own lens.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**District edge bleed:** Transitional blocks using palette interpolation, shared infrastructure that ignores district lines, light pools that span boundaries. The player feels district changes as gradual temperature shifts, not lines.
|
||||
|
||||
**Non-urban terrain:** Five natural zone palettes (farmland, wilderness, ocean/beach, mountain, secluded town) using global ambient instead of fixture pools. Natural LOS anchors (trees, rocks, terrain features) at the same 16vt max interval rule. Wilderness uses overhead layer density (canopy) to create the occlusion that walls provide in urban spaces.
|
||||
|
||||
**Anti-grid techniques:** Seven techniques. Diagonals as historical infrastructure, irregular setbacks at block faces, overhead extension past block edges, angled infrastructure as palimpsest, light territories that span blocks, vegetation overflow in planet-side settings, street width variation. The visual grammar never aligns perfectly with the block grid — it uses the grid as an invisible scaffold.
|
||||
|
||||
**Unified taxonomy:** Two-layer model — spatial type (my 5 types) × content category (Nigel's 4 + civic baseline) = 25 possible combinations, with a compatibility matrix that constrains illogical assignments. Every quarter is assigned both a type and a category.
|
||||
|
||||
**Flavor visual vocabulary:** Each of Nigel's four categories has a distinct at-a-glance signal: warm irregular lighting (informal economy), organic overhead elements (settlement), failed lighting + damage tiles (stress), cold standardized objects (faction presence).
|
||||
|
||||
**Multi-playstyle acknowledgment:** The visual grammar communicates social reality, not investigation routes. Every player reads the same space through their own lens. The grammar serves them all because it describes who inhabits a space and on whose terms — which is the information that all playstyles need.
|
||||
@@ -0,0 +1,490 @@
|
||||
# Generator Architecture Workshop — Round 3: Araminta
|
||||
## Palette Granularity, Organic Streets, Vertical Visuals, Destruction, Horizon, and What's Behind the Wall
|
||||
|
||||
**Author:** Araminta (Visual Designer)
|
||||
**Date:** 2026-02-27
|
||||
**Workshop:** Generator Architecture (Ticket #562)
|
||||
**Responding to:** Round 2 outputs (all participants), Qatux Round 2 notes, Lead Round 3 directives
|
||||
**Status:** Round 3 — convergence
|
||||
|
||||
---
|
||||
|
||||
## Opening: This Round Is About Decisions
|
||||
|
||||
Round 1 was constraints. Round 2 was extension. Round 3 is convergence. I'm going to be more prescriptive here than in previous rounds — these are my recommendations, not open explorations. Where I say "Rule:", that's a proposal for locking. Where I say "open", I'm flagging it for the room.
|
||||
|
||||
One fast note before I start: Ozzie's partial dissent on the grid (OQ-R3-A) is legitimate. My seven anti-grid techniques from Round 2 are visual camouflage, not structural change. I'll address organic/non-rectilinear districts directly in Section 2 and give her an actual answer, not a deflection.
|
||||
|
||||
---
|
||||
|
||||
## 1. Palette Granularity — The Modifier System
|
||||
|
||||
### The Problem with Six Fixed Palettes
|
||||
|
||||
The lead is right. "Industrial farming ≠ rustic farming" isn't solved by adding more base palettes. If I add a 9th base palette for every agricultural variant, I end up with dozens of fixed entries that still fail to capture the combinatorial variety Miri's cultural ingredients system produces. The correct architecture is a **modifier system** layered on a smaller base palette set.
|
||||
|
||||
The base palettes define floor, ambient, and water (the ground you stand on and the light that falls on it). The modifiers define everything humans have added to that ground. That's the right separation.
|
||||
|
||||
### The Base Terrain Palettes (Revised: 8 Types)
|
||||
|
||||
I'm expanding from 6 to 8 to cover terrain types Round 2 left underspecified:
|
||||
|
||||
| ID | Name | Floor base | Ambient | Water/reflective |
|
||||
|----|------|------------|---------|-----------------|
|
||||
| T1 | Temperate farmland | `#1a1510` dark warm brown | `#0e0c08` warm near-black | n/a |
|
||||
| T2 | Industrial/greenhouse | `#141618` grey-green dark | `#0c0e0c` flat cool | n/a |
|
||||
| T3 | Forest/wilderness | `#0e120e` organic dark | `#080a08` near-black | n/a |
|
||||
| T4 | Grassland/plains | `#141810` muted green-grey | `#0c0e08` cool-warm | n/a |
|
||||
| T5 | Coastal water | `#060c14` deep near-black blue | `#060810` cold near-black | `#c0c8d8` specular animated |
|
||||
| T6 | Beach/coastal margin | `#2a2218` warm dark tan | `#0c0a06` warm near-black | n/a |
|
||||
| T7 | Mountain/high terrain | `#181c22` dark blue-grey stone / `#c8d8e8` snow | `#10141a` cold dark | n/a |
|
||||
| T8 | Desert/arid | `#221c12` dusty warm dark | `#10100c` warm-neutral | n/a |
|
||||
|
||||
T1 and T2 are the two farmland base types — they differ at the floor and ambient level. Industrial farms (T2) have a distinctly greyer ambient because they operate under artificial light even outdoors. Rustic farms (T1) have warm organic ground.
|
||||
|
||||
This is the only place where I'm adding two entries for the same terrain category — because the lead specifically called out this distinction, and it genuinely affects the ambient regime (natural light vs. artificial) not just the structures on top.
|
||||
|
||||
### The Three Modifier Axes
|
||||
|
||||
Every non-urban terrain district gets three modifier assignments, drawn from the society profile:
|
||||
|
||||
**Modifier A: Structure Material Character** (from heritage root)
|
||||
|
||||
| Heritage root | Structure material | Surface treatment | Overhead character |
|
||||
|---------------|--------------------|-------------------|-------------------|
|
||||
| Iron | Corrugated metal, welded joints | Cold precise | Functional metal (ducts, conduits exposed) |
|
||||
| Stone | Carved stone, thick masonry | Cool solid | Low overhead, heavy permanent structures |
|
||||
| Frost | Sparse insulated panel, minimal | Cold minimal | Very sparse; exposure-resistant |
|
||||
| Vine | Timber, organic fibers, woven | Warm organic | Dense personal overhead (drying racks, planters) |
|
||||
| Tide | Weathered timber, rope, marine metal | Salt-worn neutral | Maritime: nets, moorings, tackle |
|
||||
| Dust | Rammed earth, fired clay | Warm-brown dry | Low; heat-efficient, minimal projection |
|
||||
| Salt | Preserved wood, sealed containers | Neutral functional | Practical personal (salt storage, preservation apparatus) |
|
||||
| Arc | Mixed-material, jury-rigged | Variable | Dense improvised overhead (whatever was available) |
|
||||
| Jade | Refined composite, polished | Cool refined | Deliberate aesthetic overhead (trellises, decorative structures) |
|
||||
| Spice | Vivid dyed textile over standard base | Warm + accent | Dense fabric overhead, colorful within saturation rules |
|
||||
|
||||
**Rule:** Heritage root is the primary modifier for structure material. In a two-root heritage blend, the dominant root (highest weight) determines material; the secondary root adds accent elements in the overhead layer.
|
||||
|
||||
**Modifier B: Economic Tier** (from economic function + economic pressure combination)
|
||||
|
||||
| Tier | Condition | Object density | Lighting presence |
|
||||
|------|-----------|----------------|-------------------|
|
||||
| Prosperous | Maintained, new materials | High (full budget) | Full fixtures, maintained |
|
||||
| Standard | Functional, showing age | Moderate | Fixtures present, some failed |
|
||||
| Subsistence | Worn, improvised repairs | Low | Minimal; improvised warm sources |
|
||||
| Failing | Degraded, abandoned sections | Very low | Failed fixtures; dark |
|
||||
|
||||
**Rule:** Economic tier modifies *condition* and *density*, not palette. A Stone-heritage prosperous farm and an Iron-heritage prosperous farm have different materials but similar density and maintenance. Economic tier crosses material character cleanly.
|
||||
|
||||
**Modifier C: Era** (from block era tag)
|
||||
|
||||
| Era | Material generation | Structural scale | Infrastructure visible? |
|
||||
|-----|---------------------|------------------|------------------------|
|
||||
| Era 1 | Hand-built; organic/natural construction | Human-scale, small | None visible |
|
||||
| Era 2 | Standardized; mixed natural and fabricated | Intermediate | Some surface-mounted |
|
||||
| Era 3 | Modern/industrial; fabricated, uniform | Large-scale possible | Integrated, less visible |
|
||||
|
||||
Era affects the GENERATION of structures within the chosen material character. An Iron-heritage farm that was built in Era 3 has precisely welded industrial metal structures. An Iron-heritage farm built in Era 1 has hammered metal over stone foundations. Same heritage root, different era expression.
|
||||
|
||||
### Faction Overlay (Optional, Additive)
|
||||
|
||||
Applied on top of any base palette + modifier combination:
|
||||
|
||||
| Faction | Overlay effect |
|
||||
|---------|---------------|
|
||||
| Commission | Cold LED work lights replace warm sources; institutional grey secondary structures (checkpoint posts, monitoring equipment); Michroma signage |
|
||||
| Syndic-corporate | Branded secondary structures; slightly colder, more uniform lighting than zone standard; corporate marking on z=4 |
|
||||
| Independent (labor) | Personal accumulated objects; union notice boards; warm improvised fixtures |
|
||||
| None | No overlay; pure heritage + economic tier + era |
|
||||
|
||||
### How Many Distinct Visual Feels?
|
||||
|
||||
Rough count:
|
||||
- 8 base terrain types
|
||||
- 10 heritage modifiers (functionally 6–8 meaningfully distinct groups)
|
||||
- 4 economic tiers
|
||||
- 3 eras
|
||||
- 5 faction overlays (including none)
|
||||
|
||||
**Strongly differentiated** (a player would name them differently): ~40–50. "Industrial greenhouse farm under Commission control," "subsistence Stone-heritage farmstead, old," "prosperous Tide-heritage coastal village, Era 2" — these feel like distinct places.
|
||||
|
||||
**Meaningfully distinct** (a player would perceive as different): ~200+. The granularity at which heritage blend ratios differ (a 70/30 Frost/Iron vs. 50/50) is subtle but present.
|
||||
|
||||
**The important bound:** A player going through 300 worlds encounters each combination at most a few times. The template library (D-025) remains the practical ceiling on variety, as Miri noted — but the modifier system ensures that two farmland districts with the same template read completely differently when one is a prosperous Vine-heritage settlement and the other is a failing Iron-heritage industrial operation.
|
||||
|
||||
---
|
||||
|
||||
## 2. Organic Streets and Grid Breathing
|
||||
|
||||
### Direct Answer to Ozzie's Demand
|
||||
|
||||
Ozzie: "Tell me the grid can breathe."
|
||||
|
||||
**It can. Here is what that means and what it requires visually.**
|
||||
|
||||
There are three street/district layout modes. The visual grammar needs rules for all three.
|
||||
|
||||
### Layout Mode 1: Grid Districts
|
||||
|
||||
What we've been designing until now. Perpendicular streets, regular block footprints. Institutional, industrial, corporate, station interior. The grid communicates: this was planned, authority made this.
|
||||
|
||||
Visual rules: as specified in Rounds 1 and 2. No changes.
|
||||
|
||||
### Layout Mode 2: Relaxed Grid (Breathing Grid)
|
||||
|
||||
Adjacent districts can have **different orientations** (rotated grid). A residential quarter at 15° off the main station grid reads as "built before the main plan was established, or outside its authority." The visual grammar's rules are **direction-agnostic** — corridor width minimums, LOS anchor intervals, zone palette assignments — all apply regardless of street orientation. The only additional rule needed:
|
||||
|
||||
**Grid-rotation boundary treatment:** Where two districts with different orientations share an edge, the transition strip must handle the angular discontinuity. The floor tiles in the transition strip use the "angled transition" variant (see below). Street connections between the two grids happen through **diagonal connectors** (my Round 2 Technique 1), which are now read not as historical infrastructure but as the literal connection point between two urban grids of different orientation.
|
||||
|
||||
Visual rule: the transition strip between a 0° grid and a 15° grid uses **the older era** of the two districts (as Tyre's transition block logic specifies), reinforcing the reading that one grid predates the other.
|
||||
|
||||
### Layout Mode 3: Organic Districts
|
||||
|
||||
Streets that curve. Block shapes that are L, T, or irregular polygon. This is what Ozzie is really asking for.
|
||||
|
||||
**What "organic" means in a tile-based system:**
|
||||
|
||||
The game uses a tile grid. True curves don't exist — but the *visual impression* of curves does. Organic streets are produced by a specific tile vocabulary.
|
||||
|
||||
**Organic street visual rules:**
|
||||
|
||||
1. **Curve mechanism**: Curves happen as 45° jogs in the street direction. A street that runs north for 6 tiles, then jogs northeast for 4 tiles, then returns north — from 16+ visual tiles away, this reads as a gentle curve. The jog is a visual feature, not a defect.
|
||||
|
||||
2. **Angled wall tile variants**: Buildings on organic streets need wall faces at 45° and 135°. These are specific tile variants with the same zone palette material but a diagonal face. A building that presents a 45° corner to a diagonal street reads as organic — it was built to fit the street, not placed on a grid.
|
||||
|
||||
3. **Street-width variability increases in organic districts**: In a grid district, street width might vary from 4–8 vt. In an organic district, it varies from 4–14 vt without feeling wrong. A street that opens into a piazza-width as it bends is the correct shape for an organic settlement.
|
||||
|
||||
4. **Intersection treatment for non-90° intersections:**
|
||||
- Obtuse intersection (>90°): the corner building uses a recessed setback, presenting a smooth face. The wider angle makes the building appear to "anchor" the intersection.
|
||||
- Acute intersection (<90°): the corner building has a wedge-shaped setback or a chamfered corner (45° wall face). The wedge building is a classic organic settlement marker.
|
||||
- These require "wedge corner" and "chamfered corner" floor tile variants.
|
||||
|
||||
5. **Block boundaries in organic districts are building faces, not coordinates**: The visual grammar stops using block edges as generation hints. What the player reads as a "block" is defined by the street network on three or four sides. This block might be irregular in every dimension. The generator knows the block as a data structure; the player reads it as "the cluster of buildings between these streets."
|
||||
|
||||
6. **Landmark density increase**: Without a grid, players lose orientation easily. Organic districts require **mandatory landmark placement every 12 visual tiles** (vs. 16 in grid districts). These landmarks are distinctive buildings (unusual rooftop shape, unusual material), significant trees in planet-side settings, or prominent corner objects. The navigator's landmarks replace the navigator's grid.
|
||||
|
||||
7. **LOS anchor relaxation**: Organic irregularity IS the LOS anchor. A street that bends creates a sightline break at the bend. The max 16 vt anchor interval still applies, but in organic districts, the street geometry itself contributes to anchor count. A block with three irregular protrusions needs fewer interior anchors than a perfectly rectangular block.
|
||||
|
||||
### Organic vs. Grid Visual Grammar Summary
|
||||
|
||||
The core visual grammar rules **do not change** for organic districts. Zone palettes, lighting temperature, entity hierarchy, z-layer stack — all identical. What changes is:
|
||||
|
||||
| Rule | Grid district | Organic district |
|
||||
|------|---------------|-----------------|
|
||||
| Corner tile variants | 90° only | 90°, 45°, 135°, chamfered |
|
||||
| Landmark interval | 16 vt | 12 vt |
|
||||
| Street width range | 4–8 vt | 4–14 vt |
|
||||
| Block boundary | Coordinate-aligned | Face-defined by street network |
|
||||
| LOS anchor source | Structural elements | Structural elements + street geometry |
|
||||
| Setback variation | ±3 vt from baseline | ±6 vt (wider range) |
|
||||
|
||||
The visual grammar is orientation-agnostic and curvature-extensible. This is the right answer to Ozzie.
|
||||
|
||||
---
|
||||
|
||||
## 3. Vertical Visuals — Height in 2D Top-Down
|
||||
|
||||
### The Problem
|
||||
|
||||
In top-down view, a 50-floor skyscraper and a 3-floor office building have the same roof footprint. They're the same from above unless the visual grammar does work to differentiate them.
|
||||
|
||||
### The Height Tier System
|
||||
|
||||
I propose four height tiers, each with a defined visual signature:
|
||||
|
||||
| Tier | Floors | Roof material complexity | Shadow length | Shadow hardness |
|
||||
|------|--------|--------------------------|---------------|-----------------|
|
||||
| S1 (Low-rise) | 1–3 | Simple parapet, zone material | 2–4 vt | Soft (gradient falloff) |
|
||||
| S2 (Mid-rise) | 4–10 | HVAC units, ventilation stacks visible | 5–10 vt | Medium |
|
||||
| S3 (High-rise) | 11–30 | Mechanical arrays, access structures, antenna | 12–20 vt | Hard (defined edge) |
|
||||
| S4 (Extreme) | 30+ | Minimal — antenna farm, sensor cluster, landing area | 25–40 vt | Very hard |
|
||||
|
||||
**The primary visual signal is shadow.** Shadow length scales with height. A 50-floor building casts a 30+ tile shadow. Players learn this grammar naturally — they see a long shadow and understand: something tall is nearby.
|
||||
|
||||
### Shadow Direction
|
||||
|
||||
Shadow direction is constant within a district and set at generation time. It represents the primary light source angle (the local star's position for planet-side; the orbital station's artificial sun angle for stations).
|
||||
|
||||
**Rule:** Shadow falls in one consistent direction per district. This direction is recorded in the DistrictSkeleton (as a simple angle, 0–359°). All buildings in the district cast their shadow at that angle. This creates coherent lighting across the district.
|
||||
|
||||
**Station exception:** In sealed station environments, the light strips run along the "ceiling" of the station, creating a diffuse downward illumination with no directional shadow. In station interiors, building height is communicated through **penumbra width** (ambient occlusion at the building base) rather than directional shadow. Taller station buildings have a wider soft-dark band at their ground level.
|
||||
|
||||
### Shadow Visual Treatment
|
||||
|
||||
Shadow tiles are **floor-layer overlays** (a slight darkening of the floor material beneath the shadow). They're not z=1 objects — they're a tint pass on the floor tiles in the shadow footprint.
|
||||
|
||||
| Shadow type | Visual treatment |
|
||||
|-------------|-----------------|
|
||||
| S1 soft shadow | 2–4 tile gradient fade, max opacity 25% darkening |
|
||||
| S2 medium shadow | Defined edge with 2-tile softening, max opacity 35% |
|
||||
| S3 hard shadow | 1-tile softening, max opacity 45% |
|
||||
| S4 extreme | No softening on long edge, max opacity 50% |
|
||||
|
||||
**The shadow as gameplay element:** Hiding in a tall building's shadow reduces the caster's ambient lighting, which affects the fog-reveal properties and visual detectability. This is a natural gameplay consequence of the height communication system, not an engineered feature.
|
||||
|
||||
### Rooftop Visual Vocabulary by Tier
|
||||
|
||||
The roof of a building is visible from above. It should communicate what the building IS, not just how tall it is.
|
||||
|
||||
**S1 (Low-rise):** Zone-palette roof material. A residential building has a simple flat roof or slight parapet. An industrial building has vent stacks (small z=4 objects). No structural complexity visible.
|
||||
|
||||
**S2 (Mid-rise):** HVAC clusters (irregular z=4 groupings, dark metal, 2–4 vt wide), stairwell access structures (small box on one corner), possibly a loading bay indicator if commercial. The roof is busy in a functional way.
|
||||
|
||||
**S3 (High-rise):** Complex mechanical array on z=4. Communications masts. Access platforms. If commercial: possible rooftop-level signage visible from above. If residential tower: rooftop garden elements (plant objects, seating). The roof reads as a separate zone with its own function.
|
||||
|
||||
**S4 (Extreme — skyscraper):** Sparse but distinctive. The building's footprint at this height is functional rather than comfortable. Antenna farm or sensor cluster (thin vertical structures on z=4). Possibly a landing platform (helipad equivalent). The roof is visually minimal because at this height, only essential infrastructure is maintained.
|
||||
|
||||
### Neural Insert Integration
|
||||
|
||||
In **perception mode** (insert overlay, z=6), building heights are tagged. The insert displays a small elevation indicator adjacent to buildings — a bar graph scaled to height tier. This is the only time building height is explicitly labeled; outside perception mode, the player reads height through shadow and roof complexity.
|
||||
|
||||
---
|
||||
|
||||
## 4. Destruction Visuals
|
||||
|
||||
### The Visual Grammar of Destruction
|
||||
|
||||
**Core principle:** Destruction does not create new palette colors. It corrupts the existing palette. A destroyed gate cluster area still uses gate cluster materials — just in their broken, exposed, or burned variants. This keeps destroyed areas visually coherent with their zone and prevents destruction from looking like a generic "brown rubble" overlay.
|
||||
|
||||
### Destruction Stages
|
||||
|
||||
**Stage 1 — Active (event in progress)**
|
||||
|
||||
This stage is brief — active fire/explosion. Visual markers:
|
||||
- Fire glow: `#ff6010` point lights at maximum intensity (far exceeding any zone palette fixture)
|
||||
- Smoke layer (z=5, above fog): dark grey-brown particles, animated, at 70–90% opacity in affected area
|
||||
- Structural instability indicator: flickering of any remaining fixture lights in affected area (the lighting phase-shifts, a sign of power disruption)
|
||||
|
||||
**Stage 2 — Fresh Aftermath (0–48 in-game hours)**
|
||||
|
||||
The smoke clears. The damage is visible.
|
||||
|
||||
| Visual element | Specification |
|
||||
|----------------|--------------|
|
||||
| Scorched floor | Zone floor hex value, saturation reduced 60%, brightness reduced 15%, slight red-shift (add `#0c0402` to RGB) |
|
||||
| Rubble objects | Zone wall/structure material, irregular shapes on z=2, 40% opacity — they're still there but partially blasted away |
|
||||
| Debris scatter | Small fragments (1×1 tile z=2 objects) at radial distribution from blast center |
|
||||
| Emergency barriers | Commission-grey `#787e84` temporary fencing on z=2; bright orange `#e05c20` site marking tape |
|
||||
| Emergency lighting | Small PointLight2D, `#ff9040` warm orange (emergency lanterns), at 50% normal radius |
|
||||
| Exposed infrastructure | Infrastructure layer becomes visible (see Section 6 — this is the same exposure mechanism as wall breach) |
|
||||
| Missing overhead (z=4) | Roof elements removed in blast radius — sky/ambient fully visible |
|
||||
|
||||
**Stage 3 — Stabilized**
|
||||
|
||||
48h+ after event. No active emergency. The area is safe but not repaired.
|
||||
|
||||
| Visual element | Specification |
|
||||
|----------------|--------------|
|
||||
| Cold rubble | Same rubble objects as Stage 2, now at 80% opacity (solidified) |
|
||||
| Permanent barriers | Concrete block or heavy fencing (`#4a5058`) replacing emergency barriers |
|
||||
| Dark zone | No fixture lighting in affected area — standard darkness |
|
||||
| Reconstruction markers | Site markers (`#e05c20` orange, standard shapes) — 3–5 per affected block |
|
||||
|
||||
**Stage 4 — Reconstruction**
|
||||
|
||||
Active repair in progress.
|
||||
|
||||
| Visual element | Specification |
|
||||
|----------------|--------------|
|
||||
| Construction scaffolding | Metal scaffolding on z=4, `#505458` grey, partial opacity 80% |
|
||||
| Material mix | New-era floor tiles adjacent to old-era tiles — visible seam where old and new meet |
|
||||
| Worker spawns | NPC spawn points in affected area (construction workers as NOBODY pattern) |
|
||||
| Partial roof return | z=4 elements return to chunks where repair is complete |
|
||||
|
||||
**Stage 5 — Healed Scar**
|
||||
|
||||
Reconstruction complete but history visible.
|
||||
|
||||
| Visual element | Specification |
|
||||
|----------------|--------------|
|
||||
| Era mismatch | The repaired area uses a newer era material than surroundings (same palette, visibly cleaner) |
|
||||
| Floor seam | Subtle grout/joint pattern difference at the repair boundary |
|
||||
| Memorial marker | Optional z=2 object if the event was significant — same specification as Settlement shrine |
|
||||
|
||||
### The Destruction Palette (Summarized)
|
||||
|
||||
These are the corruption values applied to any zone palette material:
|
||||
|
||||
| Element | Modification |
|
||||
|---------|-------------|
|
||||
| Scorched floor | Desaturate 60%, darken 15%, add `#0c0402` red-tint |
|
||||
| Charred structure | Base material, opacity 40–80% (varies by blast proximity) |
|
||||
| Fire glow | `#ff6010` high intensity — NOT zone palette, always the same |
|
||||
| Exposed infrastructure — power | `#c8b840` yellow-gold (standardized regardless of zone) |
|
||||
| Exposed infrastructure — water/coolant | `#4888c8` mid blue |
|
||||
| Exposed infrastructure — data/comm | `#b8b8b8` light grey, thin |
|
||||
| Exposed structure core (rebar/beam) | `#3a3e42` dark metal |
|
||||
| Open sky/void (where roof removed) | `#c8d8f0` sky ambient at 100% — deliberately bright |
|
||||
|
||||
**The open sky tile** deserves special attention. It's the only floor-layer element in the game that glows brighter than the surrounding ambient (other than snow terrain). A destroyed building with its roof removed shows a bright sky-colored floor where the ceiling was. This is visually striking and communicates "open to sky" instantly. It's also a gameplay cue: line-of-sight changes dramatically in open-roofed areas.
|
||||
|
||||
### Gas Explosion Specifically
|
||||
|
||||
The lead's example: a gas explosion destroys part of a district.
|
||||
|
||||
Gas explosions are hot, fast, and clean — no lingering fire. Visual signature:
|
||||
1. Circular scorch pattern on floor tiles, centered on explosion point
|
||||
2. Radial debris distribution (rubble objects scattered at 45°, 90°, 135°, etc. intervals for regularity)
|
||||
3. Structural deformation: wall stub objects at irregular heights at the blast boundary
|
||||
4. Exposed infrastructure: any walls within blast radius expose their infrastructure layer
|
||||
5. Cleared zone: the explosion center is OPEN — no furniture, no objects, debris pushed outward not piled at center
|
||||
|
||||
---
|
||||
|
||||
## 5. Horizon as Landmark (OQ-R3-E)
|
||||
|
||||
### The Answer: Both Palette AND Landmark Reservation
|
||||
|
||||
The ocean zone palette handles how the water looks and feels to be near. The landmark reservation system handles whether the discovery moment is guaranteed.
|
||||
|
||||
**Rule: Coastal districts must include a "horizon view" landmark reservation** in the DistrictSkeleton. This is a mandatory constraint: one block on the coastal edge of the district must have an unobstructed view corridor of minimum 8 visual tiles from the street to the water.
|
||||
|
||||
The landmark reservation is not a structure. It is **negative space** — an instruction to the generator that no building, tree, or z=4 element may be placed in a defined corridor oriented toward the water. The view must exist.
|
||||
|
||||
### Why This Can't Be Left to the Palette
|
||||
|
||||
Without an explicit reservation, the generator could place a warehouse right at the water's edge. The ocean palette would still be present beneath the warehouse floor. The player would walk through dock infrastructure, never see the water, and miss the moment entirely.
|
||||
|
||||
The reservation guarantees the VIEW. The palette guarantees the FEELING when the view arrives.
|
||||
|
||||
### The Visual Moment — Step by Step
|
||||
|
||||
As the player moves from urban interior toward a coastal edge:
|
||||
|
||||
**Distance 24+ vt from waterfront (still in district interior):**
|
||||
Normal zone palette. Warm amber or institutional grey. Standard ceiling height (z=4 overhead present).
|
||||
|
||||
**Distance 16–24 vt (transitional block begins):**
|
||||
Transitional floor palette: zone floor gradually shifts toward coastal neutral. Lighting temperature begins to cool. The sound design (Inigo's domain) changes here — urban noise begins to yield. Buildings begin to lower (S1 height tier at transitional zone).
|
||||
|
||||
**Distance 8–16 vt (coastal margin begins):**
|
||||
T6 beach palette or dock palette begins. Timber, weathered material. The overhead layer (z=4) starts to thin. Dock structures, low bollards, the hardware of the water interface. Glimpses of water visible in gaps between structures.
|
||||
|
||||
**Distance 0–8 vt (the view corridor):**
|
||||
The overhead layer *opens*. Buildings stop. The z=4 layer is empty ahead. Full ambient sky exposure (if planet-side), or the station's curved hull visible overhead (if orbital coastal equivalent exists).
|
||||
|
||||
**The moment:**
|
||||
- LOS extends to 20+ visual tiles (limited only by fog shader)
|
||||
- T5 coastal water floor tiles begin
|
||||
- The animated specular reflection layer (`#c0c8d8` at 20% opacity) starts
|
||||
- The ambient becomes cold deep blue
|
||||
- No walls or overhead in the view direction
|
||||
- A low z=2 element marks the spot: a railing, a bench, a bollard — not much, but enough to say "people come here to look"
|
||||
|
||||
**What makes it hit:** The combination of LOS extension (the player's vision suddenly triples in the water direction) and ambient inversion (warmth to cold, enclosed to open) creates a sensory discontinuity. The player has been navigating by local landmarks and wall proximity. Suddenly neither applies. The rules of navigation change.
|
||||
|
||||
**Station-interior equivalent (if applicable):** A station's "observation window" looking onto space works the same way. The overhead opens, the ambient goes deep black/star-field, and LOS extends to the window face. The landmark reservation mechanism is identical — just the palette differs.
|
||||
|
||||
---
|
||||
|
||||
## 6. What's Behind the Wall
|
||||
|
||||
### Three Cases, Three Visual Grammars
|
||||
|
||||
When a player breaches a wall, one of three spatial conditions exists on the other side. The visual grammar needs to handle all three clearly.
|
||||
|
||||
**Case 1: Adjacent Occupied Space (another room)**
|
||||
|
||||
The most common case. The breach reveals the floor and contents of the adjacent room.
|
||||
|
||||
Visual grammar:
|
||||
- Breach indicator: a **ragged wall edge** tile variant — same zone material as the wall, but with a torn/blasted face. The opening is shown as a gap in the wall tile at the breach location.
|
||||
- Debris pile: rubble objects (z=2, same material as wall) placed within 1–2 tiles of breach on both sides
|
||||
- Revealed floor: z=1 floor tiles of the adjacent space become visible through the opening (they were there all along, obscured by the wall's tile width)
|
||||
- LOS extension: the player's sightline now passes through the breach at reduced cone width (the opening is narrower than a door)
|
||||
- Objects visible: any furniture or objects in the adjacent space become visible if within the narrowed LOS cone
|
||||
|
||||
**Case 2: Interior Cavity (wall contains infrastructure)**
|
||||
|
||||
Large buildings (4+ visual tiles wide as structural dimension) have internal wall cavities. A breach into a cavity reveals the building's "nervous system."
|
||||
|
||||
This is the most visually interesting case.
|
||||
|
||||
Visual grammar:
|
||||
- The cavity is 1–2 tiles wide (enough to be visible through the breach, not enough to enter)
|
||||
- Infrastructure layer visible: **color-coded conduits and pipes** at z=1.5 (between floor and furniture layers in the z-stack):
|
||||
|
||||
| Infrastructure type | Color | Width |
|
||||
|---------------------|-------|-------|
|
||||
| Power conduit | `#c8b840` yellow-gold | 1 tile |
|
||||
| Water/coolant pipe | `#4888c8` mid blue | 1–2 tiles |
|
||||
| Data/comm line | `#b8b8b8` light grey | 0.5 tile (thin) |
|
||||
| Structural beam | `#3a3e42` dark metal | 2–3 tiles |
|
||||
| Ventilation | `#585e60` dark grey, wider | 2–4 tiles |
|
||||
|
||||
- Structural material cross-section visible: the interior face of the wall shows its core material (darker than the face material; `#181c20` dark stone or `#20242a` dark composite)
|
||||
- Era indicates infrastructure density:
|
||||
- Era 1: minimal (power only, stone structure)
|
||||
- Era 2: mixed (power + water + comm lines)
|
||||
- Era 3: full bundle (all infrastructure types, more densely bundled)
|
||||
|
||||
**Case 3: Building Perimeter Breach (exterior wall)**
|
||||
|
||||
The player has breached from inside a building to outside, or vice versa.
|
||||
|
||||
Visual grammar:
|
||||
- The outside space becomes visible through the breach (street, alley, exterior zone)
|
||||
- The exterior face of the wall was the visible face; the breach now shows the building's interior face (different material tone — slightly warmer/lighter for interior finishing versus exterior facing)
|
||||
- A **floor underwall strip**: a 1-tile-wide strip of floor tile that was hidden by the wall's footprint becomes visible. This strip often contains pushed-against objects: crates, gear, things stored against the wall. If the building has hidden objects near that wall, they're now partially revealed.
|
||||
- If something was being deliberately hidden against this wall, the breach may expose it visually before the player has crossed the threshold
|
||||
|
||||
### The Wall Infrastructure Layer — A Generator Rule
|
||||
|
||||
**Rule:** Every building with a structural footprint ≥4 vt in any dimension has a wall infrastructure layer generated at block planning time. This layer is:
|
||||
- Invisible during normal gameplay (hidden by wall tile rendering)
|
||||
- Visible when the wall tile is breached (z=1.5 reveal)
|
||||
- Consistent with the building's era tag (controls which infrastructure types are present)
|
||||
- Consistent with the zone (a residential building has water and power; a data center has redundant comm lines)
|
||||
|
||||
The infrastructure layer is pre-generated but not pre-rendered. It exists in the ChunkData but only draws when the corresponding wall tile is in a "breached" state. Generation cost is trivial (it's a pattern draw, not procedural generation).
|
||||
|
||||
### The Visual Grammar Principle: Walls Are Not Void
|
||||
|
||||
The key design principle behind this section: **walls are not empty.** They have thickness, content, and history. When a player breaches a wall, they don't find nothing. They find one of three things: another room, the building's infrastructure, or the outside. Any of those is a discovery.
|
||||
|
||||
The visual grammar should make that discovery feel earned — the ragged edge, the revealed pipes, the sudden view of the street or the next room. A breach is a spatial event. It changes what the player can see and where they can go.
|
||||
|
||||
This connects to Ozzie's "what I need is a place I'm not supposed to be." The place you're not supposed to be is not always a special room. Sometimes it's the space inside the wall. The infrastructure cavity. The under-floor route. The generator produces these spaces naturally as a consequence of how buildings are built, not as marked secrets.
|
||||
|
||||
---
|
||||
|
||||
## 7. Additional: OQ-R3-A — Can the Grid Breathe?
|
||||
|
||||
I addressed organic streets in Section 2, but I want to give Tyre a concrete visual grammar answer for what needs to change in the data structures to support grid rotation.
|
||||
|
||||
The visual grammar is already orientation-agnostic. Every rule I've written specifies distances and relationships (16 vt LOS interval, 4 vt minimum corridor width, 2-tile softening on shadows) rather than absolute directions. Rotating a grid district by 15° and applying the same rules produces a valid, coherent space.
|
||||
|
||||
What the visual grammar requires from Tyre's data structures:
|
||||
1. A `grid_orientation: f32` field on `DistrictSkeleton` (angle in degrees, 0 = standard N/S/E/W alignment)
|
||||
2. That angle propagates to block and chunk generation as a rotation parameter for template stamping
|
||||
3. The transition strip between two differently-oriented grids uses diagonal connector tile vocabulary (defined in my Round 2 Technique 1) at the rotation seam
|
||||
|
||||
There is one additional visual rule needed for multi-orientation districts:
|
||||
|
||||
**Rule:** At the boundary between two districts with different grid orientations, the transition strip must contain at least one **angular landmark** — a building or structure that reads as occupying the angular seam. This is typically a triangular or trapezoidal building footprint placed at the angular intersection. It communicates "this is where the two grids meet" through shape rather than explicit labeling.
|
||||
|
||||
The diagonal connector palette (maintenance corridor neutral `#181818`) applies to these angular landmarks regardless of zone, reinforcing their reading as "infrastructure that navigates between two spatial systems."
|
||||
|
||||
**My recommendation for Tyre:** `grid_orientation` is a simple f32 on DistrictSkeleton. Template stamping with a rotation matrix is standard geometry. This is lower implementation complexity than most of what's been proposed. Ozzie should get her grid-breathing answer in Round 3 rather than deferred.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**Palette granularity:** The modifier system (3 axes: heritage root → material character, economic tier → condition/density, era → material generation) layered on 8 base terrain types produces 40–50 strongly differentiated visual feels and 200+ meaningfully distinct combinations. Industrial farming and rustic farming differ at the base palette level (T1 vs. T2 for ambient regime) AND at the heritage/economic modifiers. The modifier system is the correct architecture — not more fixed palettes.
|
||||
|
||||
**Organic streets / grid breathing:** The visual grammar is orientation-agnostic. Organic districts require: 45° and angled wall tile variants, higher landmark density (12 vt vs. 16 vt interval), wider street width range (4–14 vt), and face-defined blocks. Grid rotation requires `grid_orientation` on DistrictSkeleton and angular landmarks at rotation seams. Ozzie's demand is satisfiable without new visual grammar rules — just additional tile variants and a data structure field.
|
||||
|
||||
**Vertical visuals:** Four height tiers (S1–S4). Primary signal is shadow length (2–40 vt), which scales with height and creates a natural gameplay shadow system. Rooftop material complexity increases with height. In station interiors, penumbra width replaces directional shadow. Neural insert perception mode displays explicit height tags.
|
||||
|
||||
**Destruction:** Corruption-based palette system — no new colors, existing materials in broken/scorched/exposed states. Five destruction stages from active fire to healed scar. The open-sky tile (bright `#c8d8f0` at 100%) is the visual flag for "roof removed." Infrastructure exposure uses standardized color codes that apply regardless of zone, making piping and conduits identifiable anywhere in the game.
|
||||
|
||||
**Horizon as landmark:** Both palette and explicit landmark reservation required. The coastal district DistrictSkeleton must include a mandatory negative-space view corridor (8+ vt unobstructed) at the waterfront. The discovery moment is produced by the combination of LOS extension, animated specular reflection, ambient inversion (warm → cold), and the absence of overhead obstruction.
|
||||
|
||||
**What's behind the wall:** Three cases — adjacent room (floor and contents visible), infrastructure cavity (color-coded pipes and conduits at z=1.5), exterior breach (outside revealed, floor underwall strip exposed). The wall infrastructure layer is generated at block planning time, invisible until breach, consistent with building era. Walls are not void — they're discoveries waiting to be opened.
|
||||
|
||||
**OQ-R3-A addendum:** `grid_orientation: f32` on DistrictSkeleton, angular landmarks at rotation seams. This is buildable. Recommend locking it in Round 3.
|
||||
|
||||
---
|
||||
|
||||
*Araminta — Round 3 complete. Standing by for convergence decisions.*
|
||||
@@ -0,0 +1,431 @@
|
||||
# Generator Architecture Workshop — Round 4: Araminta
|
||||
## Heritage Grammar Authoring, Vessel Visual Grammar, Rooftop Bar Clause, D-Record Sign-off
|
||||
|
||||
**Author:** Araminta (Visual Designer)
|
||||
**Date:** 2026-02-27
|
||||
**Workshop:** Generator Architecture (Ticket #562)
|
||||
**Responding to:** Round 3 notes (Qatux), OQ-R4-D, vessel architecture, lead directives
|
||||
**Status:** Round 4 — final convergence
|
||||
|
||||
---
|
||||
|
||||
## Opening: Four Items, All Closeable
|
||||
|
||||
This is my shortest round by design. Three of my four assignments are new specification work; the fourth is a review pass. I'm not going to repeat the grammar I established in Rounds 1–3. I'll reference it and move forward.
|
||||
|
||||
---
|
||||
|
||||
## 1. OQ-R4-D: Heritage Grammar Overlay — Authoring Workflow and Runtime Pipeline
|
||||
|
||||
### The Core Question
|
||||
|
||||
Miri asks: per-heritage modifier objects that chunk fill applies, or lookup tables within terrain palette assets?
|
||||
|
||||
**Neither exclusively. The right answer is data-driven modifier objects — structured like per-heritage objects (typed, blendable) but stored as content files (TOML/YAML), not hardcoded in the palette assets or the code.**
|
||||
|
||||
Here's why each pure option fails:
|
||||
- **Lookup tables in palette assets**: Heritage grammar gets duplicated across every terrain type that uses it. A Vine heritage modifier rule gets written once for farmland, again for wilderness, again for coastal, again for urban. Inconsistency accumulates. Updating "all Vine farms" means touching every terrain palette.
|
||||
- **Hardcoded per-heritage modifier objects**: Changing a modifier or adding a new heritage variant requires a code change and rebuild.
|
||||
|
||||
The hybrid: `HeritageModifier` structs defined in TOML/YAML content files, loaded at startup, blended at chunk fill time.
|
||||
|
||||
### The Data Structures
|
||||
|
||||
**Content file (TOML — designer-authored, one file per heritage root):**
|
||||
|
||||
```toml
|
||||
# content/heritage/vine.toml
|
||||
[heritage_modifier]
|
||||
root = "Vine"
|
||||
applicable_terrain = ["T1_farmland", "T2_industrial_farm", "T3_wilderness",
|
||||
"T4_grassland", "T6_beach", "T8_desert", "Urban", "Orbital"]
|
||||
# Modifiers below are overrides/additions to the base terrain palette defaults.
|
||||
|
||||
[floor]
|
||||
variant_preference = ["worn_path", "mossy_edge", "pressed_earth"]
|
||||
# "worn_path" is a floor tile variant in the terrain asset library
|
||||
|
||||
[objects]
|
||||
object_set = "vine_heritage_outdoor"
|
||||
# Identifies a named set in the object asset library.
|
||||
# The object set contains: trellises, planters, outdoor seating clusters,
|
||||
# seasonal decoration markers, communal fire infrastructure, door frame plants.
|
||||
arrangement = "organic_cluster"
|
||||
# "organic_cluster" is an arrangement algorithm identifier, not hardcoded.
|
||||
# Valid values: grid, organic_cluster, edge_accent, radial, scattered
|
||||
|
||||
[overhead]
|
||||
density_factor = 0.65 # 0.0-1.0 relative to terrain type's max overhead budget
|
||||
character = "personal_organic"
|
||||
# Valid values: institutional, personal_organic, personal_functional, seasonal, sparse, none
|
||||
|
||||
[gathering]
|
||||
space_probability = 0.40 # chance of a gathering space in any outdoor quarter
|
||||
|
||||
[lighting]
|
||||
temperature_adjustment_k = 800 # Kelvin added to zone baseline (positive = warmer)
|
||||
fixture_character = "personal" # personal | functional | institutional | absent
|
||||
|
||||
[boundaries]
|
||||
fence_type = "trellis_wood" # references fence asset set
|
||||
boundary_height = "low" # low | medium | high | wall
|
||||
|
||||
[social]
|
||||
# How this heritage root modifies the social texture visible in the space
|
||||
exterior_welcome_signal = true # buildings present welcoming elements toward the street
|
||||
privacy_orientation = "outward" # outward | inward | neutral
|
||||
accumulation_character = "warm_organic" # warm_organic | functional | austere | ordered
|
||||
```
|
||||
|
||||
A designer writes this file. No code changes for new heritage variants or adjustments.
|
||||
|
||||
**The full 10-root modifier table, summarized as authoring targets:**
|
||||
|
||||
| Root | Object set | Arrangement | Overhead character | Temp adj. (K) | Gathering prob. | Boundary | Exterior signal |
|
||||
|------|-----------|-------------|-------------------|--------------|----------------|----------|----------------|
|
||||
| Frost | `frost_heritage` | `grid` | `sparse` | -600 | 0.10 | `low_wire` | false |
|
||||
| Vine | `vine_heritage` | `organic_cluster` | `personal_organic` | +800 | 0.40 | `trellis_wood` | true |
|
||||
| Stone | `stone_heritage` | `edge_accent` | `personal_functional` | 0 | 0.25 | `permanent_stone` | false |
|
||||
| Tide | `tide_heritage` | `radial` | `seasonal` | +200 | 0.55 | `low_open` | true |
|
||||
| Iron | `iron_heritage` | `grid` | `institutional` | -200 | 0.30 | `shared_infra` | false |
|
||||
| Dust | `dust_heritage` | `scattered` | `functional` | 0 | 0.20 | `weatherproof` | false |
|
||||
| Spice | `spice_heritage` | `zone_divided` | `personal_organic` | +400 | 0.25 | `medium_defined` | true |
|
||||
| Salt | `salt_heritage` | `grid` | `sparse` | -100 | 0.15 | `functional` | false |
|
||||
| Arc | `arc_heritage` | `grid` | `functional` | -100 | 0.30 | `labeled_markers` | false |
|
||||
| Jade | `jade_heritage` | `edge_accent` | `personal_organic` | +100 | 0.20 | `refined_low` | true |
|
||||
|
||||
**Arrangement algorithm summary:**
|
||||
- `grid`: Evenly spaced, parallel orientations. Objects align to grid.
|
||||
- `organic_cluster`: Grouped irregular spacing, varied orientations. Objects face each other.
|
||||
- `edge_accent`: Objects placed at spatial boundaries (building edges, path margins, corners).
|
||||
- `radial`: Objects arranged relative to a central gathering point.
|
||||
- `scattered`: Low-correlation positions, minimal clustering.
|
||||
- `zone_divided`: Objects define distinct spatial sub-zones within the quarter.
|
||||
|
||||
### Runtime Pipeline — What Chunk Fill Looks Up
|
||||
|
||||
```
|
||||
Chunk fill receives:
|
||||
ChunkFillSpec {
|
||||
zone_id,
|
||||
era,
|
||||
chunk_seed,
|
||||
society_profile: SocietyProfileRef ← includes heritage blend
|
||||
}
|
||||
|
||||
Step 1: Load base terrain palette
|
||||
palette = load_base_palette(zone_id.terrain_type)
|
||||
|
||||
Step 2: Blend heritage modifiers
|
||||
For each (root, weight) in society_profile.heritage.roots:
|
||||
modifier = load_heritage_modifier(root)
|
||||
blended = blend_modifiers(modifiers_with_weights)
|
||||
// Blending rules:
|
||||
// Continuous values (temperature_adjustment_k, density_factor, gathering_probability):
|
||||
// weighted average
|
||||
// Discrete values (object_set, arrangement, fence_type):
|
||||
// weighted probabilistic selection (dominant root wins most of the time)
|
||||
// Boolean values (exterior_welcome_signal):
|
||||
// weighted probability (60% Frost / 40% Vine: 40% chance of welcome signal)
|
||||
|
||||
Step 3: Apply blended modifier to palette
|
||||
working_palette = apply_modifier(palette, blended)
|
||||
|
||||
Step 4: Fill quarter using working_palette + era + chunk_seed
|
||||
(existing fill logic — place floor tiles, objects, overhead elements, lighting)
|
||||
```
|
||||
|
||||
**Blend example: 60% Frost / 40% Vine farmland**
|
||||
- `temperature_adjustment_k`: (0.6 × -600) + (0.4 × +800) = -360 + 320 = -40K (barely cooler than baseline — the two largely cancel)
|
||||
- `density_factor`: (0.6 × sparse_default) + (0.4 × 0.65) = moderate overhead
|
||||
- `arrangement`: Frost wins probabilistically (60%). Grid arrangement, but some organic cluster elements from Vine's 40% share appear in the overhead layer.
|
||||
- `exterior_welcome_signal`: 40% chance — the farm presents a slightly inviting exterior, but the Frost efficiency still dominates the floor plan.
|
||||
|
||||
**The output is a farm that's functional and ordered (Frost dominant) but with a few organic touches — a trellis here, an informal seating corner there — that tell you Vine heritage is also present.**
|
||||
|
||||
### What an Artist/Designer Authors
|
||||
|
||||
Three asset types feed the heritage modifier system:
|
||||
|
||||
**1. Object sets** (artist-authored, tagged by heritage root)
|
||||
An object set is a named collection of placeable objects in the asset library. The artist creates objects appropriate to each heritage root's aesthetic and tags them. Adding new Vine heritage objects to the base game means adding them to the `vine_heritage_outdoor` object set — no modifier file change needed.
|
||||
|
||||
**2. Heritage modifier files** (designer-authored TOML, one per root)
|
||||
The ten files described above. A designer can adjust behavior by editing these files. No code changes. If a heritage modifier is added (future expansion), one new TOML file is all that's needed.
|
||||
|
||||
**3. Terrain palette base files** (artist-authored, one per terrain type)
|
||||
The base terrain palette with default object density, lighting, and floor tiles. The heritage modifier overrides these defaults — anything not overridden uses the terrain palette default. This means a terrain type only needs to specify its own defaults; heritage grammar is applied on top.
|
||||
|
||||
### The One Design Rule That Must Be Stated Explicitly
|
||||
|
||||
**Rule:** The heritage modifier is applied at chunk fill time (Phase 2), not at district skeleton time (Phase 1). The DistrictSkeleton carries `society_profile: SocietyProfileRef`. Phase 2 chunk fill reads the heritage blend from the profile and applies the modifier. This means the heritage grammar is visible in the tiles but never stored as a structural generator decision — it's a visual expression, not a spatial constraint.
|
||||
|
||||
**Exception:** `gathering_probability` from the heritage modifier can influence Phase 1 quarter pre-assignment (whether a quarter is pre-assigned as "outdoor gathering space"). If so, the heritage modifier must be partially evaluated at block planning time for that specific parameter. All other modifier parameters apply at Phase 2 only.
|
||||
|
||||
---
|
||||
|
||||
## 2. Vessel Visual Grammar
|
||||
|
||||
### The Answer: Modifiers, Not New Base Palettes
|
||||
|
||||
Vessels use existing zone palettes. What distinguishes a vessel visually is not a different material vocabulary — it's a different **spatial grammar**. The material inside a luxury cabin is the same amber-warm bar palette. The material inside a cargo hold is the same maintenance-grey. What's different is the envelope, the proportion, and the bounded-environment signals.
|
||||
|
||||
**Rule: vessels are existing-palette spaces with five additional visual grammar rules.**
|
||||
|
||||
### The Five Vessel Visual Grammar Rules
|
||||
|
||||
**Rule V-1: Exterior hull is vessel-identity material, not zone palette.**
|
||||
|
||||
The outer hull/skin of any vessel uses a specific material that identifies the vessel as a vessel:
|
||||
- Spacecraft: `#2a2e32` (dark cold metal, Era-appropriate surface texture — Era 1 = riveted plate, Era 2 = welded panels, Era 3 = smooth composite)
|
||||
- Trains: livery color applied to the exterior — muted version of the operating company's identity color, within saturation rules
|
||||
- Ships/boats: `#2a2820` (weathered dark hull, salt-worn)
|
||||
|
||||
The exterior hull material applies only to the outermost layer visible from outside. Interior spaces use zone palettes normally.
|
||||
|
||||
**Rule V-2: Window tiles provide exterior context.**
|
||||
|
||||
Where a vessel has windows (train windows, porthole, cockpit view), the window tile is a special element:
|
||||
- On the floor layer, the window gap shows a z=0 background buffer — a scrolling or static exterior visual
|
||||
- In transit: the exterior buffer shows appropriate passing environment (stars, landscape, water)
|
||||
- Docked: the exterior buffer shows the dock environment (warehouse wall, station hull)
|
||||
- The window frame is vessel-hull material, the opening is the contextual reveal
|
||||
|
||||
**Rule V-3: Vessel spaces use a compression modifier.**
|
||||
|
||||
The same zone palette, but proportionally tighter:
|
||||
- Overhead height budget reduced by 30% (same z=4 elements, but lower effective ceiling)
|
||||
- Object density increased within the same floor area (the space is used intensively)
|
||||
- Corridor minimums still apply (V-05 rules), but vessel corridors bias toward minimum width
|
||||
|
||||
This compression modifier communicates "bounded mobile environment" without requiring new palettes.
|
||||
|
||||
**Rule V-4: Section transitions use vessel-identity threshold elements.**
|
||||
|
||||
Where one vessel section (train car, ship compartment) connects to another, the threshold is:
|
||||
- Door frame in vessel-hull material (not zone palette)
|
||||
- A visible width reduction at the threshold (the door opening is narrower than the corridor)
|
||||
- The threshold material is constant across the vessel — it marks every internal boundary
|
||||
|
||||
This makes internal navigation feel like moving through a vessel, not a building.
|
||||
|
||||
**Rule V-5: Class stratification is expressed through proportion, not palette.**
|
||||
|
||||
In a passenger vessel with multiple service classes:
|
||||
- First class: higher overhead clearance (+20% overhead budget), wider aisles (+2 vt), warmer lighting temperature (+400K)
|
||||
- Standard class: base compression modifier
|
||||
- Economy/crew: maximum compression (-10% further from standard), colder lighting (-200K)
|
||||
|
||||
Same palette. Different proportions. A player who knows the grammar can read service class instantly from spatial feel.
|
||||
|
||||
### Vessel-Specific Surface Cases
|
||||
|
||||
**Train cars (BoundedLinear):** Each car is a short, rectangular interior space. The key visual grammar element is the **visible car sequence** — the window at each car end shows the next car, creating a visual depth cue (you can see through multiple cars from the right position). This is implemented as a window tile facing the coupling direction showing the next car's interior.
|
||||
|
||||
**Spaceship corridors (InterSystem):** No exterior context visible during transit — black void or star-field through windows. Artificial lighting dominant (no ambient from outside). Institutional palette for working spaces; residential palette for crew quarters. The compression modifier is strongest here — corridors are minimal-width, spaces are used entirely.
|
||||
|
||||
**Ship cabins (BoundedMaritime):** Port-side windows show harbor environment when docked; ocean/sky when at sea. The marine weathering texture (visible on hull material) is the primary "you're on a ship" signal. The rocking motion (client-side animation) is Inigo's domain, not mine.
|
||||
|
||||
---
|
||||
|
||||
## 3. Rooftop Bar Clause — Public vs. Restricted Rooftop Visual Grammar
|
||||
|
||||
### The Tension and Its Resolution
|
||||
|
||||
Gestalt's guarantee: every tall structure (z_band_count ≥ 3) must have a roof zone classified `Insider` or `BreachOnly` accessible by non-obvious route.
|
||||
|
||||
The lead wants rooftop bars: public social destinations on tall buildings.
|
||||
|
||||
**These are not in conflict if we amend the guarantee correctly.**
|
||||
|
||||
**Amended guarantee:** Every tall structure must have a roof zone that constitutes a *discovery* — something worth reaching the top for. That discovery can be:
|
||||
- **A public destination** (rooftop bar, garden, observation deck) — accessed via obvious route, classified `Public` or `Semi-Public`
|
||||
- **A restricted discovery** (operational rooftop, private access) — accessed via non-obvious route, classified `Insider` or `BreachOnly`
|
||||
|
||||
Both satisfy the intent: the top of the building is not just mechanical infrastructure. The access tier varies; the discovery does not.
|
||||
|
||||
**Structural rule:** A building can have both. A skyscraper with a public rooftop bar at z_band top AND a restricted antenna/comms level above it satisfies both playstyle needs. The rooftop bar is the destination for social/tycoon/investigation players. The above-the-bar maintenance level is the discovery for the breach/assassin player.
|
||||
|
||||
### Rooftop Bar Visual Grammar (S3 and S4 Height Tiers)
|
||||
|
||||
A rooftop bar at S3 (11-30 floors) or S4 (30+) is visible from above and must communicate "social destination, open to arrival."
|
||||
|
||||
**Floor material:** Warm pavers or composite decking — lighter and warmer than the industrial-default rooftop. Not the zone palette floor — this is a curated outdoor surface. Hex: `#2a2018` (warm dark slate/composite), visually distinct from the `#181c22` cold building roof tiles.
|
||||
|
||||
**Perimeter barrier (z=2):** A designed railing or low wall around the outdoor area — thin (1-tile), warm material (matching the bar zone palette's furniture material). The barrier communicates "this is a social edge, not a fall hazard." Visually distinguishable from the functional parapet of an institutional rooftop by material and continuity (it's a designed feature, not a structural necessity).
|
||||
|
||||
**Seating clusters (z=2):** Small furniture objects — tables and chairs at human scale. At S3/S4 heights, these are small enough that from the ground-level view they're just warm texture. From the adjacent building observation deck or elevator approach, they're readable as social furniture. High-value visual cue: the seat arrangement creates clusters (2-4 chairs around a table), not rows.
|
||||
|
||||
**Service structure (z=2):** A bar counter, or a small pavilion structure housing the service area. This is the anchor of the rooftop social space — the visible sign that this was designed to serve people, not just stand on.
|
||||
|
||||
**Lighting signature (z=4):** Warm pendant fixtures or string lights — the most visually distinctive element. Temperature `#f0b840` (amber, the bar zone palette) with 80% radius of a ground-floor bar fixture. From a nearby elevated position (adjacent building, observation floor), this warm lighting cluster at height is readable as "social gathering above."
|
||||
|
||||
**Access indicator:** A visible stairhouse or elevator shaft entrance at z=2/4 — not hidden, not service-hatch scale. The access is designed as part of the social space, not an afterthought.
|
||||
|
||||
### Restricted Rooftop Visual Grammar (Contrast)
|
||||
|
||||
The restricted rooftop must read as *not-welcome* at a glance:
|
||||
|
||||
**Floor material:** Zone-standard cold industrial roof tile (`#181c22` blue-grey). No warm deviation.
|
||||
|
||||
**Perimeter barrier (z=2):** Functional parapet — same material as building structure, no warmth, no design intent beyond preventing falls. Or absent (exposed edge with safety mesh).
|
||||
|
||||
**z=2 objects:** Mechanical equipment (HVAC clusters, antenna bases, sensor arrays, junction boxes). These objects face away from a human observer — they're not oriented to be seen, they're oriented to function.
|
||||
|
||||
**Lighting (z=4):** Cold work lights only, if present — or absent. Temperature `#c0d0e0` (cold), directed down. No ambient warmth.
|
||||
|
||||
**Access indicator:** Service hatch, maintenance door — small, flush with the floor, visually minimized. Or a locked stairway access door marked with institutional signage.
|
||||
|
||||
### The Visual Grammar Test
|
||||
|
||||
A player approaching a tall building from the street should be able to read, from the roof's visible lighting signature at night:
|
||||
- **Warm amber cluster, designed railing visible** = rooftop bar / social destination
|
||||
- **Cold/dark, mechanical silhouette** = restricted / breach route
|
||||
|
||||
At S4 heights, the warm glow of a rooftop bar is visible from 12+ tiles away as a warm point-light cluster elevated against the dark building mass. That visual signal is navigation — a player who wants a social destination looks for the warm light at height.
|
||||
|
||||
---
|
||||
|
||||
## 4. Final Visual Sign-off — 12 D-Ready Items
|
||||
|
||||
Reading through each item and flagging wording, additions, or changes needed before the D-record is written.
|
||||
|
||||
---
|
||||
|
||||
**D-READY-1: DistrictLayoutMode — Grid and Organic Support**
|
||||
|
||||
✓ Correctly captured. Visual grammar confirmed: 45° wall variants, 12 vt landmark interval, 4–14 vt street width range, face-defined blocks.
|
||||
|
||||
**Wording flag:** The D-record must state the 45° rotation cap as a **hard technical constraint**, not a design preference. Language: "Maximum block rotation is ±45° from district grid orientation. This limit is non-negotiable — beyond 45°, tile-based pathfinding produces unacceptable movement artifacts. Organic districts produce the visual impression of curved streets through angular jogs, not smooth curves."
|
||||
|
||||
---
|
||||
|
||||
**D-READY-2: Guarantee Tier System — Universal / Full-Only / Conditional**
|
||||
|
||||
✓ Visual grammar correctly captured. The horizon view corridor is confirmed as a Tier 2 guarantee for coastal districts.
|
||||
|
||||
**Wording addition needed:** Add the rooftop destination clause (Section 3 above) as a Tier 2 guarantee: "Every tall structure (z_band_count ≥ 3) must contain a roof zone that constitutes a discovery — either a public destination (Public/Semi-Public access tier) or a restricted discovery (Insider/BreachOnly access tier, accessible by non-obvious route). Both types are valid; neither is mandatory over the other."
|
||||
|
||||
---
|
||||
|
||||
**D-READY-3: TrianglePurpose Enum**
|
||||
|
||||
No visual grammar implications. ✓ No changes.
|
||||
|
||||
---
|
||||
|
||||
**D-READY-4: WallBackside / TileBehindState**
|
||||
|
||||
✓ Three visual cases confirmed: adjacent room, infrastructure cavity, perimeter breach.
|
||||
|
||||
**Wording addition:** "Infrastructure cavity contents are Era-tagged: Era 1 = power conduit only; Era 2 = power + water/coolant + comm lines; Era 3 = full bundle (all types, more densely bundled). The infrastructure color codes are standardized across all zones — power `#c8b840`, water/coolant `#4888c8`, comm/data `#b8b8b8`, structural beam `#3a3e42`. These colors apply regardless of zone palette."
|
||||
|
||||
---
|
||||
|
||||
**D-READY-5: Dynamic Modification via Overlay**
|
||||
|
||||
✓ Five-stage destruction visual correctly captured.
|
||||
|
||||
**Addition needed — trauma event → visual stage mapping:**
|
||||
|
||||
The D-record should include the mapping from `ModificationType::TraumaEvent` subtypes to starting destruction stage:
|
||||
|
||||
| Trauma event subtype | Starting visual stage | Scope |
|
||||
|----------------------|-----------------------|-------|
|
||||
| `PhysicalDestruction` | Stage 2 (Fresh Aftermath), decaying to Stage 3 over game-time | Area or district |
|
||||
| `ViolenceEvent` | Stage 2 (limited area — 1–4 chunks), stabilizes to Stage 3 quickly | Local |
|
||||
| `EconomicDisruption` | No destruction stage — Economic Stress quarter modifier applied instead | District-wide |
|
||||
| `PoliticalShock` | No destruction stage — Faction presence modifier (overlay) | District-wide |
|
||||
| `MigrationShock` | No destruction stage — Settlement vs. Economic Stress balance shifts | District-wide |
|
||||
|
||||
**The rule:** Destruction stages apply only to events that physically alter structures. Economic/political/migration trauma is expressed through the quarter fill modifier system (D-READY-6), not through destruction stages.
|
||||
|
||||
---
|
||||
|
||||
**D-READY-6: ZonePalette Modifier System**
|
||||
|
||||
✓ 8 base terrain types, 3 modifier axes confirmed.
|
||||
|
||||
**Wording addition needed:** Explicitly name T1 and T2 as the two farmland base palettes addressing the industrial/rustic distinction:
|
||||
|
||||
"T1 (Temperate Farmland): warm organic ground ambient, natural lighting regime. T2 (Industrial/Greenhouse Farmland): cool grey-green ambient, artificial lighting regime. The ambient regime difference (natural vs. artificial) is the primary visual distinction between rustic and industrial farming at the base palette level. Heritage root modifiers further differentiate them at the grammar level."
|
||||
|
||||
---
|
||||
|
||||
**D-READY-7: Horizon View Corridor as Coastal Guarantee**
|
||||
|
||||
✓ Fully confirmed. ≥8 vt unobstructed view corridor. Mandatory negative space reservation.
|
||||
|
||||
**Wording clarification:** The view corridor is **negative space** — an instruction to not place blocking structures, not a placed object. "The generator reserves a view corridor of minimum 8 visual tiles from the nearest public street to the water's edge. No building, tree, or z=4 element may occupy this corridor. A low z=2 element (railing, bench, bollard) marks the waterfront point as a designed viewing location."
|
||||
|
||||
---
|
||||
|
||||
**D-READY-8: Assassin Lens Spatial Guarantees (A-1 through A-4)**
|
||||
|
||||
✓ Correctly captured. The spatial guarantees don't require new visual elements — existing grammar serves the assassin.
|
||||
|
||||
**Wording note:** Guarantee A-1 (Elevated Vantage) requires clarification about what "clear LOS cone" means visually. Add: "An elevated vantage position must have a direct sightline — no z=4 overhead elements within the LOS cone between the vantage point and the Traffic Chokepoint. The generator ensures this by tagging the LOS corridor as an overhead-clear zone during block planning."
|
||||
|
||||
---
|
||||
|
||||
**D-READY-9: Heritage Grammar Overlay for Non-Urban Palettes**
|
||||
|
||||
**Status: Not yet lockable — this round provides the authoring workflow that was missing.**
|
||||
|
||||
See Section 1 above for the full specification. The D-record needs:
|
||||
1. The `HeritageModifier` struct definition (from Section 1)
|
||||
2. The 10-root modifier table (from Section 1)
|
||||
3. The authoring workflow description (from Section 1)
|
||||
4. The rule: heritage modifier applied at Phase 2 chunk fill, with one exception (gathering_probability for Phase 1 pre-assignment)
|
||||
|
||||
**With Section 1 above, this item is now lockable.**
|
||||
|
||||
---
|
||||
|
||||
**D-READY-10: Non-Urban Informal Zone Typology**
|
||||
|
||||
✓ Correctly captured. Miri's three types resolved.
|
||||
|
||||
**Addition — visual grammar for each informal zone type:**
|
||||
|
||||
The D-record should include how each type reads visually:
|
||||
|
||||
| Informal zone type | Visual signature |
|
||||
|-------------------|-----------------|
|
||||
| `social_permission` | Normal zone palette, but gathering infrastructure present (covered space, seating). The space looks designed for use, not abandoned. The signal is the gathering element, not absence of official markers. |
|
||||
| `physical_distance` | Sparse objects, low overhead. Floor tile: terrain-appropriate but less maintained (no worn path markers — the path is made by walking, not cleared). The isolation IS the visual — this space has no design attention. |
|
||||
| `utilitarian_cover` | Functional work objects. Normal zone palette. The informal zone reads as work space — the cover story is the visual appearance. There is no obvious sign that this space serves unofficial purposes; the player must infer from context. |
|
||||
|
||||
---
|
||||
|
||||
**D-READY-11: Vertical Scale Architecture**
|
||||
|
||||
✓ Height tier system (S1–S4), shadow lengths, rooftop vocabulary all confirmed.
|
||||
|
||||
**Addition for the D-record:** The Rooftop Bar Clause from Section 3 above should be incorporated as a sub-rule of the vertical scale architecture decision. Specifically: "The roof zone at tier S3+ must be assigned a social character during block planning: `PublicDestination | RestrictedDiscovery`. This character determines the visual grammar applied at chunk fill time."
|
||||
|
||||
---
|
||||
|
||||
**D-READY-12: Trauma Events as EraModification Subtypes**
|
||||
|
||||
✓ Correctly captured from Miri's work.
|
||||
|
||||
**Addition — visual stage mapping already provided in D-READY-5 wording above.** The two D-records cross-reference.
|
||||
|
||||
**One wording clarification:** The "active modification state" that decays toward baseline — this decay is visual. As time passes (in-game hours/days), the destruction stage advances from Stage 2 → Stage 3 → Stage 4 → Stage 5. The decay rate is heritage-root-dependent (Frost: faster visible recovery; Tide: faster social recovery; Arc: lingers institutionally). This decay schedule should be in the D-record as a visual parameter: `trauma_visual_decay_rate: slow | medium | fast` per heritage root, defaulting to `medium`.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**OQ-R4-D (Heritage Grammar Overlay):** Resolved. Authoring workflow: ten TOML modifier files (one per heritage root), each specifying object sets, arrangement algorithm, overhead character, lighting temperature adjustment, and gathering probability. Runtime: blend modifiers at chunk fill time by heritage weight (continuous values: weighted average; discrete values: weighted probabilistic selection). Phase 1 exception: gathering_probability evaluated at block planning for quarter pre-assignment. Item D-READY-9 is now lockable.
|
||||
|
||||
**Vessel Visual Grammar:** Vessels use existing zone palettes with five additional rules: (1) exterior hull is vessel-identity material; (2) window tiles reveal exterior context; (3) compression modifier tightens proportions; (4) section transitions use vessel-identity threshold elements; (5) service class is expressed through proportion, not palette change. No new base palettes required.
|
||||
|
||||
**Rooftop Bar Clause:** Gestalt's guarantee amended — "discovery zone at the top" replaces "Insider/BreachOnly" as the mandatory requirement. Public rooftop destinations (rooftop bar, garden) satisfy the guarantee equally with restricted ones. Visual distinction: warm amber lighting + social furniture (public) vs. cold mechanical silhouette (restricted). Both readable at a glance from adjacent elevation.
|
||||
|
||||
**D-record sign-off:** All 12 items reviewed. Five wording additions/clarifications flagged. D-READY-9 was the only item not previously lockable — it is now, with Section 1 providing the missing authoring workflow. The trauma → visual stage mapping (D-READY-5/12) is new and must be included in the D-records.
|
||||
|
||||
---
|
||||
|
||||
*Araminta — Round 4 complete. All four assignments closed. Standing by for D-record production.*
|
||||
@@ -0,0 +1,116 @@
|
||||
# Generator Architecture Workshop — Round 5 (Final Review): Araminta
|
||||
|
||||
**Role:** Visual Designer
|
||||
**Date:** 2026-02-27
|
||||
**Workshop:** Generator Architecture (#562)
|
||||
|
||||
---
|
||||
|
||||
## Sign-Off
|
||||
|
||||
The outcomes document accurately captures the visual design domain. All five wording additions and clarifications I flagged in Round 4 are incorporated correctly:
|
||||
|
||||
- **D-READY-1**: 45° cap stated as "non-negotiable" hard technical constraint — correct wording, correct framing.
|
||||
- **D-READY-2**: Rooftop Discovery Zone listed as Tier 2 Full-complexity guarantee — correct.
|
||||
- **D-READY-4**: Era-tagged infrastructure color codes present (`#c8b840` / `#4888c8` / `#b8b8b8`) — correct.
|
||||
- **D-READY-5**: Trauma event → visual stage mapping present. Physical events → Stage 2, economic/political/migration → quarter fill modifier. Correct.
|
||||
- **D-READY-6**: T1 (warm organic, natural lighting) / T2 (cool grey-green, artificial lighting) explicitly distinct — correct.
|
||||
- **D-READY-7**: "Negative-space reservation" framing — correct.
|
||||
- **D-READY-9**: Data-driven TOML modifier files (10 per heritage root), Phase 2 chunk fill blend, Phase 1 gathering_probability exception, authoring domain separation (Miri = spatial grammar, Araminta = visual expression + TOML files) — all correct.
|
||||
- **D-READY-10**: References `araminta-round4.md` for visual grammar per informal zone type — acceptable approach, avoids duplication.
|
||||
- **D-READY-11**: Rooftop Bar Clause present. Discovery layer mandatory in both configs. Correct.
|
||||
- **D-READY-12**: `trauma_visual_decay_rate: slow | medium | fast` per heritage root, default medium — correct.
|
||||
- **Q-NNN-e**: ObjectTag co-maintenance flagged as open question — correct.
|
||||
|
||||
I also confirm Ozzie's correction on D-READY-11: "Heritage root determines which config is assigned" is wrong and I should have been more precise in my R4 language. Heritage root should *weight the probability*, not *determine the outcome*. Ozzie is right — full determination kills the discovery moment. A Frost building with a rooftop bar is memorable precisely because it is unexpected. The correction stands.
|
||||
|
||||
---
|
||||
|
||||
## Corrections — Three Items
|
||||
|
||||
### Correction 1 (Critical): T5/T7 Terrain Palette Numbering Wrong
|
||||
|
||||
D-READY-6 lists the 8 base terrain types as: T1 temperate farmland / T2 industrial farmland / T3 wilderness / T4 grassland / **T5 mountain** / T6 beach / **T7 wetland** / T8 desert.
|
||||
|
||||
My Round 3 specification was:
|
||||
|
||||
| ID | Name |
|
||||
|----|------|
|
||||
| T5 | Coastal water |
|
||||
| T6 | Beach/coastal margin |
|
||||
| T7 | Mountain/high terrain |
|
||||
| T8 | Desert/arid |
|
||||
|
||||
The outcomes document has T5 and T7 transposed, and **"wetland" is not in my original 8 types at all** — it replaced mountain. This is a factual error.
|
||||
|
||||
Correct T5 = **Coastal water** (deep near-black blue, animated specular reflection, the terrain type referenced by D-READY-7's horizon view corridor guarantee). Correct T6 = **Beach/coastal margin** (warm dark tan). Correct T7 = **Mountain/high terrain** (dark blue-grey stone, snow at elevation). Wetland was never specified — if it needs to be added, it requires design work as a 9th type, not a silent replacement.
|
||||
|
||||
**This error matters because:** T5 (Coastal water) is the terrain type that triggers the D-READY-7 horizon view corridor guarantee. If T5 is mountain, the coastal guarantee has no palette to reference.
|
||||
|
||||
**Required fix in D-READY-6:** Correct the T5/T6/T7/T8 labels to match my Round 3 specification. Remove "wetland." If wetland terrain is needed for the game, file it as a new type with a new T-number.
|
||||
|
||||
---
|
||||
|
||||
### Correction 2: D-READY-9 — Araminta's Authoring Domain Listed Incomplete
|
||||
|
||||
D-READY-9 describes my authoring domain as: *"object sets, arrangement algorithms, lighting temperature (TOML modifier files, one per heritage root)".*
|
||||
|
||||
My Round 4 TOML schema included additional sections not captured here. The full domain covers:
|
||||
|
||||
- `[floor].variant_preference` — floor surface texture (worn_path, pressed_earth, etc.) — part of visual expression, not organizational grammar
|
||||
- `[overhead].density_factor` — flora/canopy density, a continuous visual parameter
|
||||
- `[overhead].character` — overhead object character (personal_organic, industrial_grid, etc.)
|
||||
- `[structure].primary_material` / `material_tone_shift` — wall material character and color temperature shift
|
||||
- `[boundaries].fence_type` — boundary/fence material (trellis_wood, stone_wall, wire_mesh, etc.)
|
||||
|
||||
Wall material character and boundary material are part of my domain — not Miri's. An implementer reading D-READY-9 would assign structural material selection to Miri (organizational principles) when these visual expression fields belong with me.
|
||||
|
||||
**Required fix in D-READY-9:** Update Araminta's domain to: *"object sets, arrangement algorithms, floor surface variants, overhead flora density and character, wall/structure material character, boundary material type, lighting temperature (TOML modifier files, one per heritage root)."*
|
||||
|
||||
---
|
||||
|
||||
### Correction 3: D-READY-13 — Vessel Visual Grammar Not Referenced
|
||||
|
||||
The MobileChunk section correctly captures structural fields, movement states, cultural grammar (via `TransitSocialModifier`), and departure schedules. It references Miri's canonical spec for cultural grammar.
|
||||
|
||||
It does not reference the vessel visual grammar I specified in Round 4 — five rules that apply to all MobileChunk-type spaces:
|
||||
|
||||
1. Exterior hull uses vessel-identity material (not zone palette)
|
||||
2. Window tiles reveal exterior context (docked vs. in transit)
|
||||
3. Compression modifier tightens proportions throughout
|
||||
4. Section transitions use vessel-identity threshold elements
|
||||
5. Class stratification expressed through proportion, not palette change
|
||||
|
||||
An implementer reading D-READY-13 in isolation has no source for how vessels look different from buildings. The compression modifier and hull-identity threshold material are required for correct chunk fill.
|
||||
|
||||
**Required fix:** Add to D-READY-13: *"Vessel visual grammar: see `docs/workshops/generator-architecture/araminta-round4.md` §2. Five rules govern visual distinction of MobileChunk interiors from static zone spaces."*
|
||||
|
||||
---
|
||||
|
||||
### Correction 4: D-READY-5 — Destruction Stage Sequence Not Enumerated; Destruction Palette Absent
|
||||
|
||||
D-READY-5 references "Stage 2 (Fresh Aftermath)" and "Stage 3" in the trauma event mapping but never enumerates the full stage sequence. The destruction palette constraint is also absent.
|
||||
|
||||
**Required addition — stage sequence:**
|
||||
|
||||
| Stage | Name | Visual state |
|
||||
|-------|------|-------------|
|
||||
| 1 | Active | Event in progress; DamageOverlay rendering live |
|
||||
| 2 | Fresh Aftermath | Structure breached; scorch, rubble, debris tiles visible |
|
||||
| 3 | Stabilized | Debris cleared; structural state permanent |
|
||||
| 4 | Reconstruction | Scaffolding tiles, incomplete floor sections |
|
||||
| 5 | Healed Scar | Functional again; residual visual tells remain |
|
||||
|
||||
**Required addition — destruction palette constraint:**
|
||||
|
||||
> Destruction palette is **corruption-only**: no new colors are introduced by destruction. Existing zone palette tiles are darkened, desaturated, or replaced with structural-damage variants drawn from the same palette family. Single exception: `#c8d8f0` (open-sky tile) appears at 100% intensity when a roofed structure has its roof removed — the only color that destruction introduces. Implementers must not create a separate destruction color set.
|
||||
|
||||
This constraint is needed in the D-record to prevent implementers from adding freestanding destruction palette colors. Without it, different implementations will diverge on whether destruction has its own visual language or borrows from zone palettes.
|
||||
|
||||
---
|
||||
|
||||
## Sign-Off (Updated)
|
||||
|
||||
With the four corrections above applied, the document is accurate for the visual design domain. The 14 D-records are ready to file.
|
||||
|
||||
*Araminta — Round 5 complete.*
|
||||
@@ -0,0 +1,261 @@
|
||||
# Generator Architecture Workshop — Round 1: Gestalt
|
||||
|
||||
**Role:** Systems Design / Gameplay Loop Requirements
|
||||
**Date:** 2026-02-27
|
||||
**Workshop:** Generator Architecture (#562)
|
||||
|
||||
---
|
||||
|
||||
## Framing Statement
|
||||
|
||||
The generator must produce spaces that create **information asymmetry** — not just visually plausible locations, but locations where what you know, who you know, and where you can go produce fundamentally different gameplay experiences. Every spatial guarantee below is ultimately a guarantee about asymmetric information production.
|
||||
|
||||
Let me break down what the generator actually needs to produce, mechanically.
|
||||
|
||||
---
|
||||
|
||||
## 1. What Gameplay Loops Does the Generator Need to Support?
|
||||
|
||||
The game has five confirmed gameplay loops (D-007 five pillars). The generator must support all five with spatial affordances.
|
||||
|
||||
### Loop 1: Observation Loop
|
||||
**Mechanic:** Player moves through space, maintains Careful/Walk stances (D-053), observes NPC tells, detects anomalies, fires monologue recognition events.
|
||||
**Generator requirement:** Every district needs spaces where NPCs are observable from cover — chokepoints, corridors, elevated positions, furniture arrangements that create natural sightline asymmetry. The functional cluster (D-025: 15-40 tile connected space with internal sightlines) is the atomic observable unit. The generator must instantiate these; it cannot produce districts where all spaces are either completely open or completely enclosed.
|
||||
|
||||
### Loop 2: Investigation Loop
|
||||
**Mechanic:** observe → notice → follow → discover. Three confirmed path types from D-093 (Path A: pattern/digital, Path B: physical/spatial, Path C: institutional/social). Each path yields unique evidence inaccessible to the other paths.
|
||||
**Generator requirement:** Every generated district must support all three path types. This means the spatial hierarchy must guarantee: (A) a zone with Meridian/camera log access (Path A), (B) a physical traversal corridor with acoustic gaps or spatial quirks (Path B), and (C) a social venue where institutional relationships develop (Path C). These are spatial guarantees, not content guarantees — the generator places the slots; templates fill them.
|
||||
|
||||
### Loop 3: Social Manipulation Loop
|
||||
**Mechanic:** Player builds rapport, climbs trust tiers (D-075), unlocks dialogue options (D-062 — invisible until unlocked), triggers unprompted disclosures. Social sites (D-025: 4-8 NPCs within 15-40 tile cluster) are the primary stage.
|
||||
**Generator requirement:** Every district needs at least one social venue (bar-type social site) that is publicly accessible without authority credentials, AND at least one workplace social site where insider access is required. These are not the same space. The generator must distinguish them at the zoning/amenity stage.
|
||||
|
||||
### Loop 4: Stealth/Exposure Loop
|
||||
**Mechanic:** Player manages their own information exposure — who saw them, where they went, what was recorded. Meridian dead spots (D-093: maintenance corridors, z=0 layer) are critical for both archetypes.
|
||||
**Generator requirement:** Every district must have at least one zone with degraded Meridian coverage where private exchanges can occur. The smuggler needs it for ring operations. The detective needs it for confidential source contact. Without this guarantee, both archetypes lose a core tool.
|
||||
|
||||
### Loop 5: Daily Life / Routine Loop (Substrate)
|
||||
**Mechanic:** Player witnesses NPC routines, builds complacency, creates emotional attachment that makes contamination hit harder (D-023: "daily life is the substrate; conspiracies are weather"). D-031 day phases drive NPC routine transitions.
|
||||
**Generator requirement:** The district must have transit nodes (encounter nodes where NPC routing creates observable patterns), temporal rhythm affordances (NPC populations change by day phase), and density variation (not all spaces always populated). The transit platform from D-093 is the canonical example — workers arrive bar-side at shift change, the pattern is observable.
|
||||
|
||||
---
|
||||
|
||||
## 2. What Must the Generator Guarantee?
|
||||
|
||||
These are non-negotiable spatial guarantees. If a generated district fails any of these, it cannot support the core gameplay loops.
|
||||
|
||||
### Guarantee 1: Surveillance Chokepoint
|
||||
**Definition:** A spatial bottleneck where NPC traffic must pass and is observable from a fixed position by a player in Walk or Careful stance.
|
||||
**Why mandatory:** The observation loop (Loop 1) requires this. Without at least one chokepoint, the player has no reliable observation position. Evidence gathered here feeds Path A and Path B investigation.
|
||||
**Example from D-093:** Gate concourse (40×8 sim tiles, public zone) + transit platform (bar-side encounter node, ~12×8 visual). These are chokepoints by geometry — narrow corridors NPCs must traverse.
|
||||
**Generator mechanism:** Infrastructure stage must place at least one high-traffic corridor connecting the transit ingress to the district's social sites. Block generation must not route NPCs around it.
|
||||
|
||||
### Guarantee 2: Quiet Zone (Meridian Dead Spot)
|
||||
**Definition:** A zone with low or absent Meridian coverage and low ambient NPC traffic, suitable for private exchanges.
|
||||
**Why mandatory:** The stealth/exposure loop (Loop 4) requires this for both archetypes. The ring (and its analog in any generated district) needs operational dead spots. Without this, the 30/50/20 entanglement model (D-029) has nowhere to put the entangled 20%.
|
||||
**Example from D-093:** Maintenance corridors at z=0 (minimal Meridian, no general NPC traffic). Restricted storage corridor (acoustic gap, accessible only via specific physical path).
|
||||
**Generator mechanism:** Infrastructure stage assigns Meridian coverage per zone. Zoning stage marks these zones. At least one zone per district must be flagged `meridian_coverage: degraded` or `minimal`.
|
||||
|
||||
### Guarantee 3: Social Manipulation Hub (Public Social Site)
|
||||
**Definition:** A social venue with D-025 cluster properties (4-8 NPCs, 15-40 tile connected space, internal sightlines) that is publicly accessible — no access credentials required.
|
||||
**Why mandatory:** Social manipulation loop (Loop 3) requires a space where both archetypes can build relationships. Bar-type social sites are the canonical form. Without this, trust tier progression has no natural stage.
|
||||
**Example from D-093:** The Last Shift / "Lera's" (bar). Public access, established regulars, D-025-compliant cluster.
|
||||
**Generator mechanism:** Amenities stage must instantiate at least one bar/social venue type. Its chunk fill must satisfy D-025 sightline requirements — the quarter system cannot fragment this into isolated enclosed rooms.
|
||||
|
||||
### Guarantee 4: Insider Access Zone (Workplace Social Site)
|
||||
**Definition:** A functional cluster where employment/membership (not authority) grants access that the detective archetype cannot obtain through institutional credentials.
|
||||
**Why mandatory:** Asymmetric access between archetypes (D-007 pillar 1) requires at least one zone where the smuggler's insider knowledge is an advantage. This is what makes two-character play produce fundamentally different games (D-027 criterion 2).
|
||||
**Example from D-093:** The Terminal (logistics hub) — freight workers have insider access, detective requires Commission warrant to access operational areas.
|
||||
**Generator mechanism:** Zoning stage marks `access_tier: insider` zones that require employment/social credentials, not authority credentials.
|
||||
|
||||
### Guarantee 5: Institutional Authority Zone (Restricted Access)
|
||||
**Definition:** A zone where authority credentials (detective archetype) provide access that the insider (smuggler archetype) cannot obtain through social rapport.
|
||||
**Why mandatory:** Mirror of Guarantee 4. Both archetypes must have at least one access advantage the other lacks. This is the mechanical expression of "two keyholes on the same world" (D-027).
|
||||
**Example from D-093:** Gate cluster restricted zones — Commission inspector access; gate aperture chamber (8×4 restricted); observation gallery (z=2, Commission-only).
|
||||
**Generator mechanism:** Zoning stage marks `access_tier: restricted/authority` zones. At least one per district.
|
||||
|
||||
### Guarantee 6: Triangle Social Geometry
|
||||
**Definition:** The district's NPC population must form at least 2 active triangles (D-024 minimum: "2 per template minimum, 1 cross-template"). Triangles require NPCs with conflicting interests positioned in overlapping spatial zones.
|
||||
**Why mandatory:** Triangles are "the atomic unit of social intrigue" (D-024). Without them, investigation and social manipulation loops have no payoff. The 30/50/20 model (D-029) requires triangles to exist across all tiers.
|
||||
**Generator mechanism:** Population stage assigns NPCs with conflicting Want/Secret/Relationship axes. District skeleton output must include at minimum: 2 triangles whose members' daily routines bring them into shared spatial zones. Cross-template triangle (D-024 minimum) means members must span at least 2 social sites.
|
||||
|
||||
### Guarantee 7: Three-Path Investigation Structure
|
||||
**Definition:** Three distinct evidence chains, each requiring different spatial access or social credentials.
|
||||
**Why mandatory:** Path A/B/C structure from D-093 is the mechanical proof-of-concept for investigation gameplay. A single investigation path collapses asymmetric information into a linear puzzle.
|
||||
**Generator mechanism:**
|
||||
- **Path A** (pattern/digital): requires a zone with Meridian/camera access point
|
||||
- **Path B** (physical): requires a physical traversal route with at least one acoustic anomaly or spatial gap
|
||||
- **Path C** (institutional/social): requires a social site where rapport-building yields unique evidence
|
||||
|
||||
Each path must produce at least one piece of evidence not obtainable via the other paths.
|
||||
|
||||
---
|
||||
|
||||
## 3. How Do the Pipeline Stages Map to Gameplay-Relevant Structures?
|
||||
|
||||
Taking the Cities Skylines top-down pipeline as the working model:
|
||||
|
||||
```
|
||||
Geography
|
||||
→ Infrastructure (transport nodes, utilities, Meridian coverage)
|
||||
→ Amenities & Services (social site types, access tiers)
|
||||
→ Population (NPC count, entanglement rate, triangle seeding)
|
||||
→ Zoning (access tier assignment per zone)
|
||||
→ Block generation (multi-block reservations, functional type per block)
|
||||
→ Chunk fill (social site template instantiation, sub-chunk quarter assignment)
|
||||
```
|
||||
|
||||
### Geography → Spatial Affordance Type
|
||||
**Gameplay product:** Determines what kinds of spaces are even possible.
|
||||
- Station → constrained corridors, z-level variation, Meridian infrastructure baked in
|
||||
- Planet-side → open space, variable Meridian coverage, weather effects (D-050: fog degrades vision cones equally)
|
||||
- Orbital → low gravity implications, different spatial scale
|
||||
The generator doesn't need to solve this in v0.1 (one station, one district). But the architecture must accept geography as an input to every downstream stage.
|
||||
|
||||
### Infrastructure → Surveillance + Stealth Topology
|
||||
**Gameplay product:** Where observation is rewarded; where privacy is possible.
|
||||
Critical generator outputs at this stage:
|
||||
- **Transit node placement** → chokepoint geometry (Guarantee 1)
|
||||
- **Meridian coverage assignment per zone** → quiet zone placement (Guarantee 2)
|
||||
- **Utility corridor routing** → maintenance spine, provides physical traversal path for Path B
|
||||
- **Z-level assignment** → which spaces are at ground level vs. elevated vs. sub-level (affects LOS per D-066)
|
||||
|
||||
**Key design requirement:** Infrastructure stage must not be purely functional — it must be evaluated through "does this produce interesting observation positions and interesting private zones?"
|
||||
|
||||
### Amenities & Services → Social Site Type Assignment
|
||||
**Gameplay product:** Which social loops are available and where.
|
||||
Critical generator outputs:
|
||||
- **Social venue (bar-type)** → public social site, social manipulation hub (Guarantee 3)
|
||||
- **Workplace (terminal-type)** → insider access zone (Guarantee 4)
|
||||
- **Institutional space** → authority access zone (Guarantee 5)
|
||||
- **Medical/service** → secondary social contact points, NPC routing attractors
|
||||
|
||||
This is where the D-025 "social site / functional cluster" concept is first instantiated as a type, not yet filled. The amenities stage selects template tags; the chunk fill stage instantiates the template.
|
||||
|
||||
### Population → Triangle and Entanglement Seeding
|
||||
**Gameplay product:** The human drama that investigation reveals.
|
||||
Critical outputs:
|
||||
- **NPC count per zone** → density sufficient for 30/50/20 split (D-029)
|
||||
- **Entanglement rate** → seeded per-game, varies to prevent metagaming (D-029)
|
||||
- **Triangle assignment** → minimum 2 active triangles (D-024), placed across confirmed social sites
|
||||
- **NPC axis rolls** → Want/Secret/Relationship/Routine assigned; these must produce spatial conflicts (NPC A works at the terminal but secretly meets NPC B in the maintenance corridor)
|
||||
|
||||
**Critical relationship with zoning:** Population and Zoning must be jointly optimized. An NPC with a secret that requires access to a restricted zone must be assigned a role that gives them that access. The generator cannot assign secrets that have no plausible staging ground.
|
||||
|
||||
### Zoning → Access Tier Palette
|
||||
**Gameplay product:** The invisible layer of gates that defines what each archetype can see.
|
||||
Zone types required (from D-093 example):
|
||||
- `public` → anyone can enter
|
||||
- `semi-public` → soft social gate (regulars, workers)
|
||||
- `semi-private` → employment/insider required
|
||||
- `private` → insider + relationship required
|
||||
- `restricted` → authority credentials required
|
||||
- `commission-only` → detective-archetype exclusive
|
||||
- `maintenance` → ring-insider exclusive or physical bypass required
|
||||
|
||||
**Key design requirement:** Every district must have at minimum one zone from each end of the spectrum (public ↔ restricted/maintenance). Middle tiers provide the interesting gameplay — they're contestable.
|
||||
|
||||
### Block Generation → Multi-Block Structure Reservation
|
||||
**Gameplay product:** The "large civic structures" (gate terminals, stadiums, gov buildings) that anchor district identity and create mandatory routing patterns.
|
||||
Critical requirements:
|
||||
- Reserve multi-block footprint before chunk fill runs
|
||||
- Multi-block structures create natural chokepoints at their approaches (Guarantee 1)
|
||||
- They contain the institutional access zones (Guarantee 5)
|
||||
- Their internal layout is constrained but not dictated — a gate terminal must have a gate concourse (public) AND a restricted zone; exact dimensions are filled at chunk level
|
||||
|
||||
### Chunk Fill → Social Site Template Instantiation
|
||||
**Gameplay product:** The specific spaces players actually inhabit.
|
||||
This is where D-025 templates are instantiated. The chunk fill stage:
|
||||
1. Selects a social site template tag (assigned at amenities stage)
|
||||
2. Configures sub-chunk quarters for that template's spatial requirements
|
||||
3. Places NPC slots, interaction points, overhearing positions, evidence anchors
|
||||
4. Ensures D-025 sightline requirement within the cluster's tile footprint
|
||||
|
||||
---
|
||||
|
||||
## 4. What Constraints Does the Triangle Template System (D-025) Place on Chunk Fill?
|
||||
|
||||
This is the critical mechanics question. Let me map it precisely.
|
||||
|
||||
### Constraint 1: Connected Space Continuity (15-40 sim tile radius)
|
||||
**D-025 says:** The functional cluster is "15-40 tiles of connected space with internal sightlines."
|
||||
**Constraint on chunk fill:** The sub-chunk quarter system must not produce fully enclosed, sightline-isolated quarters within a single social site's footprint. A 64×64 sim tile chunk (32m) can fit a 40-tile cluster comfortably — BUT the quarter merge/split rules must preserve internal connectivity.
|
||||
**Practical rule:** If a social site spans N quarters, all N quarters must share at least one sightline corridor. A 2×2 quarter full merge (open floor) trivially satisfies this. An L-shaped 3-quarter configuration must not have the interior angle be a solid wall.
|
||||
|
||||
### Constraint 2: Internal Sightline Preservation
|
||||
**D-025 says:** "physical cluster defines spatial identity (sightlines, overhearing, public/private)."
|
||||
**Constraint on chunk fill:** Quarter configurations that fragment a social site into acoustically isolated boxes violate the overhearing mechanic (D-018 sound model). The observation loop (Loop 1) depends on players being able to hear conversations from adjacent positions.
|
||||
**Practical rule:** Social site chunks must have at least one "soft partition" zone — a position where the player can hear adjacent conversations but NPCs have reasonable privacy expectation. This is the eavesdrop sweet spot that rewards Careful stance.
|
||||
|
||||
### Constraint 3: NPC Single-Ownership with Cross-Reference Links
|
||||
**D-025 says:** "NPCs are owned by exactly one template with reference links to others."
|
||||
**Constraint on chunk fill:** When a district has multiple social sites, the chunk fill for each site must assign NPC ownership unambiguously. An NPC cannot "belong" to two filled chunks.
|
||||
**Cross-template contamination** (D-025: "One NPC can hold roles in multiple social sites") is expressed through reference links, not through shared ownership. The chunk fill must represent this as: NPC primary slot in Chunk A, secondary reference appearance in Chunk B (e.g., a bar regular who also works at the terminal).
|
||||
**Practical implication for the generator:** The population stage (upstream) must assign NPC primary templates BEFORE chunk fill runs. Chunk fill uses the population stage output, not the other way around.
|
||||
|
||||
### Constraint 4: The 4-8 NPC Density Window
|
||||
**D-025 says:** "4-8 NPCs who regularly interact."
|
||||
**Constraint on chunk fill:** A social site's chunk cannot be over-filled (>8 regularly interacting NPCs in one cluster) or under-filled (<4 interacting NPCs). Tier 3 background NPCs (D-029: 30% flat wallpaper) pass through but don't "belong" to the cluster.
|
||||
**Quarter implication:** A full 2×2 quarter merge producing a large open floor can host 4-8 NPCs in a social site. A single-quarter configuration (¼ chunk = 32×32 sim tiles = 16m) can host a smaller social site, but may be too small for 8 NPCs with meaningful daily routines. The generator should default: smaller clusters in single quarters (4-5 NPCs), larger clusters in merged quarter configurations (6-8 NPCs).
|
||||
|
||||
### Constraint 5: Public/Private Gradient Within the Cluster
|
||||
**D-025 says:** "Physical cluster defines spatial identity (sightlines, overhearing, public/private)."
|
||||
**Constraint on chunk fill:** Within a single functional cluster, there must be spatial differentiation between public-facing areas and private areas. The bar example from D-093: bar area (public, all access) + back room (private, insider only) + below-bar maintenance access (restricted, ring members only). This tri-zone structure within one cluster must be representable in the quarter system.
|
||||
**Quarter solution:** A 4-quarter chunk representing a bar might configure as:
|
||||
- 2 quarters merged: main bar floor (public)
|
||||
- 1 quarter: back area / staff zone (semi-private)
|
||||
- 1 quarter gap or sub-quarter shack: storage/access point (private/restricted)
|
||||
|
||||
### Constraint 6: The "Invisible Infrastructure" Principle (G-08 from D-093)
|
||||
**D-093 says:** "every ring location reads as mundane; criminal function visible only to those who know."
|
||||
**Constraint on chunk fill:** This is a design constraint on HOW templates are instantiated. The chunk fill cannot place obvious "smuggling room" tiles. The physical arrangement must read as functional for mundane purposes while being usable for criminal ones.
|
||||
**Practical implication:** Templates must have a "surface reading" (manifest function) and a "second reading" (criminal function). Chunk fill must not diverge these visually — the same tile arrangement serves both. The distinction comes from NPC knowledge (D-041 knowledge graph), not from tile art.
|
||||
|
||||
---
|
||||
|
||||
## 5. Open Questions I'm Flagging for Round 2
|
||||
|
||||
### Q-A: Does Population Precede or Follow Zoning in Our Pipeline?
|
||||
The Cities Skylines model puts population after zoning. But our game has a critical dependency: **NPC secrets must have plausible staging grounds in the spatial layout**. An NPC with a secret meeting in a restricted zone requires that restricted zone to exist before the NPC can be validly generated.
|
||||
|
||||
**My position:** Population follows zoning for assignment (zones exist first), but population requirements should CONSTRAIN zoning (a zone must exist that satisfies the minimum secret/access requirements of the triangle configuration). This creates a feedback loop between population and zoning that the pipeline must resolve. Tyre will need to address whether this creates implementation complexity.
|
||||
|
||||
### Q-B: Triangle Template (D-025) Instantiation Stage
|
||||
The workshop brief asks where triangle templates are instantiated in the pipeline. **My position:** Triangle configuration is determined at the population stage (which NPCs exist, which have conflicting interests). Social site templates (D-025) are instantiated at chunk fill. The population stage produces a "social graph" that the chunk fill stage then places into physical space.
|
||||
|
||||
This means: social graph → chunk fill. NOT: chunk fill → social graph. The generator must not produce spatial arrangements and then try to fill them with compatible social graphs. The social graph drives spatial requirements.
|
||||
|
||||
### Q-C: What Is the Minimum Generated District?
|
||||
For v0.1 validation purposes: if we express Sova Transit District as generator output, what is the minimum generator that can reproduce it? I'd propose:
|
||||
- 1 workplace social site (terminal-type)
|
||||
- 1 social venue (bar-type)
|
||||
- 1 maintenance spine (quiet zone)
|
||||
- 1 transit encounter node
|
||||
- 1 restricted zone (authority access)
|
||||
- 2 active triangles across the above sites
|
||||
|
||||
This is the "minimum viable district" that satisfies all seven guarantees. Anything below this fails at least one gameplay loop.
|
||||
|
||||
---
|
||||
|
||||
## Summary: Generator Requirements as a Checklist
|
||||
|
||||
| Requirement | Source | Stage Where Guaranteed |
|
||||
|---|---|---|
|
||||
| Surveillance chokepoint | D-093, Loop 1 | Infrastructure |
|
||||
| Meridian dead spot / quiet zone | D-093, D-029, Loop 4 | Infrastructure |
|
||||
| Public social venue (bar-type, D-025 compliant) | D-025, Loop 3 | Amenities |
|
||||
| Insider access zone (workplace-type) | D-007, Loop 3 | Zoning |
|
||||
| Authority access zone (restricted-type) | D-007, Loop 3 | Zoning |
|
||||
| Triangle social geometry (≥2 active, D-024) | D-024, D-029, Loop 2 | Population |
|
||||
| Three-path investigation structure (A/B/C) | D-093, Loop 2 | Multi-stage |
|
||||
| Transit encounter node | D-093, D-095, Loop 5 | Infrastructure |
|
||||
| Internal sightlines within social sites | D-025, Loop 1 | Chunk fill |
|
||||
| Public/private gradient within clusters | D-025, Loops 1-3 | Chunk fill |
|
||||
| Invisible infrastructure (dual-reading spaces) | D-093 G-08, Loop 4 | Chunk fill |
|
||||
|
||||
Every pipeline stage maps to at least one row. No guarantee is produced "for free" — each requires an explicit generator mechanism.
|
||||
|
||||
---
|
||||
|
||||
*Gestalt — Round 1 complete. Ready to respond to Tyre (technical constraints), Miri (worldbuilding inputs), and Araminta (visual coherence) in Round 2.*
|
||||
@@ -0,0 +1,438 @@
|
||||
# Generator Architecture Workshop — Round 2: Gestalt
|
||||
|
||||
**Role:** Systems Design / Gameplay Loop Requirements
|
||||
**Date:** 2026-02-27
|
||||
**Workshop:** Generator Architecture (#562)
|
||||
|
||||
---
|
||||
|
||||
## Framing: Acknowledging the Lead Directive
|
||||
|
||||
The lead directive lands correctly. My Round 1 analysis was framed entirely through the investigation lens. That was useful for establishing what the generator must never fail to produce — but it was insufficient for a game that promises to support tycoon play, dating sim / social relationship play, and political drama as equally valid first-class experiences.
|
||||
|
||||
Let me break down what this means mechanically, then propose a pipeline that serves all of them.
|
||||
|
||||
The central insight: **the spatial guarantees from Round 1 are not investigation-specific. They're human-social-activity-specific.** A surveillance chokepoint is also a trade route bottleneck, a political rally point, and a serendipitous encounter location. An informal zone is also a grey market space, a tryst location, and a dissident meeting point. The 7 guarantees from Round 1 were named wrong — they were investigation vocabulary for playstyle-agnostic spatial archetypes.
|
||||
|
||||
This round I'm going to rename them, show how they serve every playstyle, add the guarantees that investigation didn't require, and propose a concrete two-phase pipeline that Tyre, Miri, and Ozzie's inputs have informed.
|
||||
|
||||
---
|
||||
|
||||
## 1. The Revised Spatial Guarantee Set: Playstyle-Agnostic Archetypes
|
||||
|
||||
Drop the investigation vocabulary. Here are the 7 spatial archetypes every generated district must contain, with their multi-playstyle readings:
|
||||
|
||||
### Archetype 1: Traffic Chokepoint
|
||||
**Physical definition:** A spatial bottleneck where significant NPC traffic must pass and is observable from a fixed adjacent position.
|
||||
**By playstyle:**
|
||||
| Playstyle | What it does for you |
|
||||
|---|---|
|
||||
| Investigation | Surveillance position — observe tells, track movements, spot anomalies |
|
||||
| Tycoon | Trade route leverage — who controls the choke controls the flow of goods |
|
||||
| Dating / Social | Serendipitous encounter point — this is where you "happen to run into" someone |
|
||||
| Political | Campaign territory — public visibility, speech platform, constituency pressure |
|
||||
|
||||
### Archetype 2: Informal Zone
|
||||
**Physical definition:** A zone with degraded institutional coverage, low ambient traffic, suitable for private or unofficial activity. Includes maintenance corridors, service back-alleys, rooftop access, below-street passages.
|
||||
**By playstyle:**
|
||||
| Playstyle | What it does for you |
|
||||
|---|---|
|
||||
| Investigation | Quiet zone — ring operations, confidential source meetings, dead drops |
|
||||
| Tycoon | Grey market space — off-ledger deals, unofficial distribution, tax-adjacent commerce |
|
||||
| Dating / Social | Privacy space — the rendezvous location, the conversation that can't happen in public |
|
||||
| Political | Back-channel space — the meeting that isn't on the record |
|
||||
|
||||
**Note for the generator:** This archetype must be generated deliberately, not incidentally. It is the space that official documentation doesn't account for (Miri's C-5 insight). Every district must have at least one, and it must be findable without being guided to.
|
||||
|
||||
### Archetype 3: Social Hub
|
||||
**Physical definition:** A D-025-compliant functional cluster (4-8 NPCs, 15-40 tile connected space, internal sightlines) that is publicly accessible without institutional credentials.
|
||||
**By playstyle:**
|
||||
| Playstyle | What it does for you |
|
||||
|---|---|
|
||||
| Investigation | Rapport-building stage — trust tier progression, gossip extraction |
|
||||
| Tycoon | Networking hub — find suppliers, hear rumors of demand, recruit partners |
|
||||
| Dating / Social | Romance venue — relationship initiation, shared leisure, social graph expansion |
|
||||
| Political | Influence gathering — read public mood, identify allies, make your presence felt |
|
||||
|
||||
### Archetype 4: Institutional Space
|
||||
**Physical definition:** A zone where official credentials, position, or authority determine access — and where institutional actors can be leveraged, pressured, or circumvented.
|
||||
**By playstyle:**
|
||||
| Playstyle | What it does for you |
|
||||
|---|---|
|
||||
| Investigation | Authority access zone — show credentials, pull records, compel cooperation |
|
||||
| Tycoon | Licensing / permit space — register trade routes, dispute cargo claims, bribe inspectors |
|
||||
| Dating / Social | Official encounter context — the formal interaction that begins some relationships |
|
||||
| Political | Power center — submit proposals, apply pressure, climb the institutional hierarchy |
|
||||
|
||||
### Archetype 5: Insider Space
|
||||
**Physical definition:** A zone where community membership, employment, or social standing (not official credentials) determines access.
|
||||
**By playstyle:**
|
||||
| Playstyle | What it does for you |
|
||||
|---|---|
|
||||
| Investigation | Insider access zone — ring operations visible only to those who belong |
|
||||
| Tycoon | Guild / union / cooperative — preferred trade terms, insider pricing, loyal suppliers |
|
||||
| Dating / Social | Close network space — the bar where the friend group drinks, the community event |
|
||||
| Political | Party / faction HQ — where the organized power lives |
|
||||
|
||||
### Archetype 6: Economic Node
|
||||
**Physical definition:** A space where goods, services, or information with economic value change hands. Includes markets, logistics points, trade counters, informal exchanges.
|
||||
**By playstyle:**
|
||||
| Playstyle | What it does for you |
|
||||
|---|---|
|
||||
| Investigation | Evidence trail — cargo manifests, transaction logs, the money that follows the crime |
|
||||
| Tycoon | Primary profit opportunity — buy low, sell high, establish routes |
|
||||
| Dating / Social | Shared activity — shopping together, market browsing, the informal economic life |
|
||||
| Political | Leverage point — economic actors are political actors; control trade = control votes |
|
||||
|
||||
**This is the archetype my Round 1 analysis missed.** It's present in v0.1 (the Terminal is an economic node) but I didn't name it as a required generator guarantee. It's non-negotiable for tycoon play.
|
||||
|
||||
### Archetype 7: Encounter Corridor
|
||||
**Physical definition:** A space primarily designed for movement — transit corridors, promenades, public routes — where encounters happen but nobody lingers.
|
||||
**By playstyle:**
|
||||
| Playstyle | What it does for you |
|
||||
|---|---|
|
||||
| Investigation | Observation route — follow NPCs, track patterns, sense when routines break |
|
||||
| Tycoon | Supply chain link — goods move through here; disrupting/controlling it is leverage |
|
||||
| Dating / Social | Casual crossing point — daily routine overlaps, the route you "happen to share" |
|
||||
| Political | Visibility territory — seen being there communicates political alignment |
|
||||
|
||||
**What I'm dropping from Round 1:** "Three-path investigation structure (A/B/C)" as a standalone guarantee. In the multi-playstyle framework, this becomes: **at least 3 distinct engagement vectors must exist for each playstyle**. For investigation these are pattern/physical/social. For tycoon they're supply/demand/regulation. For social/dating they're work-meeting/social-meeting/crisis-meeting. The generator doesn't need to know which playstyle the player chose — it needs to produce enough structural variety that at least 3 paths exist for any approach.
|
||||
|
||||
---
|
||||
|
||||
## 2. New Playstyle-Specific Guarantees
|
||||
|
||||
The 7 archetypes above are the floor. These additional guarantees serve playstyles the 7 archetypes don't fully address:
|
||||
|
||||
### Tycoon-Specific: Economic Asymmetry Signal
|
||||
The district must contain at least one indicator of **economic imbalance** — something being undersupplied, oversupplied, restricted, or priced unequally across zones. This is the tycoon's opening opportunity. In v0.1 terms: aftermarket lattice components are undersupplied because Commission regulation creates artificial scarcity. The generator must produce an analogous economic tension in every district.
|
||||
**Implementation:** The society profile's `economic_pressure` field (Miri's ingredient D) directly determines what the asymmetry is. `tight-margin` + `prohibition-economy` produces the Sova scenario. Different pressure combinations produce different economic tensions. The generator surfaces these as locatable economic nodes with discoverable demand gaps.
|
||||
|
||||
### Social/Dating-Specific: Temporal Encounter Window
|
||||
The district must produce at least one NPC whose daily routine creates **predictable, repeatable encounter opportunities** outside of work context. This is the "regulars at the bar at 1800 every day" pattern. The tycoon has stable trade windows; the romance player needs the equivalent.
|
||||
**Implementation:** This is a property of NPC routine generation (Stage 5), but the generator must guarantee the SPATIAL CONDITION: a social hub with defined temporal peaks (morning rush, evening gathering, night shift crowd). The D-031 day-phase system already provides this — social hubs must be tagged with active phases during skeleton generation.
|
||||
|
||||
### Political-Specific: Power Gradient Visibility
|
||||
The district must make its power topology **spatially legible** — who has authority over whom, and where that authority is exercised, must be readable from the space without metagame knowledge.
|
||||
**Implementation:** Araminta's zone palette and lighting temperature gradient already does this for institutional vs. social zones. The generator must additionally guarantee that at least one NPC occupies a visible authority position (SYSTEM pattern per Q-033) whose institutional relationship to other social sites is observable. The detective already gets this through the Commission; the political player needs the equivalent civic power holder.
|
||||
|
||||
### Non-Urban Specific: Natural Chokepoint
|
||||
**For non-urban templates (farmland, wilderness, maritime, ski resort, beach):** The Traffic Chokepoint archetype takes a different form — a mountain pass, a harbor entrance, a river ford, a seasonal trail. These are geographically determined rather than architecturally determined.
|
||||
**Generator requirement:** When `SettingGeometry = Rural / Maritime / Wilderness`, the infrastructure stage replaces architectural corridor planning with **terrain chokepoint identification**. The spatial archetype is the same; the generator mechanism is different.
|
||||
|
||||
---
|
||||
|
||||
## 3. Two-Phase Pipeline Proposal
|
||||
|
||||
The lead directive specifies: world/district generation is a background prep pass (spare CPU core). Local area generation is the fine-tuned interactive system. These are architecturally separate.
|
||||
|
||||
Here is the concrete two-phase pipeline, integrating Tyre's data structures, Miri's worldbuilding inputs, and the multi-playstyle requirements:
|
||||
|
||||
---
|
||||
|
||||
### PHASE 1 — Background Prep (Asynchronous, no player interaction required)
|
||||
|
||||
These stages can run while the player is already in a different location. Output is cached; player never waits.
|
||||
|
||||
#### Stage 0: World Significance Tier
|
||||
**Input:** Master world seed, galaxy topology
|
||||
**Output:** Per-location `SignificanceTier` + `SettingGeometry`
|
||||
|
||||
| Tier | What gets generated | Social density | Active scenarios |
|
||||
|---|---|---|---|
|
||||
| Center-stage | Full district generation, all phases | High | Multiple |
|
||||
| Regional | 1-4 districts, full phases | Medium | 1-2 |
|
||||
| Backwater | 1 district or partial, condensed | Low | 0-1 |
|
||||
| Waypoint | Tier 3 only — spatial skeleton, no NPCs | Minimal | None |
|
||||
| Insignificant | Not generated until player approaches | — | — |
|
||||
|
||||
This is not a Phase 1 computation — it's the classification that governs how much Phase 1 runs. A waypoint location gets a skeleton only. An insignificant place doesn't exist until the player gets close.
|
||||
|
||||
`SettingGeometry` enum (the lead directive requires this be first-class):
|
||||
```
|
||||
Station → zone-and-level grid
|
||||
Urban → terrain-influenced spread
|
||||
Rural → low-density farmland/town spread
|
||||
Maritime → coastal topology with water interface
|
||||
Wilderness → minimal infrastructure, scattered POIs
|
||||
Specialized → resort, research, military (single economic function)
|
||||
```
|
||||
|
||||
#### Stage 1: Society Profile Assembly
|
||||
**Input:** World seed, system political classification, heritage ingredients (Q-032)
|
||||
**Output:** `SocietyProfile` struct
|
||||
|
||||
This is Miri's full ingredients menu: Heritage Roots (blend weights), Settlement Motivation, Economic Function, Economic Pressure (1-2), Drift Stage, Absence Parameters, Faction Presence tiers, tech-level 3-axis profile.
|
||||
|
||||
Key outputs that feed downstream stages:
|
||||
- `social.privacy_level` → access tier thresholds
|
||||
- `trust.building_rate` → NPC trust progression speed
|
||||
- `economic_pressure` → economic asymmetry type
|
||||
- `meridian_coverage_baseline` → from tech-level axis 1
|
||||
- `active_phases` → which day phases have which social activity (D-031 integration)
|
||||
- `cultural_tags` → for NPC name generation, ambient text flavor
|
||||
|
||||
**On the serde question (Miri's OQ-5):** The society profile YAML must map to a Rust struct via serde. The nested structure (heritage with blend weights, faction presence with per-faction tiers) is manageable — Rust's serde_yaml handles this. The critical requirement is that blend weights sum to 1.0 and absence parameters serialize as `Option<T>` (NULL = None). Tyre should confirm the specific schema contract.
|
||||
|
||||
#### Stage 2: District Skeleton Generation
|
||||
**Input:** Society Profile, SignificanceTier, SettingGeometry, political classification
|
||||
**Output:** `DistrictSkeleton` (Tyre's data structure, extended with `significance_tier` and `setting_geometry` fields)
|
||||
|
||||
This stage is where the multi-playstyle spatial archetypes are guaranteed. The skeleton generator runs a **spatial guarantee audit** after producing a candidate skeleton:
|
||||
|
||||
```
|
||||
For each of the 7 spatial archetypes:
|
||||
Does the candidate skeleton contain at least 1 spatial site of this type?
|
||||
If NO → regenerate or augment the skeleton until the guarantee is satisfied
|
||||
|
||||
Additional per-playstyle checks:
|
||||
Economic node present? (required for significance_tier ≥ backwater)
|
||||
Temporal encounter window in social hub? (active_phases set?)
|
||||
Power gradient visible? (SYSTEM-pattern NPC slot in institutional space?)
|
||||
Informal zone explicitly flagged? (not inferred from access tier)
|
||||
```
|
||||
|
||||
This audit is the enforcement mechanism. Without it, the generator can technically satisfy the schema while producing an unplayable district.
|
||||
|
||||
**On edge bleed (lead directive):** The skeleton's `access_points` and `corridors` must extend to district edges as open connection slots. Neighboring districts resolve these connections when their own skeletons are generated. Blocks at district edges can be tagged `cross_district: true` on their social sites — these sites serve the district but are spatially adjacent to the neighbor. This is what creates the bleeding effect: a bar on the edge of the Transit District and the Residential Core serves both populations. It appears in both skeletons as a shared reference.
|
||||
|
||||
**Historical event modifier pass:** After the base skeleton is generated, Miri's historical events apply as modifications:
|
||||
1. Founding crisis → push drift_stage for affected zones, add blocked/repurposed blocks
|
||||
2. Economic disruption → modify economic_pressure, introduce `abandoned_state` blocks
|
||||
3. Institutional incursion → modify faction_presence_tier, add/remove authority zones
|
||||
|
||||
These modifications produce Ozzie's "historical palimpsest" — the skeleton records not just the current state but the modifications that produced it. When the L-shaped building in ChunkLayout is `LShape { corner: NE, cause: emergency_extension }`, the cause field is available for environmental storytelling at chunk fill time.
|
||||
|
||||
#### Stage 3: Block Planning
|
||||
**Input:** DistrictSkeleton, economic tier, era tags (from era stratification)
|
||||
**Output:** 16 `BlockSkeleton`s with ChunkLayouts, edge contracts, era tags, landmark slots
|
||||
|
||||
Era tags are assigned at block level here (confirming Araminta's rule). The block planning stage also:
|
||||
- Reserves multi-block footprints (gate terminals, parks, government buildings)
|
||||
- Assigns landmark slots (1 per district quadrant, per Araminta's §5.3)
|
||||
- Produces edge contracts for each chunk face (Tyre's Option A recommendation)
|
||||
- Assigns density parameters (filled quarters per block by economic tier, per Araminta's §3.3)
|
||||
|
||||
---
|
||||
|
||||
### PHASE 2 — Local Area Generation (On-Demand, Player-Proximate)
|
||||
|
||||
These stages run as the player enters loading radius. They produce the tile-level and NPC-level content.
|
||||
|
||||
#### Stage 4: Chunk Fill
|
||||
**Input:** BlockSkeleton, edge contracts, D-025 template library, society profile, era tags
|
||||
**Output:** Tile data for each 64×64 sim tile chunk
|
||||
|
||||
This is where Tyre's 500ms budget applies. Template stamping is the mechanism — select a template from the library appropriate to the zone/era/access-tier, stamp it into the quarter configuration, then decorate procedurally within the template's variation space.
|
||||
|
||||
Araminta's visual constraints apply here in full (zone palette, lighting temperature, saturation hierarchy, LOS anchor intervals, facade variation budget).
|
||||
|
||||
**Quarter fill for unclaimed space:** Nigel's flavor structure categories + Araminta's empty quarter types need unification. I'm proposing they're the same system with two vocabularies:
|
||||
|
||||
| Araminta type | Nigel category | Generator selection driver |
|
||||
|---|---|---|
|
||||
| Open plaza | Settlement indicator (seating clusters) | Social/community cultural heritage |
|
||||
| Service alley | — (implicit) | Always present at ratio, not selected |
|
||||
| Courtyard / garden | Settlement indicator (container gardens) | Heritage roots with outdoor-culture tradition |
|
||||
| Vehicle/cargo staging | — (logistics function) | Economic function = logistics/manufacturing |
|
||||
| Structural gap (undeveloped) | Economic stress (abandoned equipment) | Economic pressure = survival-gap or economic disruption event |
|
||||
| — | Informal economy (market stalls) | Economic pressure = tight-margin or prohibition-economy |
|
||||
| — | Faction presence (Commission kiosk) | Faction presence tier = standard or comprehensive |
|
||||
|
||||
**The critical addition:** Flavor structure type does feed NPC generation. A `market_stall` quarter increases the probability of OPERATOR-pattern NPCs in the adjacent social site's population. A `commission_kiosk` quarter increases SYSTEM-pattern NPCs. A `settlement_indicator (shrine)` quarter increases ANCHOR-pattern NPCs. This answers Ozzie's OQ-3 — yes, what fills a quarter has downstream social consequences.
|
||||
|
||||
#### Stage 5: NPC Instantiation
|
||||
**Input:** Chunk fill (role slots), triangle assignments from DistrictSkeleton, society profile, world seed
|
||||
**Output:** Generated NPCs with D-024 10-axis configurations, assigned roles, triangle connections, D-029 entanglement assignments
|
||||
|
||||
NPC instantiation runs after chunk fill because the role slots and their spatial context must exist before NPCs can be validly assigned to them. The role slot (e.g., "this is a HANDLER-pattern position in the logistics hub social site") constrains which axis combinations are generated.
|
||||
|
||||
This resolves my Round 1 OQ-A: **I was wrong about needing full co-resolution of population and zoning.** The feedback loop I identified is already handled by the skeleton stage:
|
||||
- **Skeleton stage:** triangle TOPOLOGY is locked (role types, conflict structure, staging ground requirements) — this ensures staging grounds exist
|
||||
- **Chunk fill stage:** role SLOTS are placed in the correct spatial positions
|
||||
- **NPC instantiation:** axis VALUES are generated to fill role slots
|
||||
|
||||
No feedback loop required. The skeleton guarantees that "a secret-holder NPC needs access to a restricted zone" by including a restricted zone in the skeleton's social site configuration for that template. The NPC instantiation then generates an NPC whose Secret axis is appropriate for that role.
|
||||
|
||||
**REVISED position (OQ-A):** Zoning → Skeleton (triangle topology + role types) → Block Planning → Chunk Fill (role slots in space) → NPC Instantiation (NPC values). Sequential, no feedback loop. The skeleton stage's role-type specification is the mechanism that guaranteed staging grounds without requiring simultaneous resolution.
|
||||
|
||||
#### Stage 6: Scenario Instantiation
|
||||
**Input:** NPC population, Tier 1 module pool draw (D-023), world seed
|
||||
**Output:** Active scenario config, entanglement assignments, evidence placement, active triangle configurations
|
||||
|
||||
This is the final step that makes the district "live" as an investigation/tycoon/social/political space. The entanglement pattern (D-029) is seeded here — which 20% (variable per seed) of NPCs are entangled. Evidence placement is seeded here. Active triangles are activated here.
|
||||
|
||||
For **tycoon play:** the economic asymmetry signals (economic node contents, demand gaps, price differentials) are set in this stage. The "who controls what" question in economic space is answered here.
|
||||
|
||||
For **political play:** the power topology (which institutional NPC reports to which, what's up for contest, where alliances can shift) is configured in this stage.
|
||||
|
||||
---
|
||||
|
||||
## 4. On Seed Architecture (OQ-2 from Round 1 Notes)
|
||||
|
||||
The lead says seeds are solved: single master seed, don't over-engineer. Endorsing this position.
|
||||
|
||||
**Single master seed derives all sub-seeds deterministically.** Same seed + different character = same world with different lenses. This is the correct design — it makes the "two playthroughs of the same seed" comparison a first-class experience (Nigel's comparison test, my D-027 criterion 2 goal). The player chose to play the same world from a different angle. They should discover exactly that.
|
||||
|
||||
For the pipeline: each stage receives `derive_seed(master_seed, stage_id, location_id)` → deterministic sub-seed. No per-stage seed parameters. The seed architecture is solved at the hash function level.
|
||||
|
||||
---
|
||||
|
||||
## 5. Non-Urban Templates and Insignificant Places
|
||||
|
||||
The lead directive requires the architecture to handle: farmland, wilderness, secluded towns, ocean, boats, ski resorts, beaches.
|
||||
|
||||
**These don't break the pipeline — they parameterize it differently.**
|
||||
|
||||
The key parameters that change for non-urban settings:
|
||||
|
||||
| Parameter | Urban (station) | Rural / Wilderness | Maritime |
|
||||
|---|---|---|---|
|
||||
| Block density | High (12-16 filled quarters) | Low (2-6 filled quarters) | Variable (coastal vs. open water) |
|
||||
| Social hub type | Bar, restaurant, forum | Rural tavern, community hall, farm cooperative | Harbor tavern, ship crew quarters, fish market |
|
||||
| Traffic chokepoint | Architectural corridor | Geographical feature (pass, ford, gate) | Harbor mouth, dock access, tide-dependent route |
|
||||
| Meridian coverage | Standard to comprehensive | Sparse to absent | Absent on water, sparse on shore |
|
||||
| NPC routine pattern | Shift-based (industrial) | Seasonal / agricultural | Tide-based / weather-dependent |
|
||||
| Economic node | Logistics terminal, market | Farm output, resource extraction | Harbor trade, catch market |
|
||||
| Informal zone | Maintenance corridor | Forest edge, ravine, cave | Below-deck, hidden cove, underwater |
|
||||
|
||||
**The generator handles all of these through `SettingGeometry` as a first-class input.** The pipeline stages run the same logic; the input parameters shape what's generated at each stage.
|
||||
|
||||
For **specialized settings** (ski resort, beach):
|
||||
- These are `SettingGeometry::Specialized` with a single primary economic function (recreation/tourism)
|
||||
- Social hubs are themed (lodge, beach bar, equipment rental as informal economy)
|
||||
- The institutional space is smaller (ski patrol office, lifeguard station) but must exist
|
||||
- The informal zone is the spaces off the maintained routes (off-piste terrain, after-hours areas)
|
||||
|
||||
**Insignificant places** are handled by the significance tier system (Stage 0). A waypoint gets a Tier 3 spatial skeleton only — traversable space with environmental storytelling baked in, but no NPC simulation. These are the spaces that "exist because the geometry requires them to," not because they host drama. They're the breathing room that makes the center-stage locations feel like islands in a wider world.
|
||||
|
||||
---
|
||||
|
||||
## 6. Responding to Tyre's Technical Constraints
|
||||
|
||||
**On hierarchy depth:** Accepted. Quarters are generation-time layout constraints, not hierarchy levels. Four levels (Region/District/Block/Chunk) is correct.
|
||||
|
||||
**On edge contracts:** Option A (edge contracts) is correct and essential for the district-edge bleed the lead requires. The `CorridorSpine` in the DistrictSkeleton should explicitly model cross-district continuity — a corridor that exits the district boundary records its target access point in the neighboring district.
|
||||
|
||||
**On the district skeleton data structure:** Tyre's `DistrictSkeleton` needs three additions for multi-playstyle support:
|
||||
1. `significance_tier: SignificanceTier` — governs how much of Phase 2 runs
|
||||
2. `setting_geometry: SettingGeometry` — governs what spatial archetypes are appropriate
|
||||
3. `guarantee_audit: GuaranteeAuditResult` — records which of the 7 archetypes are satisfied and where (makes the audit inspectable for validation)
|
||||
|
||||
And a modification: `SocialSitePlacement.triangles` should carry not just `TriangleTemplate` (which NPC types conflict) but `triangle_purpose: TrianglePurpose` — an enum that records what the conflict is FOR (investigation/economic/political/social), allowing the scenario instantiation stage to activate relevant triangles based on active playstyle context.
|
||||
|
||||
**On chunk fill budget:** Template stamping approach is correct for v0.2. One addition: the flavor structure assignment (Stage 4) should be pre-computed at block planning time (Stage 3) and stored in the BlockSkeleton, so chunk fill can read flavor_structure_type from its BlockSkeleton rather than computing it. This reduces per-chunk computation.
|
||||
|
||||
**On the Transit District as ground truth:** Fully endorsed. The validation deliverable is: hand-authored `DistrictSkeleton` + 64 `ChunkData`s for the Transit District, validated against the guarantee audit (all 7 archetypes satisfied, all 3 multi-playstyle engagement vectors verified for investigation, tycoon, and social play simultaneously). If it satisfies all three lenses, the schema is correct.
|
||||
|
||||
---
|
||||
|
||||
## 7. Responding to Miri's Worldbuilding Inputs
|
||||
|
||||
**On society profile as first-class data structure:** Confirmed and extended. The society profile feeds Stage 1 (pre-pipeline) and is passed through the entire Phase 1 pipeline. Every downstream stage receives the society profile as a parameter, not just the abstract outputs from it.
|
||||
|
||||
**On cultural variation producing mechanical variation:** Fully endorsed. The mechanism I'm adding: `social.privacy_level` from Miri's society profile maps directly to `access_tier.*` thresholds in the DistrictSkeleton's social sites. A high-privacy culture (Frost-dominant) produces more semi-private zones and slower trust-building windows at the same social site type. Same "bar" template, different mechanical behavior.
|
||||
|
||||
**On era stratification:** Era tags live at block level (Stage 3), as Araminta confirmed. I'm adding that era tags also record the REASON for era stratification — specifically, whether a block's era differs from the district norm due to a historical event modifier. `era: Era2, era_cause: corporate_merger` tells the chunk fill stage to use era-2 materials AND to place visible seams/contrasts that suggest the building was constructed in two phases.
|
||||
|
||||
**On grey economy as negative space:** The Stage 2 guarantee audit explicitly checks for `informal_zone: present`. This zone is not generated from amenities or zoning — it's generated from the infrastructure stage as an absence in the official map. The generator must model what's missing, not just what's placed.
|
||||
|
||||
---
|
||||
|
||||
## 8. Responding to Ozzie's Fan Concerns
|
||||
|
||||
**On Second Station Syndrome:** The multi-playstyle expansion actually solves this more thoroughly than investigation-only would. If the investigation-only player has learned "bar is always northwest," the tycoon-focused player might have shaped the district's economic node distribution differently. But more importantly: the social topology (who's in conflict, who's in power, what's the grey economy structure) varies by seed more than the spatial topology. The skeleton is never "the same shape wearing a different hat" — the shape is determined by the social topology, not the other way around.
|
||||
|
||||
**On quarters feeling caused, not random:** The `LShapeCause` enum I proposed in Stage 2 is the mechanism. The generator doesn't produce L-shaped buildings randomly — it produces them because `emergency_extension` (an addition was forced by a blocked demolition), `acquisition_boundary` (two buildings merged under new ownership), or `organic_growth` (gradual expansion over time). At chunk fill time, these causes manifest as visual evidence: a material seam in `organic_growth`, a slightly different era tag on the added portion in `acquisition_boundary`. The player may not consciously read the cause, but they feel "this shape makes sense here."
|
||||
|
||||
**On quarters producing social variation:** Confirmed, with the flavor-structure-to-NPC-affinity link I described in §3's chunk fill stage. Market stall quarter → OPERATOR-pattern NPC affinity. This makes the choice of flavor structure have downstream social consequences, not just visual ones.
|
||||
|
||||
**On historical palimpsest:** Miri's era stratification + historical event modifier pass + my `era_cause` addition together produce this. The generator records history as a sequence of modifications, not just a current state. The chunk fill stage can read the full modification history and express it in the tiles.
|
||||
|
||||
**On density contrast (the rhythm: crowded market → narrow corridor → sudden atrium):** This is produced by the combination of Araminta's density parameter (§3.3) and the landmark slot reservation (§5.3). The generator ensures density contrast exists within a district — a landmark multi-block structure is always adjacent to smaller-scale blocks, creating the density inversion Ozzie wants. This must be a *guarantee*, not a coincidence.
|
||||
|
||||
**Generator rule I'm adding:** Adjacent blocks in a district must not all have the same density tier. The block planning stage must enforce density alternation — if block (2,2) is high-density, at least one of its 4 orthogonal neighbors must be medium or low density.
|
||||
|
||||
---
|
||||
|
||||
## 9. Minimum Viable District — Multi-Playstyle Version
|
||||
|
||||
Round 1's MVD was investigation-scoped. Here's the universal MVD that serves all four playstyles simultaneously:
|
||||
|
||||
| Archetype | Physical form | Inv | Tycoon | Social | Politics |
|
||||
|---|---|---|---|---|---|
|
||||
| Traffic Chokepoint | Transit node or architectural corridor | ✓ | ✓ | ✓ | ✓ |
|
||||
| Informal Zone | Maintenance spine or off-route passage | ✓ | ✓ | ✓ | ✓ |
|
||||
| Social Hub | Bar or community gathering space | ✓ | ✓ | ✓ | ✓ |
|
||||
| Institutional Space | Authority access zone | ✓ | ✓ | — | ✓ |
|
||||
| Insider Space | Workplace or community insider zone | ✓ | ✓ | ✓ | ✓ |
|
||||
| Economic Node | Market or logistics point | — | ✓ | — | ✓ |
|
||||
| Encounter Corridor | Promenade or transit route | ✓ | — | ✓ | ✓ |
|
||||
| Plus: ≥2 active social triangles | Seeded social conflict | ✓ | ✓ | ✓ | ✓ |
|
||||
| Plus: ≥1 temporal encounter window | Day-phase social peak | — | — | ✓ | — |
|
||||
| Plus: ≥1 power gradient signal | Visible authority NPC | ✓ | — | — | ✓ |
|
||||
| Plus: ≥1 economic asymmetry signal | Demand/price gap | — | ✓ | — | ✓ |
|
||||
| Plus: Density contrast (adjacent blocks ≠ same tier) | Visual rhythm | ✓ | ✓ | ✓ | ✓ |
|
||||
|
||||
**Count:** 7 archetypes + 4 additional guarantees = 11 total checks in the guarantee audit. If a generated district passes all 11, it is playable for all four primary playstyles.
|
||||
|
||||
---
|
||||
|
||||
## 10. Summary: Pipeline Stages with Multi-Playstyle Outputs
|
||||
|
||||
```
|
||||
[Pre-pipeline]
|
||||
World Significance Tier + Setting Geometry → governs how much generation runs
|
||||
Society Profile (Miri's 6-category ingredients) → feeds everything downstream
|
||||
|
||||
[PHASE 1 — Background Prep]
|
||||
Stage 1: Society Profile Assembly
|
||||
Outputs: privacy_level, trust.building_rate, economic_pressure, meridian_coverage_baseline,
|
||||
active_phases, cultural_tags, faction_presence_tiers
|
||||
Serves: All playstyles (cultural foundation)
|
||||
|
||||
Stage 2: District Skeleton Generation
|
||||
Outputs: DistrictSkeleton + guarantee audit (11 checks passed)
|
||||
Includes: historical event modifier pass, LShape causes, cross-district edge connections
|
||||
Serves: Investigation (surveillance topology), Tycoon (economic nodes),
|
||||
Social (encounter windows), Politics (power gradient)
|
||||
|
||||
Stage 3: Block Planning
|
||||
Outputs: 16 BlockSkeletons + edge contracts + era tags with causes + landmark slots
|
||||
Includes: density alternation enforcement, flavor structure pre-assignment
|
||||
Serves: All playstyles (physical navigation, density contrast)
|
||||
|
||||
[PHASE 2 — Local Area, On-Demand]
|
||||
Stage 4: Chunk Fill
|
||||
Outputs: Tile data, applied flavor structures with NPC-affinity tags
|
||||
Includes: zone palette, era materials, LOS anchors, facade variation
|
||||
Serves: All playstyles (spatial readability)
|
||||
|
||||
Stage 5: NPC Instantiation
|
||||
Outputs: Generated NPCs with 10-axis configs, role assignments, triangles
|
||||
Includes: NPC affinity from flavor structure types, society profile behavioral mods
|
||||
Serves: All playstyles (the human texture)
|
||||
|
||||
Stage 6: Scenario Instantiation
|
||||
Outputs: Active scenarios, entanglement pattern, evidence placement,
|
||||
economic asymmetry specifics, political power topology
|
||||
Serves: Investigation (entanglement), Tycoon (economic opportunity),
|
||||
Social (relationship status), Politics (power configuration)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Open Questions for Round 3
|
||||
|
||||
### OQ-R2-A: Triangle Purpose Taxonomy
|
||||
I proposed `triangle_purpose: TrianglePurpose` (investigation/economic/political/social) in `SocialSitePlacement`. This needs formal definition. Tyre to confirm whether this adds implementation complexity, or whether the existing `TriangleTemplate` struct can carry purpose tags.
|
||||
|
||||
### OQ-R2-B: Guarantee Audit as Formal Schema
|
||||
The 11-check guarantee audit I've proposed should become a validated runtime check, not just a design document item. Every generated `DistrictSkeleton` should serialize its audit results. This supports validation (Tyre's test fixture proposal) and debugging.
|
||||
|
||||
### OQ-R2-C: Maritime and Wilderness Navigation
|
||||
The Traffic Chokepoint archetype works for all setting geometries. But for wilderness/maritime, the "informal zone" mechanic changes — there's no "maintenance corridor." What's the wilderness equivalent? I propose `terrain_informal_zone` (a geography-defined sheltered space — cave, ravine, hidden cove) that satisfies the same guarantee. This needs Miri's input on what wilderness informal zones look like culturally.
|
||||
|
||||
---
|
||||
|
||||
*Gestalt — Round 2 complete. Position is clear: the 7 archetypes serve all four playstyles simultaneously; the pipeline is two-phase with a 6-stage internal structure; population ordering question is resolved (sequential, no feedback loop); the guarantee audit is the enforcement mechanism. Ready for Round 3 convergence.*
|
||||
@@ -0,0 +1,870 @@
|
||||
# Generator Architecture Workshop — Round 3: Gestalt
|
||||
|
||||
**Role:** Systems Design / Gameplay Loop Requirements
|
||||
**Date:** 2026-02-27
|
||||
**Workshop:** Generator Architecture (#562)
|
||||
|
||||
---
|
||||
|
||||
## Framing: Five Lead Directives, One Convergence Document
|
||||
|
||||
Round 3 has five directives: assassin playstyle, universal-vs-conditional guarantees, destructible boundaries, vertical scale, and dynamic world modification. Plus Qatux flagged my own open question (OQ-R3-B: triangle purpose taxonomy) and three overlapping tier concepts that need reconciliation.
|
||||
|
||||
Let me break down what each directive means mechanically, then converge everything into a final pipeline statement.
|
||||
|
||||
---
|
||||
|
||||
## 1. Assassin as Full Playstyle Lens
|
||||
|
||||
Let me map the assassin's spatial needs before touching the archetype table.
|
||||
|
||||
**What the assassin is doing:** Pre-operation intelligence gathering → position staging → execution window → egress. The assassin is an investigator first (must understand the target's pattern), a political actor second (understands whose contract they're operating under), and a precision combatant third. Mechanically, what distinguishes assassination from combat is **timing and position** — not "can I win a fight" but "can I be in this specific place at this specific moment without being observed."
|
||||
|
||||
**The assassin's unique spatial requirements:**
|
||||
|
||||
| Need | Spatial expression |
|
||||
|---|---|
|
||||
| **Elevated vantage** | Any position 1+ z-level above a Traffic Chokepoint with LOS coverage downward |
|
||||
| **Timing window** | A period in the target area's day-cycle when NPC/observer density is reduced enough to act |
|
||||
| **Crowd anonymity** | A Social Hub or Encounter Corridor dense enough to blend into — the assassin needs to be unremarkable |
|
||||
| **Staging approach** | A route from the district edge to the target area that avoids institutional observation (Meridian dead zones, maintenance routes, unofficial paths) |
|
||||
| **Egress multiplicity** | At least 2 independent exits from the target zone that don't share a chokepoint — if one is cut off, the other remains |
|
||||
| **Pattern intelligence** | NPCs on the target's known route — the assassin needs to confirm the schedule before committing |
|
||||
| **No-door access route** | At least one path to the target area that doesn't pass through an institutional access point (guard, checkpoint, scan) |
|
||||
|
||||
Now the updated archetype table. Notice how every assassination archetype serves multiple playstyles — the SPACE is universal, only the USE differs.
|
||||
|
||||
### 1.1 Updated 7 Spatial Archetypes — Assassin Column Added
|
||||
|
||||
| Archetype | Definition | Investigation | Tycoon | Dating Sim | Political | **Assassin** |
|
||||
|---|---|---|---|---|---|---|
|
||||
| **Traffic Chokepoint** | Spatial bottleneck where significant NPC flow passes and is observable from an adjacent fixed position | Surveillance of movements and tells | Control trade flow leverage | Serendipitous encounter, reliable find | Campaign/voter presence, speechmaking | **Timing window — target must pass here; vantage position nearby** |
|
||||
| **Informal Zone** | Degraded institutional coverage, low ambient traffic, unofficial use — grey-area space | Quiet zone, dead drops, private meetings | Grey market exchange, unofficial trade | Private encounter, trysts, earned intimacy | Back-channel negotiation, away from faction eyes | **Staging approach — pre-op cache, equipment stash, unobserved waiting position** |
|
||||
| **Social Hub** | High NPC density, social mixing, multiple access tiers coexisting — the gathering place | Rapport building, information gathering | Networking, trade leads, rumor sourcing | Romance venue, repeated encounter, public ritual | Influence gathering, political reading | **Crowd cover — anonymity in numbers, pattern intelligence via overhearing** |
|
||||
| **Institutional Space** | Formal authority presence, access-tier-enforced, zone palette signals power | Official authority access, procedural leverage | Licensing, formal contracts, regulatory navigation | Formal encounter context (job interviews, official appointments) | Power center — the space authority controls | **Security architecture to map, access points to exploit or avoid** |
|
||||
| **Insider Space** | Non-public access, social proof required, closed group — the back room | Ring access visibility, trusted social network | Guild/cooperative insider, exclusive supplier | Close friend group, earned intimacy depth | Faction HQ, party inner circle | **Target's personal protection circle — and the gap where access might exist** |
|
||||
| **Economic Node** | Visible economic activity, pricing signals, transaction infrastructure | Evidence trail — money follows crime | Primary profit opportunity, arbitrage, deals | Shared activity creates natural encounter | Economic leverage over actors who control resources | **Contract source (who pays for the job), payment receipt, opposition's funding** |
|
||||
| **Encounter Corridor** | NPC daily movement route, traversal path, corridor with observable foot traffic | NPC observation, pattern detection | Supply chain visibility, route control | Daily routine overlap — the street the target of affection always takes at 3pm | Visibility territory, patrol routes, political march | **Target's known route — where and when they're predictably exposed** |
|
||||
|
||||
### 1.2 Assassin-Specific Spatial Guarantees
|
||||
|
||||
The 7 archetypes don't fully cover assassin requirements. The assassin needs additional guarantees that don't reduce to any single archetype:
|
||||
|
||||
**Guarantee A-1: Elevated Vantage Position**
|
||||
Every Full-complexity district must contain at least one position at z-level 1 or above that has a clear LOS cone covering the primary Traffic Chokepoint. This isn't a separate zone — it's a spatial property of the chokepoint's adjacent structures.
|
||||
|
||||
*Other playstyle uses:* Investigation (counter-surveillance vantage), Dating Sim (Ozzie's "vertical surprise — going up when I didn't expect to"), Political (speaking platform that can address the crowd).
|
||||
|
||||
**Guarantee A-2: Egress Multiplicity**
|
||||
Every Entry Point (district boundary access point) must have at least 2 independent egress routes that don't share a secondary chokepoint. "Independent" means: if one route is blocked by an NPC or physical obstacle, the other remains viable. This is a connectivity property of the Encounter Corridor graph.
|
||||
|
||||
*Other playstyle uses:* Investigation (if blown, you need another way out), Tycoon (redundant supply routes), Dating Sim (the ability to leave a scene with dignity).
|
||||
|
||||
**Guarantee A-3: Temporal Opacity Window**
|
||||
Every Full-complexity district must have at least one period in each day-cycle (D-031) where the Traffic Chokepoint's observer density drops below the "crowd cover" threshold. Mechanically: a phase of the day when the chokepoint has fewer than N active NPCs in observation range. This is determined at NPC schedule generation time.
|
||||
|
||||
*Other playstyle uses:* Investigation (dead-of-night investigation access), Tycoon (off-hours deals), Dating Sim (Ozzie's ritual gathering — you know they'll be here at 3pm).
|
||||
|
||||
**Guarantee A-4: Non-Institutional Access Route**
|
||||
At least one path from district entry to the Social Hub must not pass through any access tier above `Semi-Private`. A district where every path to the gathering place requires crossing an institutional checkpoint is inhospitable to any non-credentialed character — including all playstyles.
|
||||
|
||||
*Note: This is not assassination-specific — it's an accessibility guarantee that serves all non-credentialed characters.*
|
||||
|
||||
---
|
||||
|
||||
## 2. Universal vs. Conditional Guarantee System
|
||||
|
||||
**The lead directive correction:** Not every place serves every playstyle. A farmstead doesn't need an assassination sightline.
|
||||
|
||||
This is the right correction, and it resolves a design tension that's been implicit since Round 1. Let me build the formal model.
|
||||
|
||||
### 2.1 Two-Axis Classification
|
||||
|
||||
Every guarantee is classified on two axes:
|
||||
|
||||
**Axis 1: District Complexity Threshold**
|
||||
- `Universal`: applies to all inhabited districts regardless of complexity
|
||||
- `Full-only`: applies to Full-complexity districts only
|
||||
- `Conditional`: applies when specific district parameters (terrain, drama density, playstyle context) warrant
|
||||
|
||||
**Axis 2: Terrain-Agnostic vs. Terrain-Aware**
|
||||
- `Terrain-agnostic`: the guarantee applies regardless of setting geometry (Station/Urban/Rural/Maritime/etc.)
|
||||
- `Terrain-aware`: the guarantee has a terrain-specific expression (a Traffic Chokepoint in wilderness is a mountain pass, not a corridor)
|
||||
|
||||
### 2.2 The Guarantee Tiers
|
||||
|
||||
**TIER 1: Universal Guarantees (all inhabited districts, any terrain)**
|
||||
|
||||
These are not negotiable even for a Minimal-complexity backwater. They're properties of any space where humans live and move.
|
||||
|
||||
| Guarantee | Rationale | Terrain expression |
|
||||
|---|---|---|
|
||||
| **Social Hub** | Any inhabited space has somewhere people gather. Even in a village of 8. | Station: bar. Urban: market. Rural: community hall. Maritime: the dock. Wilderness: the campfire. |
|
||||
| **Informal Zone** | Everywhere humans live has grey-area space that institutional authority doesn't fully penetrate. | Station: maintenance corridor. Urban: back alley. Rural: the back of the barn. Maritime: below deck. Wilderness: the whole thing. |
|
||||
| **Encounter Corridor** | Any connected place has routes people use regularly. Even a settlement of 8 has a path people walk every day. | Station: main transit spine. Urban: high street. Rural: farm road. Maritime: the dock approach. Wilderness: trail. |
|
||||
|
||||
**TIER 2: Full-Complexity Guarantees (Full-complexity districts, terrain-aware)**
|
||||
|
||||
These require sufficient population and infrastructure to produce. They apply when the district is at `ComplexityTier::Full`.
|
||||
|
||||
| Guarantee | Terrain-agnostic? | Terrain-specific expression where needed |
|
||||
|---|---|---|
|
||||
| **Traffic Chokepoint** | Terrain-aware | Urban/Station: architectural chokepoint. Non-urban: natural bottleneck (mountain pass, harbor mouth, river ford, valley entrance) |
|
||||
| **Institutional Space** | Terrain-aware | Urban/Station: formal building with access control. Rural: may be absent (zero faction presence) — if absent, the guarantee is waived. Wilderness: always absent. |
|
||||
| **Insider Space** | Terrain-agnostic | Exists wherever there is a community. The "insider" social geography scales with population size. |
|
||||
| **Economic Node** | Terrain-aware | Urban/Station: commercial infrastructure. Rural: market day, granary, water allocation point. Maritime: harbor trading post. Wilderness: resource extraction site (not economic in the formal sense — gameplay differently). |
|
||||
|
||||
**TIER 3: Conditional Guarantees (apply based on playstyle-context and complexity)**
|
||||
|
||||
These don't apply universally — they activate when the district's complexity, drama density, and active playstyle warrant them.
|
||||
|
||||
| Guarantee | Condition |
|
||||
|---|---|
|
||||
| **Elevated Vantage Position** (A-1) | Full-complexity districts with vertical architecture (z_levels ≥ 2) |
|
||||
| **Egress Multiplicity** (A-2) | Full-complexity districts; applies in all terrains but particularly critical in enclosed settings (Station, Maritime) |
|
||||
| **Temporal Opacity Window** (A-3) | Full-complexity districts with defined NPC schedules |
|
||||
| **Economic Asymmetry Signal** | Full-complexity districts with active economic function (not subsistence/transit-only) |
|
||||
| **Temporal Encounter Window** | Full-complexity districts with active social sites and D-031 day-phase integration |
|
||||
| **Power Gradient Visibility** | Full-complexity districts with faction presence tier ≥ Standard |
|
||||
|
||||
### 2.3 The Guarantee Audit: Conditional Logic
|
||||
|
||||
The 11-check audit from Round 2 needs to be conditional-aware. Here's the revised `GuaranteeAuditResult` logic:
|
||||
|
||||
```
|
||||
For each district being audited:
|
||||
|
||||
1. Check TIER 1 (all inhabited districts):
|
||||
[ ] Social Hub present?
|
||||
[ ] Informal Zone present?
|
||||
[ ] Encounter Corridor present?
|
||||
|
||||
2. IF complexity == Full OR Moderate:
|
||||
[ ] Traffic Chokepoint present (terrain-appropriate form)?
|
||||
[ ] Insider Space present?
|
||||
|
||||
3. IF complexity == Full:
|
||||
[ ] Institutional Space present? (WAIVED if faction_presence == Absent everywhere)
|
||||
[ ] Economic Node present? (WAIVED if economic_function == Subsistence or Wilderness)
|
||||
|
||||
4. IF complexity == Full AND z_levels >= 2:
|
||||
[ ] Elevated Vantage present?
|
||||
|
||||
5. IF complexity == Full AND npc_schedule_density >= threshold:
|
||||
[ ] Temporal Opacity Window exists in at least one day-phase?
|
||||
|
||||
6. IF complexity == Full AND faction_presence >= Standard:
|
||||
[ ] Power Gradient Visibility present?
|
||||
|
||||
7. IF complexity == Full AND economic_function is not Subsistence/Wilderness:
|
||||
[ ] Economic Asymmetry Signal present?
|
||||
```
|
||||
|
||||
A Minimal-complexity farmstead district: only 3 checks. A Full-complexity urban hub: up to 11 checks. The audit scales with district class. **No false positives from applying urban guarantees to rural fields.**
|
||||
|
||||
### 2.4 The Assassin Lens as a Superposition
|
||||
|
||||
Critically: the assassin doesn't add new spaces to the district. The assassin reads EXISTING spaces differently. The Encounter Corridor is the target's known route. The elevated position above the Traffic Chokepoint is the vantage. The Informal Zone is the staging ground.
|
||||
|
||||
The generator doesn't tag spaces "this is for assassins." The generator produces spaces with certain properties (elevated, overlooking chokepoint, low observation) and the assassin system identifies which spaces satisfy which operational requirements. This is Miri's core insight: one information landscape, multiple lenses.
|
||||
|
||||
**Implication for the guarantee audit:** The assassin-specific guarantees (A-1, A-2, A-3) are NOT new checks to add to the 11. They're DERIVED PROPERTIES from the existing spatial configuration. If the Full-complexity district has an elevated vantage position, the assassin has a sightline. If it has route multiplicity, the assassin has egress options. The audit verifies spatial properties; the playstyle systems decide what to do with them.
|
||||
|
||||
---
|
||||
|
||||
## 3. Destructible Boundaries: Generator Rules
|
||||
|
||||
**The question:** What does the generator put behind a wall the player destroys?
|
||||
|
||||
Let me break down what "behind a wall" means in a top-down tile grid.
|
||||
|
||||
### 3.1 Every Tile Is Pre-Generated
|
||||
|
||||
The key insight: the game is a top-down tile grid. A 64×64 chunk is a 64×64 array of tiles. Every tile in that array exists in the data structure, whether the player has access to it or not. **Walls don't create holes in the tile data — they're a tile TYPE that prevents movement and blocks LOS.**
|
||||
|
||||
"What's behind the wall" is already in the `ChunkData`. The question is what TILE TYPE is on the far side of the wall tile, and whether that tile has ever been prepared for player-facing content.
|
||||
|
||||
### 3.2 Three Wall-Behind States
|
||||
|
||||
The generator must produce one of three states for every wall tile's reverse side:
|
||||
|
||||
**Type 1: Structural Void**
|
||||
`TileBehind::StructuralFill`
|
||||
|
||||
Behind this wall is structural material — load-bearing concrete, pressure bulkhead, foundation column. No space. Blowing it open produces rubble and debris at best; at worst, triggers a structural instability cascade affecting adjacent tiles.
|
||||
|
||||
Generator marks these at block planning time based on building structural logic:
|
||||
- Outer walls of a building are always `StructuralFill` on the exterior face
|
||||
- Load-bearing interior walls (every 4th wall in a standard building grid) are `StructuralFill`
|
||||
- Pressure bulkheads in station environments (separating pressurized from vacuum) are `StructuralFill`
|
||||
|
||||
```rust
|
||||
enum TileBehindState {
|
||||
/// No space. Structural material. Breaching is possible but triggers consequences.
|
||||
StructuralFill {
|
||||
material: StructuralMaterial,
|
||||
stability_impact: f32, // how much this wall contributes to building structural integrity
|
||||
},
|
||||
/// A real room that has no player-facing access route in normal play.
|
||||
/// Pre-generated at chunk fill time with full content (may be empty, may have contents).
|
||||
HiddenRoom {
|
||||
room_seed: u64,
|
||||
fill_tag: String, // what kind of room this is
|
||||
},
|
||||
/// Small interstitial void between structures — not a room, not structural.
|
||||
/// A 1-4 tile gap. Can be entered, has nothing in it.
|
||||
Interstitial {
|
||||
width_tiles: u8,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Type 2: Hidden Room**
|
||||
`TileBehind::HiddenRoom`
|
||||
|
||||
Behind this wall is a real room that the chunk fill has generated as a complete space — floor, potential contents, potential NPC spawn — but which has no door or access point opening onto the player's side. The room is pre-generated whether the player ever reaches it or not (idempotent from seed).
|
||||
|
||||
This is the mechanical foundation for Ozzie's "place I'm not supposed to be." The generator guarantees:
|
||||
|
||||
> **Every Full-complexity district must contain at least one Hidden Room zone accessible only via breach (no door, no vent, no official access point on the player's side of the wall).**
|
||||
|
||||
Generator implementation:
|
||||
- At chunk fill time, `access_point_count: u8 = 0` marks a `ChunkFillSpec` as breach-only
|
||||
- These are generated with full content — they're rooms that happen to have no door
|
||||
- Contents are seeded based on the room's social context: a private office adjacent to a Institutional Space might contain files; a storage room adjacent to an Informal Zone might contain contraband; a maintenance junction might contain infrastructure controls
|
||||
|
||||
Hidden rooms are how the generator produces secrets that aren't marked "this is a secret." They're just rooms without obvious doors. Players who blow through walls find them; players who don't, don't. No quest marker. Just space with contents and no apparent route.
|
||||
|
||||
**Type 3: Interstitial Void**
|
||||
`TileBehind::Interstitial`
|
||||
|
||||
The gap between two buildings that's 1-4 tiles wide — not a room, not structural. These are produced by the anti-grid techniques (Araminta's setback variation, irregular building footprints). They have nothing in them by default, though NPC procedural placement might use them as informal routes.
|
||||
|
||||
### 3.3 The Player-Facing Contract
|
||||
|
||||
The generator's destructible boundary contract:
|
||||
|
||||
1. **All tile data pre-exists.** Chunk fill generates EVERYTHING in the chunk, including rooms with no player-facing access points. Breaching a wall doesn't require on-the-fly generation.
|
||||
|
||||
2. **Structural walls are marked.** `TileFlag::LoadBearing` identifies walls that cause structural cascades when destroyed. The gameplay system reads this flag to determine breach consequences.
|
||||
|
||||
3. **Hidden rooms have real content.** The generator doesn't produce empty "dead space" behind walls (Ozzie's Generation Sin #3). If the space exists, it pays rent — even if rent is "a small maintenance junction with spare parts and a scratched message someone left on the wall."
|
||||
|
||||
4. **Breach access is a first-class access tier.** `AccessTier::BreachOnly` — a space that requires destructive entry. This is distinct from `Restricted` (which is passable with credentials) and `Insider` (which is passable with social trust).
|
||||
|
||||
### 3.4 Guarantee: At Least One Breach-Only Space per Full District
|
||||
|
||||
> **Guarantee: Every Full-complexity district must contain at least one zone classified `AccessTier::BreachOnly` — a space that has no non-destructive access route.**
|
||||
|
||||
This is the generator's structural implementation of Ozzie's Round 1 demand: "I need to find somewhere that feels like I wasn't meant to find it." The generator doesn't flag these as "secrets." It just makes rooms without doors and leaves the player to find them.
|
||||
|
||||
---
|
||||
|
||||
## 4. Vertical Scale: Multi-Z Architecture
|
||||
|
||||
**The question:** How does a 50-floor skyscraper emerge from the generator? The current 3 z-level model works for station environments. What about planet-side cities with tall buildings?
|
||||
|
||||
### 4.1 What Vertical Means in a Top-Down Game
|
||||
|
||||
First, the mechanical reality: this is a top-down 2D game. The player never sees a cross-section of a building. They see ONE horizontal slice at a time — the floor they're on. Transitioning between floors means traversing a stairwell or lift (which is effectively a corridor connecting two separate map states).
|
||||
|
||||
So "50 floors" doesn't mean the player sees 50 floors simultaneously. It means there are 50 distinct horizontal-slice states the player can transition between via vertical corridors. Each floor is a 2D map. Vertical scale is about the NUMBER of distinct horizontal states and the CONNECTIVITY between them.
|
||||
|
||||
### 4.2 Z-Bands: Grouping Floors into Generator Units
|
||||
|
||||
The generator doesn't need to model every floor independently at the skeleton stage. It models **z-bands** — groups of floors with similar social function. The social content changes at z-band boundaries, not floor-by-floor.
|
||||
|
||||
A 50-floor skyscraper might have 4 z-bands:
|
||||
- **Z-band 0** (floors 1-5): Ground-level commercial/public. Full public access.
|
||||
- **Z-band 1** (floors 6-20): Office/institutional. Semi-private access tier.
|
||||
- **Z-band 2** (floors 21-45): Upper office/restricted functions. Insider/restricted access tier.
|
||||
- **Z-band 3** (floors 46-50): Executive/penthouse. Restricted/breach-only access tier.
|
||||
|
||||
Each z-band is generated as an independent horizontal layer with its own:
|
||||
- `ZoneType` and zone palette
|
||||
- Access tier (the vertical gradient runs highest-to-most-restricted as you go up)
|
||||
- NPC population subset from the district roster
|
||||
- Social sites (a z-band 0 might have a lobby bar; z-band 2 might have a boardroom social site)
|
||||
- Internal floor layout (Phase 2 chunk fill generates each floor's tiles)
|
||||
|
||||
### 4.3 Multi-Block Reservation for Tall Structures
|
||||
|
||||
Tall buildings use `MultiBlockReservation` — the same mechanism that handles large horizontal structures — extended to include `vertical_extent`:
|
||||
|
||||
```rust
|
||||
struct MultiBlockReservation {
|
||||
/// Which blocks this structure occupies (horizontal footprint)
|
||||
block_coords: Vec<(u8, u8)>,
|
||||
|
||||
/// NEW: Vertical extent in z-bands
|
||||
z_band_count: u8,
|
||||
|
||||
/// NEW: Height in visual/sim floors (for rendering and collision)
|
||||
floor_count: u8,
|
||||
|
||||
/// Structure type
|
||||
structure_type: MultiBlockStructureType,
|
||||
|
||||
/// Social site hosted (may span multiple z-bands)
|
||||
hosted_sites: Vec<SocialSiteId>,
|
||||
|
||||
/// NEW: Per-z-band zone assignment
|
||||
z_band_zones: Vec<ZoneDefinition>,
|
||||
|
||||
/// NEW: Vertical corridor spines (lifts, stairs, shafts)
|
||||
vertical_corridors: Vec<VerticalCorridorSpec>,
|
||||
}
|
||||
|
||||
struct VerticalCorridorSpec {
|
||||
/// Which blocks contain this corridor (may be 1 block or span multiple)
|
||||
block_coords: Vec<(u8, u8)>,
|
||||
|
||||
/// Which z-bands this corridor connects
|
||||
z_bands_connected: Vec<u8>,
|
||||
|
||||
/// Access tier required to use this corridor
|
||||
access_tier: AccessTier,
|
||||
|
||||
/// Type (lift, stairwell, service shaft, emergency escape)
|
||||
corridor_type: VerticalCorridorType,
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 Vertical Access Tier Gradient
|
||||
|
||||
The access tier gradient that Araminta defined running inward from street face — public → semi-private → restricted — has a VERTICAL analog:
|
||||
|
||||
**Vertical gradient:** ground level = most public; upper levels = most restricted
|
||||
|
||||
This is a spatial law that players understand intuitively (penthouses are exclusive; lobbies are open). The generator enforces it: `AccessTier` must be monotonically non-decreasing as z-band index increases, with at least one tier step between z-bands.
|
||||
|
||||
The bottom z-band can be `Public`. The top z-band will typically be `Restricted` or `BreachOnly`.
|
||||
|
||||
**Assassin implication:** Getting to the top of a tall building is an access-tier challenge. The vertical corridor is a chokepoint. The lift is a bottleneck. The stairwell is monitored. Getting UP is the operational problem, more than getting to the target once you're there.
|
||||
|
||||
### 4.5 The Roof as Mandatory Discovery Zone
|
||||
|
||||
> **Guarantee: Every tall structure (z_band_count ≥ 3) must have a roof zone classified `AccessTier::Insider` or `AccessTier::BreachOnly` — accessible by a non-obvious route.**
|
||||
|
||||
The roof isn't a separate district. It's the top of the `MultiBlockReservation`, generated as an additional z-band with open-sky tile properties. The roof:
|
||||
- Has dramatically extended LOS (no walls, elevated position over surrounding blocks)
|
||||
- Is the highest vantage point in the district
|
||||
- Has no official occupants (its own `HiddenRoom` equivalent at building scale)
|
||||
- Must be reachable — but the route is non-obvious (service access, emergency hatch, window ledge)
|
||||
|
||||
The roof is Ozzie's "vertical surprise that reorients my mental map" at building scale.
|
||||
|
||||
### 4.6 What Stays the Same
|
||||
|
||||
The 4-level spatial hierarchy (chunk/block/district/system) doesn't change. Tall buildings are multi-block, multi-z-band structures within an existing district. The chunk size (64×64 sim tiles) doesn't change — each floor of a building is composed of chunks. The streaming model doesn't change — the player's 3×3 loading grid operates in the current z-band, with adjacent z-bands cached.
|
||||
|
||||
---
|
||||
|
||||
## 5. Dynamic World Modification: Delta Layer Model
|
||||
|
||||
**The question:** Gas main explosion in a district the player already visited. Should the generator re-render affected chunks?
|
||||
|
||||
### 5.1 Two Sources of World State
|
||||
|
||||
The core architectural distinction:
|
||||
|
||||
- **Generator State**: The seed-derived, deterministic foundation. `PreparedDistrict` + `ChunkData`. This is IMMUTABLE after generation. Its determinism guarantee is the game's foundation.
|
||||
- **World State Deltas**: Post-generation modifications. Events, player actions, gameplay consequences. These live in a separate structure layered on top of Generator State.
|
||||
|
||||
The gas explosion doesn't change what the generator produced. It creates a `WorldStateDelta` that describes the explosion's effect. When rendering or gameplay processes chunk (x,y), it applies:
|
||||
|
||||
1. Generator `ChunkData` (base state, always deterministic from seed)
|
||||
2. All `WorldStateDelta` entries for this chunk (ordered by tick timestamp)
|
||||
|
||||
Result: the chunk looks like the generated version plus the applied deltas. **The generator never re-runs. The delta layer carries the modification.**
|
||||
|
||||
### 5.2 The Delta Structure
|
||||
|
||||
```rust
|
||||
struct WorldStateDelta {
|
||||
/// Which chunk this delta affects
|
||||
chunk: ChunkCoords,
|
||||
|
||||
/// When this happened (simulation tick)
|
||||
timestamp: SimTick,
|
||||
|
||||
/// What changed
|
||||
delta_type: DeltaType,
|
||||
|
||||
/// Source of the change (gameplay consequence, NPC action, Tier 1 module event, etc.)
|
||||
source: DeltaSource,
|
||||
}
|
||||
|
||||
enum DeltaType {
|
||||
/// Structural damage from explosion, combat, decay
|
||||
StructuralDamage {
|
||||
tiles: Vec<TileCoord>,
|
||||
damage_level: DamageLevel, // Scorched, Damaged, Destroyed, Collapsed
|
||||
},
|
||||
|
||||
/// A wall has been breached (player or NPC action, explosion)
|
||||
WallBreached {
|
||||
wall_tile: TileCoord,
|
||||
breach_type: BreachType, // Blown, Forced, Cut
|
||||
},
|
||||
|
||||
/// A door's state has changed persistently
|
||||
DoorStateChanged {
|
||||
door_id: DoorId,
|
||||
state: DoorState, // Open, Closed, Locked, Breached, Destroyed
|
||||
},
|
||||
|
||||
/// An object has been added or removed
|
||||
ObjectModified {
|
||||
position: TileCoord,
|
||||
modification: ObjectModification, // Added, Removed, Damaged, Moved
|
||||
object_id: ObjectId,
|
||||
},
|
||||
|
||||
/// A tile's traversability changed (collapse reveals new space, explosion creates gap)
|
||||
TileTypeChanged {
|
||||
position: TileCoord,
|
||||
new_type: TileType,
|
||||
},
|
||||
|
||||
/// An access tier changed due to gameplay events (lockdown, faction capture)
|
||||
AccessTierChanged {
|
||||
zone: ZoneId,
|
||||
new_tier: AccessTier,
|
||||
expires_at: Option<SimTick>, // NULL = permanent
|
||||
},
|
||||
|
||||
/// An NPC position has been permanently altered (killed, arrested, moved away)
|
||||
NpcRemoved {
|
||||
npc_id: NpcId,
|
||||
reason: NpcRemovalReason,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 Handling Large-Scale Events
|
||||
|
||||
For minor events (one gas explosion, a door being kicked in): the `WorldStateDelta` layer handles it cleanly. Tens to hundreds of tile modifications. Lightweight.
|
||||
|
||||
For major events (fire that guts an entire block, a faction takeover that completely rebuilds a zone): the delta layer becomes expensive. Many thousands of tile modifications. For these cases:
|
||||
|
||||
**Soft Re-Generation**: Re-run Phase 2 for affected chunks with an event-modified seed:
|
||||
|
||||
```
|
||||
event_chunk_seed = original_chunk_seed XOR event_seed
|
||||
```
|
||||
|
||||
This produces a consistent, deterministic "post-event" state. The new chunk state is:
|
||||
- Different from the generator's original output (the event happened)
|
||||
- Still deterministic (reproducible from seed + event record)
|
||||
- Cached and saved as a new `ChunkData` snapshot
|
||||
|
||||
The save file maintains a record of which chunks have been "soft re-generated" and their event-modified seeds. This preserves:
|
||||
- **Determinism**: same events → same post-event state
|
||||
- **Persistence**: player returns to find the same damage
|
||||
- **Memory**: the player can understand what was there before (NPCs remember; environmental evidence remains)
|
||||
|
||||
### 5.4 World Modification as Gameplay Consequence
|
||||
|
||||
The delta layer is not just for disaster events. It handles the full range of world modification:
|
||||
|
||||
| Player action | Delta type |
|
||||
|---|---|
|
||||
| Kick down a door | `WallBreached` or `DoorStateChanged` |
|
||||
| Kill an NPC | `NpcRemoved` |
|
||||
| Plant evidence | `ObjectModified::Added` |
|
||||
| Cause an explosion | `StructuralDamage` + `WallBreached` x N |
|
||||
| Commission faction clears a district | `AccessTierChanged` (district-wide) |
|
||||
| Smuggling ring abandons a stash location | `ObjectModified::Removed` x N + `AccessTierChanged` |
|
||||
|
||||
The game's consequence systems write `WorldStateDelta` entries. The rendering and gameplay systems read them when processing chunks. **The generator never needs to know that any of this happened.**
|
||||
|
||||
### 5.5 What the Generator Guarantees vs. What the Delta Layer Guarantees
|
||||
|
||||
| Property | Generator guarantees | Delta layer maintains |
|
||||
|---|---|---|
|
||||
| Spatial structure | Always available | May be modified by events |
|
||||
| NPC roster | Generated deterministically | May shrink as NPCs are removed |
|
||||
| Access tiers | Defined by district skeleton | May change due to faction events |
|
||||
| Room contents | Generated at chunk fill time | May be modified by player/NPC actions |
|
||||
| Tile data | Deterministic from seed | May be overwritten by delta events |
|
||||
|
||||
The generator produces the world as it was. The delta layer describes what has happened to it since. The player experiences the composition.
|
||||
|
||||
---
|
||||
|
||||
## 6. Reconcile SignificanceTier / ComplexityTier / DramaDensity
|
||||
|
||||
Qatux correctly flagged these three overlapping concepts. My Round 2 `SignificanceTier` introduced a redundancy. Here is the clean two-parameter model.
|
||||
|
||||
### 6.1 The Two-Parameter Model
|
||||
|
||||
**I am retiring `SignificanceTier`.** It conflates two orthogonal properties and creates confusion with `ComplexityTier`. Here's the correct model:
|
||||
|
||||
| Parameter | Type | When Set | What It Controls |
|
||||
|---|---|---|---|
|
||||
| **`ComplexityTier`** | Static (generator) | Phase 1 Pre-Pipeline | CAPACITY: what the generator produces |
|
||||
| **`DramaDensity`** | Dynamic (storyteller) | Gameplay, Storyteller | UTILIZATION: what the Storyteller fires |
|
||||
|
||||
These answer different questions:
|
||||
- `ComplexityTier` answers: "What kind of district is this?"
|
||||
- `DramaDensity` answers: "What is happening in this district right now?"
|
||||
|
||||
### 6.2 ComplexityTier (Static, Generator-Determined)
|
||||
|
||||
```rust
|
||||
enum ComplexityTier {
|
||||
/// Full social architecture. 7+ archetypes. 20-80+ NPCs.
|
||||
/// All Tier 1 and Tier 2 guarantees apply.
|
||||
/// Supports: all playstyles at full depth.
|
||||
Full,
|
||||
|
||||
/// Moderate social architecture. 3-5 archetypes. 8-20 NPCs.
|
||||
/// Tier 1 guarantees + Traffic Chokepoint + Insider Space.
|
||||
/// Supports: all playstyles at reduced depth.
|
||||
Moderate,
|
||||
|
||||
/// Minimal social architecture. 1-2 archetypes. 1-8 NPCs.
|
||||
/// Tier 1 guarantees only.
|
||||
/// Supports: background world texture; Ozzie's "density contrast" filler.
|
||||
Minimal,
|
||||
|
||||
/// No social architecture. 0 archetypes. 0 NPCs.
|
||||
/// No guarantees. Pure terrain and traversal.
|
||||
Empty,
|
||||
}
|
||||
```
|
||||
|
||||
**ComplexityTier is determined at Phase 1 Stage 0 (Pre-Pipeline).** It's derived from:
|
||||
- World network position (hub nodes → Full; remote periphery → Minimal/Empty)
|
||||
- Setting geometry (Urban/Station → usually Full; Wilderness → usually Empty)
|
||||
- Storyteller pre-seeding (the Storyteller can override the default for a seed's purposes)
|
||||
|
||||
### 6.3 DramaDensity (Dynamic, Storyteller-Controlled)
|
||||
|
||||
```rust
|
||||
/// Drama Density: what the Storyteller is firing in this district right now.
|
||||
/// This is NOT a generator parameter — it's a runtime game state.
|
||||
enum DramaDensity {
|
||||
/// No Tier 1 modules active. Social fabric stable.
|
||||
/// Backwater guarantee: the Storyteller will not fire modules here.
|
||||
Zero,
|
||||
|
||||
/// One Tier 1 module active at low intensity, or mundane triangle fully active.
|
||||
Low,
|
||||
|
||||
/// One Tier 1 module + active mundane triangle pressure + economic tension.
|
||||
/// Sova Transit District in steady state.
|
||||
Medium,
|
||||
|
||||
/// Multiple modules active, contested faction presence, elevated pressure.
|
||||
High,
|
||||
|
||||
/// Maximum: multiple modules, faction conflict, historical disruption, elevated entanglement.
|
||||
/// Should be rare. Must feel rare. The Storyteller deploys this sparingly.
|
||||
Flashpoint,
|
||||
}
|
||||
```
|
||||
|
||||
### 6.4 The Critical Relationship: Capacity vs. Utilization
|
||||
|
||||
**The Storyteller cannot fire DramaDensity above the ComplexityTier's capacity ceiling.**
|
||||
|
||||
| ComplexityTier | Maximum DramaDensity | Why |
|
||||
|---|---|---|
|
||||
| Full | Flashpoint | Has the population density and social infrastructure to support it |
|
||||
| Moderate | High | Has enough NPCs for conflict, but not full-ring infrastructure |
|
||||
| Minimal | Low | Very few NPCs; even a single active module strains the social fabric |
|
||||
| Empty | Zero | No NPCs, no modules possible |
|
||||
|
||||
A `Minimal`-complexity village can have `Low` DramaDensity — a single personal drama, a domestic dispute with outsider consequences. It cannot have a full political crisis (no faction infrastructure) or a major smuggling ring (too few people to sustain it). The Storyteller respects this ceiling.
|
||||
|
||||
**The "false backwater" (Nigel's concept) is now expressible:**
|
||||
|
||||
> District: `ComplexityTier::Minimal`, `DramaDensity::Zero` — appears to be a quiet unremarkable stop.
|
||||
> BUT: the district is a node in a Tier 1 module (ring transit route) that the Storyteller has flagged as active but not yet surfaced in this location.
|
||||
> RESULT: The tycoon who investigates finds the module. The detective who passes through and doesn't look, doesn't. Same ComplexityTier. Same DramaDensity. Different player perception.
|
||||
|
||||
The false backwater doesn't require high complexity OR active drama. The Tier 1 module exists at a meta-level; the district itself is genuinely quiet. The module activates in response to the player's investigative actions, not as a property of the district.
|
||||
|
||||
### 6.5 Final Disposition
|
||||
|
||||
| Round 2 concept | Round 3 status |
|
||||
|---|---|
|
||||
| `SignificanceTier` (Gestalt) | **RETIRED.** Absorbed into ComplexityTier + DramaDensity + network position metadata |
|
||||
| `ComplexityTier` (Tyre) | **RETAINED.** Static, generator-determined capacity parameter |
|
||||
| `DramaDensity` (Nigel) | **PROMOTED.** Dynamic, storyteller-controlled utilization parameter — formally added to game state |
|
||||
|
||||
---
|
||||
|
||||
## 7. Triangle Purpose Taxonomy (OQ-R3-B: Resolved)
|
||||
|
||||
This is my open question from Round 2. Here's the resolution.
|
||||
|
||||
### 7.1 The Purpose Enum
|
||||
|
||||
```rust
|
||||
/// Why does this triangle exist, and which playstyle activates it?
|
||||
/// Note: a triangle can serve multiple purposes simultaneously.
|
||||
enum TrianglePurpose {
|
||||
/// Three NPCs in conflicting interests around hidden criminal/grey activity.
|
||||
/// Activated by: investigation-related Tier 1 modules, detective archetype engagement.
|
||||
Investigation,
|
||||
|
||||
/// Three NPCs in conflicting economic interests (market, trade, resources, contracts).
|
||||
/// Activated by: tycoon playstyle interaction, economic Tier 1 modules.
|
||||
Economic,
|
||||
|
||||
/// Three NPCs in romantic, family, or social competition.
|
||||
/// Activated by: dating sim playstyle interaction, social-drama modules.
|
||||
/// SAME mechanical structure as Investigation triangle — different content tags.
|
||||
Social,
|
||||
|
||||
/// Three NPCs in institutional power contest (positions, authority, faction allegiance).
|
||||
/// Activated by: political playstyle interaction, faction modules.
|
||||
Political,
|
||||
|
||||
/// Three NPCs structurally relevant to an assassination context:
|
||||
/// the target, their protector/guardian, and the informant or witness.
|
||||
/// Activated by: contract modules, assassination target proximity.
|
||||
Tactical,
|
||||
|
||||
/// Background social tension — never "activated" as player-facing primary drama.
|
||||
/// Workplace rivalries, family disputes, neighborhood dynamics.
|
||||
/// The 50% mundane from D-029. ALWAYS present in any inhabited district.
|
||||
/// Multiple playstyles can NOTICE these but they are not primary drama drivers.
|
||||
Mundane,
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 Rules for Triangle Composition per District
|
||||
|
||||
- Every Full-complexity district: at minimum 2 triangles, at least 1 `Mundane`
|
||||
- Every Full-complexity district: at minimum 1 non-Mundane triangle whose purpose matches the district's primary gameplay context (derived from district_type and society_profile)
|
||||
- Cross-template triangles (D-024): can span purposes (a `Social` triangle can have an `Economic` dimension — the romantic rival is also a business competitor)
|
||||
|
||||
### 7.3 Tactical Triangle and Assassination Gameplay
|
||||
|
||||
Every potential assassination target NPC is the central node of a `Tactical` triangle:
|
||||
- **Node 1 (Target)**: the NPC with the contract on them
|
||||
- **Node 2 (Protector)**: whoever guards/monitors/knows the target's movements — could be official security, a close friend, a suspicious colleague
|
||||
- **Node 3 (Informant/Witness)**: someone who has useful information about the target's pattern, OR someone who might witness and report the act
|
||||
|
||||
The generator guarantees: if a Tier 1 module with contract assassination potential is placed in a Full-complexity district, that district's triangle pool contains a `Tactical` triangle appropriately configured.
|
||||
|
||||
**Tyre's implementation question:** Does `TrianglePurpose` add meaningful complexity? My assessment: it's a simple `Vec<TrianglePurpose>` field on `TriangleTemplate`. The scenario instantiation system already needs to know which triangles to activate for a given module. This tag just makes that lookup explicit rather than inferential.
|
||||
|
||||
---
|
||||
|
||||
## 8. The Grid Breathing: A Gameplay Position
|
||||
|
||||
Ozzie is asking whether the block grid can rotate, whether streets can curve, whether two adjacent districts can have different orientations. This is primarily Tyre's technical question (D-094 is his to modify or defend). But from a gameplay systems perspective, here is my position:
|
||||
|
||||
**Araminta's seven anti-grid techniques are necessary but not sufficient.**
|
||||
|
||||
Here's why they're necessary: hiding the grid through visual means is cheap and effective for most players most of the time. Diagonal connectors, irregular setbacks, angled infrastructure, light territories — these work. I believe in them.
|
||||
|
||||
Here's why they're not sufficient alone: Ozzie will feel the skeleton. She's right. A systematic player who maps the district on paper will eventually see the 128m block grid. The visual camouflage produces "natural-feeling irregularity" within the grid, not "natural-feeling irregularity OF the grid."
|
||||
|
||||
**My recommendation to Tyre (for his Round 3):** The minimum viable intervention is not full non-rectilinear blocks — that's a major architectural change. The minimum viable intervention is:
|
||||
|
||||
1. **District-level rotation**: Allow districts to be placed at 45° to each other. The grid IS a grid, but adjacent districts can orient differently. A quarter-turn between two adjacent districts produces street angles that feel geological when the streets meet.
|
||||
|
||||
2. **Organic district edge**: Rather than a straight-line boundary between two districts, allow the boundary to follow a jagged line (within 1-2 block tolerance). The transition strip (Tyre's Round 2 solution) already gives us 2 boundary blocks of "neither district" — let that boundary zigzag rather than run straight.
|
||||
|
||||
These two interventions don't change the internal block grid. They change how grids MEET, which is where the visual seam is most dangerous. Ozzie is right that the seam will eventually show — but the seam appears most clearly at district boundaries, and that's what the transition strip is for.
|
||||
|
||||
**What I'm not asking Tyre to do:** Full WFC-style non-rectilinear districts with curved streets. That's V0.5+ territory if it's ever worth the implementation cost. The question for V0.1-V0.3 is whether the visual techniques plus boundary-level interventions get us to "player doesn't feel the grid on the 5th station." I believe they do with the boundary improvements.
|
||||
|
||||
---
|
||||
|
||||
## 9. Final Pipeline Architecture: Canonical Summary
|
||||
|
||||
Convergence from three rounds. This is the definitive pipeline statement.
|
||||
|
||||
```
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
PRE-PIPELINE (Static World Architecture)
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
|
||||
Master Seed (single, from Tyre §3)
|
||||
↓
|
||||
System Generation
|
||||
derives: star type, world count per system, gate connections
|
||||
↓
|
||||
Per-World Significance Assignment
|
||||
├── ComplexityTier: Full / Moderate / Minimal / Empty
|
||||
│ (derived from network position, setting geometry, world role)
|
||||
├── SettingGeometry: Station / Urban / Agricultural / Wilderness /
|
||||
│ Water / Transitional / Orbital
|
||||
└── DramaDensity ceiling: constrained by ComplexityTier
|
||||
(DramaDensity itself is set by Storyteller at runtime)
|
||||
|
||||
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
PHASE 1: WORLD PREP (Background, Async, ~50-500ms per district)
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
|
||||
Stage 1: Society Profile Assembly
|
||||
input: system_seed, world network position, SettingGeometry
|
||||
output: SocietyProfile (serde YAML → Rust struct, Tyre §4)
|
||||
produces: heritage blend, economic function/pressure, drift stage,
|
||||
faction presence, philosophical alignment, meridian coverage
|
||||
skipped for: Wilderness/Empty districts (no society)
|
||||
↓
|
||||
|
||||
Stage 2: District Skeleton Generation
|
||||
input: district_seed, SocietyProfile, ComplexityTier, SettingGeometry
|
||||
output: DistrictSkeleton (canonical struct — Tyre + Gestalt composite)
|
||||
produces:
|
||||
- Zoning (block types, access tiers)
|
||||
- Social site placement (D-025 templates, triangle topology)
|
||||
- Triangle pool (with TrianglePurpose tags — §7)
|
||||
- Multi-block reservations (horizontal + vertical extent — §4)
|
||||
- Vertical corridor spines (for tall structures — §4)
|
||||
- Corridor spines (Encounter Corridors + access points)
|
||||
- Zone palette assignment (Araminta's zone types)
|
||||
- Boundary descriptors (for edge bleed — Tyre §2)
|
||||
- Guarantee audit: conditional on ComplexityTier (§2)
|
||||
- Breach-only zones: at least 1 in Full-complexity (§3)
|
||||
↓
|
||||
VALIDATE spatial prerequisites (Tyre §6.1):
|
||||
check guarantees for this ComplexityTier + SettingGeometry
|
||||
adjust zoning if prerequisites not met
|
||||
check breach-only zone guarantee
|
||||
↓
|
||||
|
||||
Stage 3: Block Planning
|
||||
input: DistrictSkeleton, district_seed
|
||||
output: BlockPlan per block (Tyre §1.3)
|
||||
produces:
|
||||
- ChunkLayout (merge strategy, quarter layout)
|
||||
- Era assignment + era_modifications (with era_cause — §R1 resolved)
|
||||
- Edge contracts (Tyre's Option A)
|
||||
- Quarter form × function assignments (Araminta + Nigel composite)
|
||||
- TileBehindState for all walls in ChunkFillSpec (§3)
|
||||
- For tall structures: z_band_zones, z_band_access_tiers
|
||||
↓
|
||||
|
||||
Stage 4: NPC Population
|
||||
input: DistrictSkeleton (NPC role slots), district_seed
|
||||
output: NpcRoster (Tyre §1.1)
|
||||
produces:
|
||||
- 10-axis NPC generation per role slot
|
||||
- Triangle instantiation with TrianglePurpose (§7)
|
||||
- Entanglement marking (D-029: 20% entangled)
|
||||
- Spawn location preferences (flavor type → NPC pattern weight)
|
||||
- NPC schedule generation (DramaDensity-aware: opaque window timing — §1.2 A-3)
|
||||
↓
|
||||
|
||||
Stage 5: Transition Strip Generation
|
||||
input: adjacent PreparedDistrict pairs
|
||||
output: TransitionStrip per shared edge (Tyre §2.4)
|
||||
produces:
|
||||
- Palette blending (Araminta §1)
|
||||
- Access point alignment
|
||||
- Cultural bleed gradient (Miri §5)
|
||||
↓
|
||||
|
||||
OUTPUT: PreparedDistrict (DistrictSkeleton + BlockPlans + NpcRoster + SeedChain)
|
||||
TransitionStrips (shared between adjacent PreparedDistricts)
|
||||
|
||||
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
PHASE 2: LOCAL AREA GEN (On-Demand, Per Chunk, ~100-500ms)
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
|
||||
Player enters loading radius of chunk
|
||||
↓
|
||||
Chunk Fill (per chunk, derived chunk_seed)
|
||||
input: ChunkFillSpec (from BlockPlan), NpcRoster, SocietyProfile
|
||||
output: ChunkData (64×64 tile array)
|
||||
produces:
|
||||
- Architecture/terrain tiles from template tag
|
||||
- ALL tiles in chunk generated — including breach-only rooms (§3)
|
||||
- TileBehindState applied to wall tiles (§3)
|
||||
- Zone palette + era materials
|
||||
- Furniture from form × function matrix
|
||||
- NPC spawn points (flavor → NPC affinity weights applied)
|
||||
- LOS anchors (urban: walls/pillars; non-urban: trees/terrain features)
|
||||
- Edge contracts validated against loaded neighbors
|
||||
- Per-floor tile generation for tall structures (z-band-aware)
|
||||
↓
|
||||
OUTPUT: ChunkData (cached in memory, saved to save file)
|
||||
|
||||
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
WORLD STATE LAYER (Runtime, Post-Generation)
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
|
||||
WorldStateDelta stream (managed by gameplay systems, not generator)
|
||||
├── StructuralDamage (explosions, combat, decay)
|
||||
├── WallBreached (player/NPC destructive action)
|
||||
├── DoorStateChanged (persistent door states)
|
||||
├── ObjectModified (loot, evidence, planted objects)
|
||||
├── TileTypeChanged (post-damage tile state)
|
||||
├── AccessTierChanged (faction events, lockdowns)
|
||||
└── NpcRemoved (killed, arrested, fled)
|
||||
|
||||
Applied to: ChunkData at render/gameplay-query time
|
||||
Large events: soft re-generation via (original_seed XOR event_seed) — §5.3
|
||||
|
||||
Rendering: Generator ChunkData + ordered WorldStateDeltas = current visible state
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Canonical DistrictSkeleton Fields
|
||||
|
||||
Addressing D-R2-3 (Tyre and Gestalt adding fields independently). Here is the composite canonical field list for `DistrictSkeleton`. Tyre should author the final Rust struct; this is the field-level specification:
|
||||
|
||||
**Core (Tyre Round 1):**
|
||||
- `district_id: DistrictId`
|
||||
- `seed: u64`
|
||||
- `district_type: DistrictType`
|
||||
- `context: DistrictContext`
|
||||
- `blocks: [[BlockSkeleton; 4]; 4]`
|
||||
- `social_sites: Vec<SocialSitePlacement>`
|
||||
- `reservations: Vec<MultiBlockReservation>` (updated with vertical extent — §4)
|
||||
- `access_points: Vec<AccessPoint>`
|
||||
- `corridors: Vec<CorridorSpine>`
|
||||
- `z_levels: u8` → renamed `z_level_count: u8`
|
||||
- `zone_palette: Vec<ZoneDefinition>`
|
||||
|
||||
**Tyre Round 2 additions:**
|
||||
- `boundaries: DistrictBoundaries`
|
||||
- `society_profile: SocietyProfileRef`
|
||||
- `terrain: TerrainType`
|
||||
- `complexity: ComplexityTier`
|
||||
|
||||
**Gestalt Round 2 additions (revised):**
|
||||
- `setting_geometry: SettingGeometry` ← RETAINED
|
||||
- `guarantee_audit: GuaranteeAuditResult` ← RETAINED (now conditional-aware per §2)
|
||||
- `significance_tier` ← **REMOVED** (retired, absorbed into complexity + network position)
|
||||
|
||||
**Gestalt Round 3 additions:**
|
||||
- `vertical_structure: VerticalStructure` (Flat/Medium/Tall/Skyscraper — §4)
|
||||
- `breach_only_zones: Vec<ZoneId>` (at least 1 for Full-complexity — §3)
|
||||
|
||||
**Modification to SocialSitePlacement:**
|
||||
- `triangles: Vec<TriangleTemplate>` — each `TriangleTemplate` gains `purpose: Vec<TrianglePurpose>` (§7)
|
||||
|
||||
---
|
||||
|
||||
## 11. Open Questions for Implementation
|
||||
|
||||
The workshop has converged. Three architectural decisions should be formally recorded before implementation begins:
|
||||
|
||||
**OQ-R3-A (Grid rotation):** Tyre needs to decide whether district-level rotation is feasible within D-094 constraints, or whether boundary-level interventions (zigzag transition strip + diagonal infrastructure) are the full mitigation. From gameplay perspective: the boundary intervention is the minimum; full rotation would be ideal but is not required for V0.1-V0.3.
|
||||
|
||||
**OQ-R3-C (Wilderness informal zone):** For wilderness/maritime settings, the Informal Zone guarantee still applies (it's Tier 1 Universal), but its terrain expression is different. I've proposed `terrain_informal_zone` (cave, ravine, hidden cove, underdeck hold). Miri should confirm the cultural meaning.
|
||||
|
||||
**OQ-R3-D (Vessel architecture):** Miri's `bounded_mobile` flag for vessels needs architectural resolution before maritime DLC template authoring begins. Tyre's assessment should drive this.
|
||||
|
||||
**OQ-R3-E (Horizon as landmark):** Ozzie's requirement that water's edge is a reserved landmark. Araminta has the visual grammar; the question is whether the district skeleton generator needs an explicit `coastal_landmark_reservation` or whether the natural zone palette transition is sufficient. From guarantee perspective: the horizon should be a Tier 2 guarantee for any district with `TerrainType::Water` on one boundary — ensure it's generated, not filled.
|
||||
|
||||
---
|
||||
|
||||
*Gestalt — Round 3 complete. The pipeline is converged. Five directives addressed. Two redundant concepts retired. Tyre has the canonical struct reconciliation; Miri has the wilderness informal zone; Araminta has the horizon as landmark. These are the three remaining loose threads before architectural specification can be signed off.*
|
||||
|
||||
*Let me break down what this means for implementation sequencing: the delta layer (§5) can be stubbed trivially in V0.1; the breach-only zones (§3) are a chunk fill flag, not a pipeline change; the vertical scale (§4) only matters when a Full-complexity district has a tall structure reservation. None of these require V0.1 implementation. The pipeline is sound for V0.1 with stubs.*
|
||||
@@ -0,0 +1,440 @@
|
||||
# Generator Architecture Workshop — Round 4: Gestalt
|
||||
|
||||
**Role:** Systems Design / Gameplay Loop Requirements
|
||||
**Date:** 2026-02-27
|
||||
**Workshop:** Generator Architecture (#562)
|
||||
**Round:** 4 — Final Convergence
|
||||
|
||||
---
|
||||
|
||||
## Framing
|
||||
|
||||
Four tasks this round. Three open questions to close. One guarantee to amend. Twelve D-records to sign off. The lead has already settled the major architectural splits (WorldTier, entity-carried chunks, DramaDensity as runtime). My job is to close the remaining mechanical questions with concrete answers and lock the D-records.
|
||||
|
||||
Let me crack my knuckles.
|
||||
|
||||
---
|
||||
|
||||
## 1. OQ-R4-C: Assassination Difficulty Descriptor — Definitive Answer
|
||||
|
||||
Miri proposed `assassination_difficulty: low/medium/high/extreme` derived from:
|
||||
**observation density × information liquidity × aftermath engagement**
|
||||
|
||||
The question: where does it live? DistrictSkeleton, SocietyProfile, or computed on demand?
|
||||
|
||||
**Answer: Computed on demand. Never stored.**
|
||||
|
||||
Here's why this is the only defensible answer mechanically:
|
||||
|
||||
### The inputs are not all static
|
||||
|
||||
`assassination_difficulty` is a function of three input streams, only two of which are stable:
|
||||
|
||||
| Input stream | Source | Stability |
|
||||
|---|---|---|
|
||||
| Observation density | SocietyProfile (heritage root, institutional coverage) | Stable (generator output) |
|
||||
| Information liquidity | SocietyProfile (heritage root, settlement density) | Stable (generator output) |
|
||||
| Aftermath engagement | SocietyProfile (heritage root, faction presence) | Stable (generator output) |
|
||||
| Current NPC distribution | Storyteller state (DramaDensity, activated triangles) | **Dynamic** |
|
||||
| Spatial audit satisfaction | A-1 through A-4 guarantee flags | Stable (generator output) |
|
||||
| Active guard state | Simulation tick (faction events, alert level) | **Dynamic** |
|
||||
|
||||
The dynamic inputs mean a stored `assassination_difficulty` on any struct would be stale the moment the storyteller fires an event. A political crisis event spikes guard coverage; aftermath engagement goes from `medium` to `extreme`. A faction purge reduces community observation. The stored value would be wrong within a single session.
|
||||
|
||||
### Storing it causes incorrect player expectations
|
||||
|
||||
If the player sees an `assassination_difficulty` assessment that was baked at district generation, they're reading a stale number. The immersive sim promise is that the world responds. If the player INCREASED guard presence by burning down the safe house two districts over, the difficulty should reflect that. A stored value can't.
|
||||
|
||||
### The correct integration point
|
||||
|
||||
```
|
||||
assassination_difficulty =
|
||||
fn(
|
||||
society_profile: &SocietyProfile, // from SocietyProfile (stable)
|
||||
spatial_audit: &SpatialGuarantees, // from DistrictSkeleton (stable)
|
||||
active_state: &StorytellerState, // from runtime (dynamic)
|
||||
) -> DifficultyDescriptor
|
||||
```
|
||||
|
||||
This function is called:
|
||||
- At contract acceptance (player sees their pre-op assessment)
|
||||
- During pre-op planning phase (player can see how conditions change by day-phase)
|
||||
- NOT stored anywhere persistent
|
||||
|
||||
### What the D-record should specify
|
||||
|
||||
The D-record for `assassination_difficulty` should specify:
|
||||
1. The formula/weighting for the three input components
|
||||
2. The four output levels (`low/medium/high/extreme`) and their thresholds
|
||||
3. The integration points where the computation is invoked
|
||||
4. That it is **explicitly not a struct field on DistrictSkeleton or SocietyProfile** — it is a derived computation
|
||||
|
||||
The reason to canonicalize the formula in a D-record: other systems (NPC routing, faction responses, storyteller module selection) may want to consume the same computation. Having it defined once prevents different systems from computing it differently.
|
||||
|
||||
**Verdict: OQ-R4-C is resolved. No persistent storage. Computed on demand from SocietyProfile + spatial audit + runtime state.**
|
||||
|
||||
---
|
||||
|
||||
## 2. OQ-R4-F: Soft Re-Generation — The Concrete Example
|
||||
|
||||
Ozzie's principle: destruction must be *caused*, not *random*. The question: does `original_seed XOR event_seed` satisfy this, or do we need structured damage parameters?
|
||||
|
||||
I'm going to show you the actual output of both approaches for the same event. Then we'll know.
|
||||
|
||||
### The Scenario
|
||||
|
||||
**District X:** `district_seed = 0xA3F8C21B_7E64D509`
|
||||
- Heritage root: Iron (dense residential, workshop clusters)
|
||||
- Block layout: 4×4 grid, SMALL complexity
|
||||
- Block (2,3): Worker residential block, ~60 tiles
|
||||
- Floors 1-3: apartment units, corridor, shared kitchen
|
||||
- Sub-level (z=-1): utility tunnel, gas line infrastructure
|
||||
- Event: gas line rupture at (tile 2,3,38) at sim tick 47,302
|
||||
|
||||
---
|
||||
|
||||
### Approach A: XOR Reseeding
|
||||
|
||||
```
|
||||
event_seed = hash(EventType::GasExplosion, TilePosition(2,3,38), SimTick(47302))
|
||||
= 0x5B7E349A_1C82A7F3
|
||||
|
||||
reseeded = 0xA3F8C21B_7E64D509 XOR 0x5B7E349A_1C82A7F3
|
||||
= 0xF886F6816AE672FA
|
||||
```
|
||||
|
||||
The chunk fill re-runs on block (2,3) with `reseeded`. What does this produce?
|
||||
|
||||
| Tile position | Before | After (XOR reseed) |
|
||||
|---|---|---|
|
||||
| (2,3,1) — entry corridor | Corridor tile, N-S orientation | **Corridor tile, E-W orientation** |
|
||||
| (2,3,4) — apartment 1A | Residential interior | **Workshop space** (RNG diverged at zone assignment) |
|
||||
| (2,3,12) — shared kitchen | Kitchen fixture cluster | **Storage room** |
|
||||
| (2,3,38) — explosion origin | Gas line junction (sub-level) | **Open floor tile** |
|
||||
| (2,3,40) — adjacent unit | Apartment interior | **Wall** (block subdivision changed) |
|
||||
| (2,3,55) — block corner | Exterior wall | Exterior wall (stable, geometric) |
|
||||
|
||||
**The result:** The block has been *replaced*, not *damaged*. Tile (2,3,4) changed from a residential apartment to a workshop — not because the explosion destroyed residential use and workers moved in; the generator just made different decisions with the new seed. The zone assignment diverged at the first RNG call that governs zone type selection.
|
||||
|
||||
The explosion origin tile (2,3,38) lost its gas line fixture — but so did tiles across the entire block, because the fixture placement logic runs from a different RNG stream now. There's no spatial logic to the changes. The modifications don't radiate from the explosion center.
|
||||
|
||||
**Diagnosis:** XOR reseeding is a blender, not a bomb. It mixes the content uniformly rather than concentrating disruption at a source. The result looks *replaced* rather than *damaged*. This fails Ozzie's test — the destruction has no cause visible in the output.
|
||||
|
||||
**XOR reseeding is appropriate only for era-scale discontinuities**, where the settlement genuinely rebuilt from scratch (decades passed, original structures gone, new generation built different). It is wrong for in-playthrough events.
|
||||
|
||||
---
|
||||
|
||||
### Approach B: Structured Damage Parameters
|
||||
|
||||
```rust
|
||||
struct GasExplosionEvent {
|
||||
origin: TilePosition, // (2, 3, 38)
|
||||
blast_radius: u16, // 8 tiles primary, 14 tiles secondary
|
||||
intensity: f32, // 0.85 (high pressure rupture)
|
||||
propagation_dir: Option<Dir>, // None (omnidirectional rupture)
|
||||
ignition: bool, // true (gas ignites)
|
||||
}
|
||||
```
|
||||
|
||||
Application: the generator output is **unchanged**. The chunk maintains `original_seed = 0xA3F8C21B_7E64D509`. The damage event is appended to the `ChunkMutations` overlay:
|
||||
|
||||
```rust
|
||||
ChunkMutations {
|
||||
structural_changes: [
|
||||
// Primary blast zone (radius ≤ 8 tiles): damage proportional to distance
|
||||
StructuralChange { tile: (2,3,38), change: TileType::Rubble { debris_density: 1.0 } },
|
||||
StructuralChange { tile: (2,3,37), change: TileType::Rubble { debris_density: 0.9 } },
|
||||
StructuralChange { tile: (2,3,39), change: WallState::Breached { gap_size: 3 } },
|
||||
StructuralChange { tile: (2,3,36), change: TileType::Rubble { debris_density: 0.7 } },
|
||||
StructuralChange { tile: (2,3,4), change: TileType::Rubble { debris_density: 0.4 } },
|
||||
// Floor above (if loaded): ceiling collapse
|
||||
StructuralChange { tile: (2,3,38+floor), change: FloorState::PartialCollapse },
|
||||
// Secondary zone (radius 8–14): soot, scorch marks, broken fixtures
|
||||
TileOverride { tile: (2,3,50), visual_state: VisualMod::Scorched },
|
||||
TileOverride { tile: (2,3,51), visual_state: VisualMod::SootLayer },
|
||||
// ...
|
||||
],
|
||||
removed_objects: [gas_line_fixture_38, apartment_door_36, ...],
|
||||
placed_objects: [
|
||||
PlacedObject { pos: (2,3,42), object_type: DebrisPile, seed: derived },
|
||||
PlacedObject { pos: (2,3,35), object_type: FireScorch, seed: derived },
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
**The result:**
|
||||
|
||||
| Tile position | Before | After (structured overlay) |
|
||||
|---|---|---|
|
||||
| (2,3,1) — entry corridor | Corridor, N-S | **Corridor, N-S** (unchanged) |
|
||||
| (2,3,4) — apartment 1A | Residential interior | **Residential interior, debris scattered** (within blast radius but low intensity at distance) |
|
||||
| (2,3,12) — shared kitchen | Kitchen fixtures | **Kitchen fixtures, scorched** (secondary zone) |
|
||||
| (2,3,38) — explosion origin | Gas line junction | **Rubble, debris_density 1.0** |
|
||||
| (2,3,40) — adjacent unit | Apartment interior | **Rubble, debris_density 0.8** |
|
||||
| (2,3,55) — block corner | Exterior wall | **Exterior wall, soot marks** (secondary zone) |
|
||||
|
||||
The block is recognizably itself — a worker residential block that has been damaged. You can see the block's original structure through the destruction. The explosion origin is identifiable. The damage radiates outward. The adjacent block at (2,4) is untouched.
|
||||
|
||||
**This is caused destruction.** The spatial logic is legible.
|
||||
|
||||
---
|
||||
|
||||
### Decision: Regeneration Strategy Enum
|
||||
|
||||
```rust
|
||||
enum RegenerationStrategy {
|
||||
/// For localized in-playthrough events: explosions, fires, structural collapse
|
||||
/// Generator output unchanged; damage applied as ChunkMutations overlay
|
||||
LocalOverlay(DamageParameters),
|
||||
|
||||
/// For district-scale temporal changes: rebuilding after war, years of neglect
|
||||
/// Modify seed slightly; re-run generator for significant structural changes
|
||||
/// Appropriate when player returns to a district 10+ years later (between scenarios)
|
||||
SoftReseed { seed_modifier: u64 },
|
||||
|
||||
/// For era-level discontinuities: orbital strike, catastrophic flood, decades of war
|
||||
/// Appropriate between major time-skip scenarios, not within playthrough
|
||||
FullReseed,
|
||||
}
|
||||
```
|
||||
|
||||
**Rule:** In-playthrough events are ALWAYS `LocalOverlay`. `SoftReseed` and `FullReseed` only apply during scenario setup (between playthroughs or at major time-skip boundaries). The generator never re-runs for events the player witnesses or causes.
|
||||
|
||||
This resolves OQ-R4-F. The D-record should canonicalize these three strategies and explicitly prohibit XOR reseeding for in-playthrough events.
|
||||
|
||||
**Verdict: OQ-R4-F is resolved. LocalOverlay for in-playthrough events. XOR/soft reseed only at scenario boundaries.**
|
||||
|
||||
---
|
||||
|
||||
## 3. Rooftop Bar Clause — Amended Guarantee
|
||||
|
||||
### The Original Guarantee (Round 3)
|
||||
|
||||
> "Every tall structure (z_band_count ≥ 3) must have a roof zone classified `Insider` or `BreachOnly` accessible by non-obvious route."
|
||||
|
||||
### The Problem
|
||||
|
||||
This forces ALL rooftops to be secret or restricted. But the setting has:
|
||||
- Commission-era arcologies with public observation galleries
|
||||
- Commercial towers with rooftop restaurants
|
||||
- Religious structures with sky gardens
|
||||
- A transit hub's roof terrace where residents watch shuttle departures
|
||||
|
||||
The guarantee as written would require the rooftop restaurant to be an unauthorized trespass destination. That's wrong. Some rooftops are meant to be a public destination — a reason to climb, not a secret discovered by climbing.
|
||||
|
||||
What the guarantee was *trying* to protect: the discovery element. Rooftops should never be structurally irrelevant. They should always offer something — either a restricted secret, or a public destination with a hidden layer.
|
||||
|
||||
### The Revised Guarantee — Vertical Discovery
|
||||
|
||||
**For every tall structure (z_band_count ≥ 3), at least one of the following must be true:**
|
||||
|
||||
**Option A — Restricted Rooftop (Discovery Through Access)**
|
||||
The primary roof zone is classified `Insider` or `BreachOnly`, accessible by non-obvious route. The discovery is the access itself.
|
||||
|
||||
**Option B — Public Rooftop with Hidden Layer (Discovery Within Destination)**
|
||||
The primary roof zone is publicly accessible (Social Hub, Economic Node, or equivalent). A secondary zone within the same z-band is classified `Insider` or `BreachOnly`. This could be:
|
||||
- A maintenance level behind an access panel
|
||||
- A restricted transmitter array within the rooftop space
|
||||
- A private penthouse cluster separated by `Semi-Private` partition
|
||||
- A service stairwell to a sub-roof level
|
||||
|
||||
**The inviolable rule across both options:** Every tall structure must have *something* at the top that is not fully accessible from below. The discovery layer is mandatory. The public/private split of the primary space is not.
|
||||
|
||||
### Generator Implementation
|
||||
|
||||
```rust
|
||||
enum RooftopConfig {
|
||||
Restricted {
|
||||
zone_class: AccessTier, // must be Insider or BreachOnly
|
||||
access_route: RouteObviousness, // must be NonObvious
|
||||
},
|
||||
PublicWithHiddenLayer {
|
||||
primary_zone: ZoneType, // Social Hub, Economic Node, etc.
|
||||
secondary_restricted: ZoneSpec, // always present; Insider or BreachOnly
|
||||
},
|
||||
}
|
||||
|
||||
struct MultiBlockReservation {
|
||||
// ... existing fields ...
|
||||
rooftop: RooftopConfig, // replaces the old "roof zone guaranteed restricted" constraint
|
||||
}
|
||||
```
|
||||
|
||||
### The Guarantee Audit Change
|
||||
|
||||
Old check:
|
||||
> "Does this tall structure have a roof zone classified Insider or BreachOnly?"
|
||||
|
||||
New check:
|
||||
> "Does this tall structure have a `RooftopConfig::Restricted` or a `RooftopConfig::PublicWithHiddenLayer` with a non-empty `secondary_restricted`?"
|
||||
|
||||
Both options satisfy the audit. The key: the generator must choose one at district generation time based on the building's zone palette and heritage root. Commission-institutional buildings: `PublicWithHiddenLayer` (observation gallery + restricted records floor). Iron-heritage trade towers: `Restricted` (the roof belongs to the guild leadership). Frost-heritage isolated structures: `Restricted` (the roof is where the heating systems live and no one else goes up).
|
||||
|
||||
**Verdict: Rooftop guarantee amended. Rooftop bars are valid. The discovery layer remains mandatory.**
|
||||
|
||||
---
|
||||
|
||||
## 4. D-Record Sign-Off — All 12
|
||||
|
||||
Going through each. I'm flagging amendments where the D-record needs additional language beyond what the Round 3 notes contain.
|
||||
|
||||
| # | Item | Status | My Position |
|
||||
|---|---|---|---|
|
||||
| D-READY-1 | DistrictLayoutMode: Grid / Organic | **SIGNED OFF** | No amendments. Canonical. |
|
||||
| D-READY-2 | Guarantee Tier System | **SIGNED OFF** | Amendment below. |
|
||||
| D-READY-3 | TrianglePurpose Enum | **SIGNED OFF** | No amendments. |
|
||||
| D-READY-4 | WallBackside / TileBehindState | **SIGNED OFF** | Amendment below. |
|
||||
| D-READY-5 | Dynamic Modification via Overlay | **SIGNED OFF** | Amendment below (from OQ-R4-F). |
|
||||
| D-READY-6 | ZonePalette Modifier System | **SIGNED OFF** | No amendments. |
|
||||
| D-READY-7 | Horizon View Corridor | **SIGNED OFF** | No amendments. |
|
||||
| D-READY-8 | Assassin Lens Spatial Guarantees | **SIGNED OFF** | Amendment below. |
|
||||
| D-READY-9 | Heritage Grammar Overlay | **SIGNED OFF** | No amendments. |
|
||||
| D-READY-10 | Non-Urban Informal Zone Typology | **SIGNED OFF** | No amendments. |
|
||||
| D-READY-11 | Vertical Scale Architecture | **SIGNED OFF** | Amendment below (Rooftop Bar Clause). |
|
||||
| D-READY-12 | Trauma Events as EraModification | **SIGNED OFF** | Amendment below (from OQ-R4-F integration). |
|
||||
|
||||
---
|
||||
|
||||
### D-READY-2 Amendment: Guarantee Tier System
|
||||
|
||||
The D-record should include explicit naming for the three tiers:
|
||||
|
||||
- **Tier 1 — Universal Inhabited Guarantees** (all inhabited districts, any complexity)
|
||||
- Social Hub, Informal Zone, Encounter Corridor
|
||||
- **Tier 2 — Full-Complexity Guarantees** (Full-complexity only)
|
||||
- Traffic Chokepoint, Institutional Space, Insider Space, Economic Node
|
||||
- Horizon View Corridor (coastal Full-complexity)
|
||||
- BreachOnly Zone (≥1 per Full-complexity)
|
||||
- **Tier 3 — Conditional Parameter Guarantees** (depend on district parameter values)
|
||||
- Elevated Vantage, Egress Multiplicity, Temporal Opacity Window (A-1/A-2/A-3)
|
||||
- Non-Institutional Access Route (A-4 — applies to all Full-complexity)
|
||||
- Economic Asymmetry Signal (when `economic_disparity` flag present)
|
||||
- Power Gradient Visibility (when `faction_control` field is non-null)
|
||||
|
||||
The audit runs all applicable checks. A Minimal farmstead gets 3 checks. A Full-complexity coastal urban hub gets up to 12. The D-record should specify which checks are mandatory vs. which are triggered by parameter flags.
|
||||
|
||||
---
|
||||
|
||||
### D-READY-4 Amendment: Dual Classification System
|
||||
|
||||
The D-record should clearly establish that `TileBehindState` and `WallBackside` serve complementary roles and **both** are canonical:
|
||||
|
||||
| Enum | Scope | Purpose |
|
||||
|---|---|---|
|
||||
| `WallBackside` (Tyre) | Structural | What is physically behind this wall tile (for generation and LOS) |
|
||||
| `TileBehindState` (Gestalt) | Gameplay | What kind of space this represents for gameplay systems |
|
||||
|
||||
These are not duplicates. A wall with `WallBackside::ServiceVoid` has `TileBehindState::Interstitial`. A wall with `WallBackside::AdjacentSpace` has `TileBehindState::HiddenRoom` OR `TileBehindState::StructuralFill` depending on access tier configuration. The D-record should canonicalize both enums and document the mapping between them.
|
||||
|
||||
---
|
||||
|
||||
### D-READY-5 Amendment: RegenerationStrategy Integration
|
||||
|
||||
Add to the D-record:
|
||||
|
||||
```rust
|
||||
enum RegenerationStrategy {
|
||||
LocalOverlay(DamageParameters), // in-playthrough events; generator output unchanged
|
||||
SoftReseed { seed_modifier: u64 }, // scenario-boundary temporal changes only
|
||||
FullReseed, // era-level discontinuities only
|
||||
}
|
||||
```
|
||||
|
||||
**Explicit constraint in the D-record:** In-playthrough events must use `LocalOverlay`. `SoftReseed` and `FullReseed` are scenario-setup tools, not event responses. The generator does not re-run for player-witnessed events.
|
||||
|
||||
---
|
||||
|
||||
### D-READY-8 Amendment: A-1 through A-4 as Tier 3 Conditional
|
||||
|
||||
The assassin spatial guarantees (A-1: Elevated Vantage, A-2: Egress Multiplicity, A-3: Temporal Opacity Window, A-4: Non-Institutional Route) should be positioned explicitly as **Tier 3 Conditional Guarantees**, not as an assassin-specific subsystem.
|
||||
|
||||
The D-record language should be:
|
||||
|
||||
> "A-1, A-2, and A-3 are conditional guarantees triggered when `complexity_tier == Full`. A-4 is a mandatory Full-complexity guarantee (all playstyles benefit from non-institutional routes). These are derived properties of the existing spatial configuration, validated by the guarantee audit. They are not spatial features tagged for the assassin — they are properties that any playstyle can discover and exploit."
|
||||
|
||||
This framing prevents scope creep where assassin-specific content gets its own generation budget. The guarantees audit against existing spatial output; they don't add generation cost.
|
||||
|
||||
---
|
||||
|
||||
### D-READY-11 Amendment: Rooftop Bar Clause
|
||||
|
||||
The D-record should replace the original guarantee with the amended `RooftopConfig` model from Section 3 above. Specifically:
|
||||
|
||||
> "Every tall structure (z_band_count ≥ 3) must specify a `RooftopConfig`. If `Restricted`, the roof zone must be `Insider` or `BreachOnly` with a non-obvious access route. If `PublicWithHiddenLayer`, the primary public zone must be accompanied by a secondary restricted zone within the same z-band. The discovery layer is mandatory in both configurations. Heritage root and building zone palette determine which configuration the generator assigns."
|
||||
|
||||
---
|
||||
|
||||
### D-READY-12 Amendment: Trauma Event + RegenerationStrategy
|
||||
|
||||
Trauma events trigger `LocalOverlay`, not reseeding. The D-record should explicitly state:
|
||||
|
||||
> "`ModificationType::TraumaEvent` applies structural changes via `ChunkMutations::LocalOverlay`. The generator output (original_seed) is preserved. Cultural aftermath decays toward baseline at heritage-root-dependent rates, tracked in simulation state. Physical destruction and cultural aftermath are separate tracks — the wall being rubble is a `StructuralChange`; the community's altered NPC weight distribution is simulation state that decays."
|
||||
|
||||
---
|
||||
|
||||
## 5. Lead Decisions — Acknowledged
|
||||
|
||||
The following lead decisions are received and incorporated:
|
||||
|
||||
**WorldTier wins over SignificanceTier**
|
||||
|
||||
Acknowledged. `WorldTier` correctly describes what this parameter measures: the simulation fidelity budget allocated to this location. `SignificanceTier` implied narrative importance, which is wrong — a politically significant backwater still gets Minimal complexity if the generator didn't budget for it. The field is now `world_tier: WorldTier` on the DistrictSkeleton.
|
||||
|
||||
**Entity-carried chunks are CORE architecture**
|
||||
|
||||
Acknowledged. `MobileChunk` as entity-carried `ChunkData`. Vessels exist as persistent world entities — docked at port, visible from the dock, present on the world map. The exterior is a scrolling visual buffer in `InTransit` state. Miri's cultural grammar applies fully to both static and mobile chunk types. The arrival-deadline temporal pressure is core gameplay.
|
||||
|
||||
**DramaDensity is runtime state, NOT on DistrictSkeleton**
|
||||
|
||||
Acknowledged and confirmed from my own Round 3 position. The DistrictSkeleton carries the capacity ceiling. The storyteller carries the current value. The D-records should explicitly state this constraint.
|
||||
|
||||
---
|
||||
|
||||
## Final Pipeline Statement — Locked
|
||||
|
||||
Three-layer model, canonicalized:
|
||||
|
||||
```
|
||||
GENERATOR STATE (immutable after Phase 1)
|
||||
├── Phase 1: DistrictSkeleton
|
||||
│ ├── world_tier: WorldTier (simulation fidelity budget)
|
||||
│ ├── complexity_tier: ComplexityTier (content budget)
|
||||
│ ├── layout_mode: DistrictLayoutMode (Grid | Organic)
|
||||
│ ├── spatial guarantees: Tier 1/2/3 audit flags
|
||||
│ ├── rooftop: RooftopConfig (Restricted | PublicWithHiddenLayer)
|
||||
│ └── society_profile: SocietyProfile (heritage root, institutional coverage, etc.)
|
||||
└── Phase 2: PreparedDistrict
|
||||
├── SocialSitePlacement (triangles with Vec<TrianglePurpose>)
|
||||
├── NpcManifest (seeded from society_profile)
|
||||
├── ZonePalette assignments (base + modifiers)
|
||||
└── ChunkMutations pending (pre-queued from simulation events)
|
||||
|
||||
SIMULATION STATE (runtime storyteller)
|
||||
├── DramaDensity (per-district, storyteller-controlled)
|
||||
├── ActivatedTriangles (subset of SocialSitePlacement)
|
||||
├── assassination_difficulty (computed on demand from SocietyProfile + audit + runtime)
|
||||
└── StorytellerModules (fired events, fragility triggers)
|
||||
|
||||
DELTA LAYER (post-generation)
|
||||
├── ChunkMutations applied (LocalOverlay for in-playthrough)
|
||||
├── NpcRemoved / NpcStateChanged
|
||||
├── AccessTierChanged (factions seal or open zones)
|
||||
└── WorldStateDelta (composed from all active mutations)
|
||||
```
|
||||
|
||||
These three layers compose at render time. The generator never re-runs. The pipeline is locked.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions Remaining
|
||||
|
||||
**None.**
|
||||
|
||||
- OQ-R4-A: Entity-carried chunks selected by lead. Resolved.
|
||||
- OQ-R4-B: WorldTier selected by lead. Resolved.
|
||||
- OQ-R4-C: Computed on demand. Resolved (Section 1).
|
||||
- OQ-R4-D: Heritage grammar overlay — this is OQ-R4-D which is Araminta's domain (representation in chunk fill assets vs. modifier objects). I'm waiting on Araminta's response; it doesn't block D-record production since the *content* of the heritage grammar (Miri) and the *integration point* (modifier system, D-READY-6) are both locked.
|
||||
- OQ-R4-E: One NPC, five lenses — Nigel's domain. Doesn't affect my output.
|
||||
- OQ-R4-F: LocalOverlay for in-playthrough events. Resolved (Section 2).
|
||||
|
||||
**Round 4 closes from my side. Twelve D-records ready. Three open questions resolved. Pipeline locked.**
|
||||
@@ -0,0 +1,95 @@
|
||||
# Generator Architecture Workshop — Round 5: Gestalt (Final Review)
|
||||
|
||||
**Role:** Systems Design / Gameplay Loop Requirements
|
||||
**Date:** 2026-02-27
|
||||
**Workshop:** Generator Architecture (#562)
|
||||
**Round:** 5 — Final Review
|
||||
|
||||
---
|
||||
|
||||
## Review: Does `workshop-outcomes.md` Accurately Capture My Contributions?
|
||||
|
||||
Short answer: **Yes, with two minor corrections.**
|
||||
|
||||
---
|
||||
|
||||
## What Is Accurately Captured
|
||||
|
||||
### Three-Layer Model
|
||||
|
||||
The generator/simulation/delta separation is correctly captured and the pipeline diagram in the outcomes matches my Round 4 locked state. The immutability of generator output post-Phase 1, and the composition of layers at render time, are correct.
|
||||
|
||||
### LocalOverlay Mandate + XOR Prohibition
|
||||
|
||||
The RegenerationStrategy enum is correctly specified with all three tiers:
|
||||
- `LocalOverlay` for in-playthrough events (mandatory)
|
||||
- `SoftReseed` at scenario boundaries only
|
||||
- `FullReseed` at era-level discontinuities only
|
||||
|
||||
The explicit XOR prohibition is correctly recorded under both D-READY-5 and D-READY-14. The separate D-READY-14 records the prohibition as an architectural mandate, which is the right framing — it's bigger than just the overlay mechanics.
|
||||
|
||||
### RooftopConfig: Restricted | PublicWithHiddenLayer
|
||||
|
||||
The amended Rooftop Bar Clause is correctly captured. The key requirement — discovery layer mandatory in both configurations, heritage root drives assignment — is accurate. The guard rails around mandatory hidden layers are preserved.
|
||||
|
||||
### D-READY-4: Dual Classification System
|
||||
|
||||
The `WallBackside` / `TileBehindState` split is correctly framed as complementary, not duplicated. The mapping (ServiceVoid → Interstitial; AdjacentSpace → HiddenRoom or StructuralFill) is accurate.
|
||||
|
||||
### D-READY-8: Assassin Lens as Derived Properties
|
||||
|
||||
Correctly captured: A-1 through A-4 are derived properties of existing spatial configuration, not assassin-tagged features. "They add no generation cost; the audit validates existing output." That is the exact framing from my Round 4 and it is preserved.
|
||||
|
||||
### Assassination Difficulty — Tension Preserved
|
||||
|
||||
The outcomes document correctly notes the minor tension between my "computed entirely on demand" position and Miri's "stored cultural baseline" position, and correctly flags it as Q-NNN-f (open question for sprint work). The recommended synthesis — stored baseline in DerivedDistrictAnalysis + on-demand runtime computation for player-facing assessment — is a reasonable middle ground. I accept this as the forward direction.
|
||||
|
||||
### Open Questions Attributed to Me
|
||||
|
||||
Q-NNN-d (DramaDensity naming) and Q-NNN-f (assassination difficulty synthesis) are correctly attributed and framed.
|
||||
|
||||
---
|
||||
|
||||
## Corrections
|
||||
|
||||
### Correction 1: Guarantee Audit Count — Minor
|
||||
|
||||
**What the outcomes say:** "A Full-complexity coastal urban hub gets up to 13."
|
||||
|
||||
**What my Round 4 said:** "up to 12."
|
||||
|
||||
The discrepancy is one check. Reviewing the Tier 2 and Tier 3 lists in D-READY-2:
|
||||
|
||||
- Tier 2: Traffic Chokepoint, Institutional Space, Insider Space, Economic Node, Horizon View Corridor, BreachOnly Zone, Rooftop Discovery Zone = **7**
|
||||
- Tier 3: A-1, A-2, A-3, A-4, Economic Asymmetry Signal, Power Gradient Visibility = **6**
|
||||
- Tier 1: Social Hub, Informal Zone, Encounter Corridor = **3** (these always run)
|
||||
|
||||
3 + 7 + 6 = 16 potential checks, but most Tier 3 checks are conditional. "Up to 13" vs "up to 12" depends on whether Rooftop Discovery Zone (Tier 2) was included in my count or not. Given that the Rooftop Bar Clause was an amendment I made in Round 4 after the original count was established, it appears the outcomes correctly adds it and reaches 13. **The outcomes document is correct; my Round 4 count of "up to 12" did not yet include the Rooftop Discovery Zone that I myself amended into Tier 2.**
|
||||
|
||||
No change needed to the outcomes document — it has the right number.
|
||||
|
||||
### Correction 2: Field Name — `complexity` vs `complexity_tier`
|
||||
|
||||
**What the outcomes say (pipeline diagram):** `complexity: ComplexityTier`
|
||||
|
||||
**What my Round 4 pipeline diagram said:** `complexity_tier: ComplexityTier`
|
||||
|
||||
The outcomes document uses `complexity` as the field name in the Phase 1 struct. My Round 4 used `complexity_tier`. The outcomes document should use consistent naming — either `complexity` (shorter, idiomatic) or `complexity_tier` (explicit, parallel with `world_tier`).
|
||||
|
||||
**My recommendation:** Use `complexity_tier` to parallel `world_tier`. Both fields identify a tier; both field names should follow the same convention. The outcomes diagram should be corrected to `complexity_tier: ComplexityTier` for consistency.
|
||||
|
||||
**This is a minor point.** If the lead prefers `complexity`, that's also fine — it just needs to be consistent everywhere.
|
||||
|
||||
---
|
||||
|
||||
## Items Added by Other Participants (No Objections)
|
||||
|
||||
**D-READY-13 (MobileChunk)** and **D-READY-14 (DamageOverlay/RegenerationStrategy as separate D-record)** were not in my Round 4 twelve-item sign-off table because they were developed primarily by Tyre/Miri/Nigel and the 14-record count was assembled from the full team's output. I have no objections to either. D-READY-13 is correctly scoped (entity-carried, no Phase 1/Phase 2 split, departure schedules mandatory). D-READY-14 correctly separates the prohibition into its own record.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The outcomes document accurately represents my Round 4 contributions. One self-correcting count discrepancy (12→13 checks, resolved by my own Rooftop Bar Clause amendment), one minor naming consistency question (`complexity` vs `complexity_tier`). No substantive misrepresentations. No positions attributed to me that I didn't hold.
|
||||
|
||||
**Round 5 review complete from my side.**
|
||||
@@ -0,0 +1,436 @@
|
||||
# Generator Architecture Workshop — Round 1: Miri (Worldbuilder)
|
||||
|
||||
**Topic:** Worldbuilding inputs for the generator pipeline
|
||||
**Date:** 2026-02-27
|
||||
**Source decisions:** D-025, D-036, D-093, D-095, Q-032, Q-036, Q-039
|
||||
**Prior art:** Wiki Review Workshop Round 4 (Miri — ingredients menu, society profile)
|
||||
|
||||
---
|
||||
|
||||
## Orientation: What worldbuilding supplies to the generator
|
||||
|
||||
The generator does not produce setting — it *reproduces* setting from parameters. My role is to define what those parameters are and where in the pipeline they enter. This document organizes my prior work (wiki-review Round 4 ingredients menu, Krenn System brief) into a form the pipeline architecture can consume.
|
||||
|
||||
Everything below connects to the confirmed existence of Q-032 (cultural ingredients menu, approved by lead but not yet formally specified). This workshop is the first place those parameters need to map onto a concrete pipeline. That mapping is what I'm providing here.
|
||||
|
||||
---
|
||||
|
||||
## Question 1: How does the generator reproduce the cultural/economic variation of 300 worlds?
|
||||
|
||||
The short answer: **through a society profile assembled from combinatorial ingredients, not through per-world hand-authoring**.
|
||||
|
||||
The longer answer follows from what I established in Wiki Review Round 4. The Krenn System brief (Rounds 1-2 of that workshop) was a proof of concept — one complete output. Round 4 inverted the question: instead of writing a brief for each world, we design the *ingredient pantry* from which any brief can be generated.
|
||||
|
||||
### The ingredient categories (Q-032 scope, prior work summary)
|
||||
|
||||
I defined six ingredient categories in Wiki Review Round 4. Restating them here as formal generator inputs:
|
||||
|
||||
**A. Heritage Roots** (1-3 selected, blend weights summing to 1.0)
|
||||
|
||||
Ten roots, each a parameter bundle encoding phonetics + social dynamics + trust model:
|
||||
|
||||
| Root | Phonetic Signature | Social Signature | Trust Model |
|
||||
|---|---|---|---|
|
||||
| **Frost** | Compact, consonant-clusters, hard stops | Reserved, privacy-first | Patience — time + shared labor |
|
||||
| **Tide** | Flowing, vowel-rich, soft consonants | Expressive, group-oriented | Hospitality — sharing food/space |
|
||||
| **Iron** | Heavy, rhythmic, gutturals | Communal solidarity, labor-proud | Collective — trust the group |
|
||||
| **Spice** | Layered, precise stress, sibilants | Extended kinship, spiritual undertone | Kinship — blood/marriage networks |
|
||||
| **Jade** | Precise, clean, varied-short | Hierarchical, honor-aware | Competence — skill earns respect |
|
||||
| **Dust** | Rhythmic, open vowels, nasals | Communal decision, oral tradition | Witness — public demonstration |
|
||||
| **Vine** | Warm, soft stops, rolled consonants | Class-conscious, family honor | Family — recognized kinship |
|
||||
| **Salt** | Pragmatic, clipped, dental | Individualist, commercial | Transaction — fair dealing |
|
||||
| **Stone** | Steady, balanced, laterals | Enduring, traditional, land-connected | Tenure — long presence |
|
||||
| **Arc** | Sharp, fricatives, unusual combos | Intellectual, cosmopolitan | Argument — demonstrated reasoning |
|
||||
|
||||
Blend example (confirmed Krenn): `{frost: 0.55, salt: 0.30, iron: 0.15}`. The blend weights produce naming phonetics, social dynamics, and trust parameters. The Krenn System regional brief from wiki-review Rounds 1-2 is the validated output of this blend.
|
||||
|
||||
**B. Settlement Motivation** (one primary)
|
||||
|
||||
Economic-extractive / economic-trade / economic-agricultural / ideological-political / ideological-academic / institutional-military / institutional-administrative / refugee / frontier-adventurist.
|
||||
|
||||
Absence is valid: a world with no ideological motivation has no philosophical frame for its grey economy. People smuggle because they need to, not because they believe anything. That absence IS character.
|
||||
|
||||
**C. Economic Function** (current activity, may differ from founding motivation)
|
||||
|
||||
Extraction / logistics / manufacturing / agriculture / services / research / military-security / administrative / transit / mixed.
|
||||
|
||||
**D. Economic Pressure** (1-2 selected — what makes extra income tempting)
|
||||
|
||||
Tight-margin / debt-trap / status-competition / survival-gap / opportunity-disparity / prohibition-economy / generational-extraction.
|
||||
|
||||
Krenn is `[tight-margin, prohibition-economy]`. That double pressure produces the specific moral texture: economically rational AND ideologically defensible. A different system with `[survival-gap, prohibition-economy]` produces the same contraband type but a more desperate, less principled grey economy. The player feels the difference in NPC motivation.
|
||||
|
||||
**E. Drift Stage** (age modifier on heritage blending)
|
||||
|
||||
Pioneer (0-50yr) / crystallizing (50-150yr) / mature (150-300yr) / ancient (300+yr).
|
||||
|
||||
Drift affects how blended the roots are and how much novel cultural content has emerged from local conditions. At "ancient," the heritage roots are substrate — barely detectable. At "pioneer," the primary root dominates strongly. This is the generator's primary defense against franchise similarity.
|
||||
|
||||
**F. Absence Parameters** (what's missing)
|
||||
|
||||
Any ingredient category can be NULL:
|
||||
- No heritage consciousness → functional naming, no substrate words, no food traditions
|
||||
- No institutional authority → self-governing, Commission-absent, grey economy is the only economy
|
||||
- No community bonds → transient population, no social fabric to investigate
|
||||
- No ideological framework → people survive, they don't philosophize
|
||||
|
||||
NULL values produce distinct societies. A transit hub with high turnover and no heritage consciousness plays completely differently from a mature logistics district.
|
||||
|
||||
### How 300 worlds get variety
|
||||
|
||||
The ingredient space is large but bounded:
|
||||
- 10 roots × 3 blend positions (with weights) = many thousands of phonetic/social combinations
|
||||
- 9 motivation types × NULL option = 10 states
|
||||
- 10 economic functions = 10 states
|
||||
- 7 pressure types × 2 picks × NULL option = combinatorial
|
||||
- 4 drift stages
|
||||
|
||||
Conservative estimate: even constraining the ingredient space heavily, the combination space comfortably exceeds 300 meaningfully distinct societies. The deeper question (for Nigel and Gestalt) is whether the GAMEPLAY variation matches the cultural variation — whether cultural differences translate to meaningfully different investigation experiences. My input: the society profile parameters feed directly into dialogue access thresholds, NPC pattern distributions, and trust-building timelines. Cultural variation produces mechanical variation.
|
||||
|
||||
**The parameter that translates culture to gameplay:** `social.privacy_level` and `trust.building_rate` are the key levers. A Frost-dominant society (high privacy, slow trust) produces patient-observation investigations. A Tide-dominant society (low privacy, fast trust) produces social-network investigations. The same PC archetype plays completely differently across these cultures.
|
||||
|
||||
---
|
||||
|
||||
## Question 2: What lore-level inputs drive the pipeline?
|
||||
|
||||
Breaking this down by the specific categories the brief mentions:
|
||||
|
||||
### Faction identity
|
||||
|
||||
Faction presence enters the pipeline at **two stages**:
|
||||
|
||||
**Stage 1 — System-level (above geography):** Before any geography is generated, the system has a political classification:
|
||||
- Commission presence tier: comprehensive / standard / intermittent / absent
|
||||
- Concord Assembly reach: represented / liaison-only / nominal / beyond-reach
|
||||
- Syndic presence: dominant / significant / minor / absent
|
||||
- Independent governance: none / district / station / system-wide
|
||||
|
||||
These classify who controls the space. They set the baseline Meridian coverage tier (Commission presence = comprehensive coverage; absence = structural gaps). They also determine which faction templates are available for district-level social sites.
|
||||
|
||||
**Stage 2 — District-level (at the skeleton):** Faction control at the district level determines:
|
||||
- Which faction's social sites are instantiated (Commission inspection post vs. independent workers' hall)
|
||||
- Authority NPC pattern distribution (more SYSTEM NPCs under strong faction control, more HANDLER NPCs under faction absence/power vacuum)
|
||||
- What the grey economy structure looks like (ring-style horizontal cooperative vs. cartel-style vertical with handler hierarchy)
|
||||
|
||||
Setting note — faction identity does NOT map to specific named factions at generation time. The system generates "dominant regulatory faction" and "local economic faction" from the political classification. Specific named factions (Commission, Concord Assembly, Talvik/Sova Logistics Consortium) are instantiated in hand-authored content only.
|
||||
|
||||
### Economic function
|
||||
|
||||
Economic function is both a society-level input (what the world does) and a district-level input (what this district specifically does within the world). Sova Transit District's economic function (logistics) differs from the station's overall mix. The district's function determines:
|
||||
|
||||
- **Daily rhythm:** Shift-based / seasonal / project-based / client-based / continuous
|
||||
- **Primary gathering trigger:** Shift-end / market-day / lecture-end / patrol-rotation
|
||||
- **Primary social site type:** Bar (shift-end) / market (commercial) / lab commons (research) / mess hall (military)
|
||||
- **Investigation vector:** Manifest discrepancies (logistics) / land records (agricultural) / research logs (academic) / chain-of-command gaps (military)
|
||||
- **Grey economy structure:** What's being smuggled depends on what flows through legitimately. Logistics = cargo-embedded contraband. Research = stolen data or restricted compounds. Manufacturing = diverted materials.
|
||||
|
||||
### Tech level
|
||||
|
||||
Tech level is not a simple progression tier. It's a **three-axis profile**:
|
||||
|
||||
**Axis 1: Meridian coverage density** (surveillance infrastructure)
|
||||
- Comprehensive (Commission-grade): full public and institutional coverage
|
||||
- Standard: common areas covered, private spaces standard-grade
|
||||
- Degraded: structural gaps, older infrastructure, potentially exploited
|
||||
- Minimal: maintenance corridors, pre-Meridian construction, no effective coverage
|
||||
|
||||
This is already canonized for Sova (D-093 zone palette by coverage tier). The generator must assign coverage tiers per zone/district based on construction era + institutional investment + ring/grey-economy interference.
|
||||
|
||||
**Axis 2: Neural lattice penetration** (how much of the population has lattice)
|
||||
|
||||
Station Sova's working-class logistics district: broad lattice penetration, but regulated to basic-tier. This is what makes aftermarket lattice components valuable — broad desire, restricted supply, economic barriers to legitimate upgrade.
|
||||
|
||||
At 300 worlds, lattice penetration is a society parameter that affects:
|
||||
- What contraband type is in demand (high penetration + strict regulation = aftermarket components; low penetration + no regulation = basic access kits)
|
||||
- PC perception modes available
|
||||
- NPC information-sharing patterns (Meridian-mediated vs. physical word-of-mouth)
|
||||
|
||||
**Axis 3: Infrastructure age / construction era**
|
||||
|
||||
Already canonized in D-093 (Z-level model: z=0 Era 1 maintenance, z=1 operational, z=2 Era 3 gate cluster). Era tagging is the generator's architectural stratification tool. Different eras produce different:
|
||||
- Structural materials (visual coherence input for Araminta)
|
||||
- Meridian coverage gaps (older = less coverage)
|
||||
- Social dynamics (workers in Era 1 maintenance have different relationship to the station than workers in Era 3 institutional spaces)
|
||||
|
||||
### Population density
|
||||
|
||||
Population density is **extrapolated from economic function and capacity**, not input directly. This matches the Cities Skylines model (population follows zoning/capacity). From a worldbuilding perspective:
|
||||
|
||||
- Logistics district: 30-40 workers per shift × shift overlap = ~800 permanent residents on Sova (D-036 confirmed). High transient component (freight crews, visitors).
|
||||
- Transit hub element: adds transient flux. The investigation texture changes when a significant portion of the population is temporary — transients have less community loyalty but also less community protection.
|
||||
|
||||
For the generator, population density should be an OUTPUT of the capacity calculation, then fed back as an input to social site scale (how large a social site needs to be to serve this population).
|
||||
|
||||
### Historical events
|
||||
|
||||
Historical events are the generator's "damage to the initial output" pass. A society profile describes what the world was like at steady state. Historical events modify that:
|
||||
|
||||
- **Founding crisis** (refugee wave, corporate collapse, war): pushes drift_stage forward for that event's effects while leaving broader culture at current drift
|
||||
- **Economic disruption** (resource depletion, trade route change): shifts economic pressure parameters mid-system history
|
||||
- **Institutional incursion** (Commission crackdown, Syndic restructuring): changes faction presence tier, modifies authority attitude parameter
|
||||
|
||||
Sova's relevant historical events:
|
||||
- Original Talvik Logistics founding (~180yr ago): founding motivation = economic-trade, sets initial parameters
|
||||
- Syndic restructuring into Sova Logistics Consortium (~80yr ago): corporate disruption event, modest authority attitude shift
|
||||
- Current ring activity: not a historical event in the generator sense — it's the Tier 1 drama module applied to an otherwise stable logistics district
|
||||
|
||||
For the generator, historical events are a modifier pass AFTER society profile generation. They produce anomalies — places where the current state doesn't match the expected parameters because something happened.
|
||||
|
||||
---
|
||||
|
||||
## Question 3: Station settings vs. planet-side cities vs. orbital installations
|
||||
|
||||
**Recommendation: Same pipeline, different geography topology input.**
|
||||
|
||||
The geography input is the first stage of the pipeline. What differs between setting types is the *geometry of what geography can be*, not the pipeline structure itself.
|
||||
|
||||
### Stations
|
||||
|
||||
Closed-envelope geography. Key characteristics:
|
||||
- **Bounded and layered:** Z-levels are hard separations (D-093). No "outside." Access topology is vertical as much as horizontal.
|
||||
- **Era-stratified:** Older construction is typically the foundation (z=0, maintenance, low coverage); newer construction is the top (z=2, institutional, high coverage). This is reversed from planet-side where old=historic center, new=suburbs.
|
||||
- **Class is expressed spatially:** Upstation = institutional/administrative. Working level = operational. Below = maintenance/grey zone. Players can read social class from Z-level in a way that planet-side maps can't reproduce.
|
||||
- **No natural geography:** Weather is HVAC. "Outdoors" is a viewport. Natural beauty is completely absent unless the station was designed as a habitat (which logistics stations are not).
|
||||
- **Transit-culture possibility:** If the station is a hub (like Sova), a significant fraction of the population is transient. The grey economy can exploit this — unfamiliar faces don't get scrutinized.
|
||||
|
||||
**Generator implication:** Station geography is a *zone-and-level grid*. The block generator produces zones (institutional, operational, maintenance, commercial, residential) arranged in vertical layers rather than geographic gradients.
|
||||
|
||||
### Planet-side cities
|
||||
|
||||
Open-envelope geography. Key characteristics:
|
||||
- **Unbounded and spread:** Natural geography (terrain, water, weather) shapes districts. Investigation can include outdoor traversal. Districts are separated by natural boundaries (river district, hillside administrative quarter, dockyards).
|
||||
- **Weather as gameplay element:** Already canonized for Velen (D-050 — fog degrades vision cones, storyteller times weather for dramatic effect). Planet-side investigations use weather as a genuine mechanical variable.
|
||||
- **Era-stratified differently:** Historic center vs. expansion vs. suburbs. Old is typically the heart of the city. New construction is peripheral. Class is expressed through distance from center and quality of infrastructure.
|
||||
- **Agriculture possible:** Rural surrounding territory. Food culture is stronger. Seasonal rhythms affect NPC schedules.
|
||||
- **Gravity normal:** Station-born visitors notice 1.0g. Gravity is background reality for inhabitants.
|
||||
|
||||
**Generator implication:** Planet-side geography is a *terrain-influenced zone spread*. The district generator places districts according to natural features rather than Z-levels.
|
||||
|
||||
### Orbital installations (non-station)
|
||||
|
||||
Specialized closed-envelope. Key characteristics:
|
||||
- **Smaller and more homogeneous:** Research platforms, military outposts, mining operations. Population is typically 200-1000 rather than 12,000. Smaller population = fewer social sites, tighter community.
|
||||
- **Single-purpose:** One economic function dominates. There's no "upstation commercial quarter" — everything serves the primary function.
|
||||
- **Higher institutional control:** Smaller populations are more supervised. Meridian coverage is usually comprehensive. Grey economies exist but are harder to maintain — everyone knows everyone.
|
||||
- **Mission-temporal:** Installations may have defined operational lifespans. Workers rotate. This pushes toward "pioneer" drift stage even for old installations (constant personnel rotation prevents cultural accumulation).
|
||||
|
||||
**Generator implication:** Orbital installations use the same pipeline but with constraints: single economic function, small social site count, high coverage baseline, low drift stage.
|
||||
|
||||
### The same pipeline handles all three
|
||||
|
||||
The pipeline differences are:
|
||||
1. **Geography topology generator** (first stage): station → zone-and-level grid; planet-side → terrain-influenced spread; orbital → single-zone constrained
|
||||
2. **Heritage drift adjustments:** Station and orbital installations push toward slower drift (no natural anchors, more cosmopolitan mixing); planetary surfaces allow stronger regional drift (geographic isolation)
|
||||
3. **Meridian baseline:** Station and institutional orbital → higher baseline; planet-side rural → lower baseline
|
||||
|
||||
Everything downstream (amenities, zoning, block generation, chunk fill) runs the same logic. The geography input shapes what's available; the downstream stages fill it with culturally-appropriate content.
|
||||
|
||||
---
|
||||
|
||||
## Question 4: What makes Sova's Transit District culturally distinct from a similar district on another station?
|
||||
|
||||
This is the question the ingredients menu directly answers. Let me walk through it concretely.
|
||||
|
||||
### A "similar district" defined
|
||||
|
||||
A freight logistics district on a different station — same economic function (logistics), same setting type (station), same rough population scale (~800), similar construction era.
|
||||
|
||||
### The Sova ingredients (specific)
|
||||
|
||||
```yaml
|
||||
heritage:
|
||||
primary: frost # 0.55 — compact, reserved, patience-trust
|
||||
secondary: salt # 0.30 — pragmatic, transactional, direct
|
||||
tertiary: iron # 0.15 — labor solidarity, communal endurance
|
||||
drift_stage: mature # 180 years
|
||||
settlement_motivation: economic-trade # founded as a logistics contract, not a community
|
||||
economic_function: logistics
|
||||
economic_pressure: [tight-margin, prohibition-economy] # extra income tempting AND
|
||||
# Commission lattice regulation = access-as-contraband
|
||||
faction_presence:
|
||||
commission: intermittent # present but not dominant; gate cluster + Upstation
|
||||
concord: liaison-only # represented but distant
|
||||
syndic: significant # Sova Logistics Consortium controls employment
|
||||
philosophical_alignment: null # absent — pure pragmatism, no ideology
|
||||
meridian_coverage: degraded # structural gaps + ring exploitation of existing gaps
|
||||
```
|
||||
|
||||
### A different station's ingredients (example contrast)
|
||||
|
||||
Call it Station Vareth — also a freight logistics hub, roughly same population:
|
||||
|
||||
```yaml
|
||||
heritage:
|
||||
primary: dust # 0.60 — communal, oral tradition, public trust
|
||||
secondary: vine # 0.40 — warm, class-aware, family-connected
|
||||
drift_stage: crystallizing # 90 years — roots still distinct
|
||||
settlement_motivation: economic-agricultural # farming colony that added a logistics hub
|
||||
economic_function: logistics # same function
|
||||
economic_pressure: [status-competition, generational-extraction] # DIFFERENT pressures
|
||||
faction_presence:
|
||||
commission: standard # more coverage than Sova
|
||||
syndic: minor # smaller Syndic footprint, more independent operators
|
||||
philosophical_alignment: labor-solidarity # workers have ideological framework
|
||||
meridian_coverage: standard # better maintained, fewer gaps
|
||||
```
|
||||
|
||||
### What the player experiences differently at Station Vareth
|
||||
|
||||
**1. Investigation texture:** On Sova, silence is cultural (Frost/Salt = mind your business). On Vareth, silence is suspicious — a Dust/Vine culture talks, so NPC silence signals something specific. The detective reads the same cultural behavior (worker avoidance) as opposite signals.
|
||||
|
||||
**2. Trust-building mechanism:** On Sova, trust requires patience — shared time and shared labor over months. On Vareth, trust requires public demonstration — you earn it by acting in ways the community can see and validate. The same investigation timeline produces different access levels.
|
||||
|
||||
**3. Grey economy motivation:** On Sova, people smuggle because they're economically squeezed AND Commission regulation cuts them off from lattice upgrades they want. On Vareth, people participate in the grey economy because visible inequality (status-competition) creates social pressure to match higher earners, AND Syndic owners extract value from the community (generational-extraction). The contraband type may be similar (luxury goods, restricted equipment) but the moral texture is completely different. The detective's confrontation with ring participants lands differently.
|
||||
|
||||
**4. Social site character:** Same template type (logistics district), different NPC population distribution. Vareth's higher philosophical alignment (labor-solidarity) increases ANCHOR and SYSTEM pattern counts — more community pillars, more institutionalized labor representation. Sova's null philosophical alignment produces more NOBODYs and CIVILIANs — the grey economy is moral shelter, not ideology.
|
||||
|
||||
**5. Naming and ambient text:** Frost/Salt/Iron + mature drift produces Krenn-style compact consonant-heavy names (Kael, Voss, Drin). Dust/Vine + crystallizing drift produces different phonetics — possibly more flowing, more syllables, different stress patterns. The environment text (signs, graffiti, vendor names) sounds different.
|
||||
|
||||
**6. Access topology** (the investigation architecture): Vareth's higher Meridian coverage and better-maintained infrastructure means fewer natural dead zones. The ring (or equivalent grey economy) on Vareth had to BUILD its dead zones rather than exploit existing gaps. This affects which locations are grey-economy-accessible and how the spatial investigation works.
|
||||
|
||||
### The critical insight
|
||||
|
||||
Sova's cultural distinctiveness is not decorative — it's mechanically load-bearing. The Frost/Salt/Iron + mature-drift + tight-margin + prohibition-economy combination produces **specific investigation difficulty, specific NPC behavior patterns, and specific contraband moral texture**. Change any two ingredients and you get a materially different play experience, even in an identically-structured logistics district.
|
||||
|
||||
This is what the generator must preserve: not just that worlds look different, but that they PLAY differently because the cultural parameters drive mechanical parameters.
|
||||
|
||||
---
|
||||
|
||||
## Question 5: Where do political/economic conditions enter the pipeline?
|
||||
|
||||
**Short answer: Multiple stages, but primarily above geography and at the district skeleton.**
|
||||
|
||||
Here is my proposed entry map, following the Cities Skylines pipeline structure from the brief:
|
||||
|
||||
```
|
||||
[PRE-PIPELINE] System political classification
|
||||
→ Commission presence tier
|
||||
→ Concord Assembly reach
|
||||
→ Syndic presence scale
|
||||
→ Independent governance scope
|
||||
→ This sets the baseline institutional envelope for everything downstream
|
||||
|
||||
[GEOGRAPHY] World type + natural constraints
|
||||
→ Station / planetary / orbital determines zone-topology geometry
|
||||
→ Economic function constrains what infrastructure is possible
|
||||
|
||||
[INFRASTRUCTURE] Transport + utilities
|
||||
→ Economic function (logistics) → span gates, tram networks, freight lifts
|
||||
→ Faction presence → which infrastructure is Commission-maintained vs. independent
|
||||
→ Political conditions → maintenance allocation (the Sector 3 ventilation dispute is a
|
||||
political-condition artifact: Industrial Sector queue vs. Transit District priority)
|
||||
|
||||
[AMENITIES & SERVICES] Social site types available
|
||||
→ Faction presence → Commission office vs. workers' hall vs. independent clinic
|
||||
→ Economic pressure → what services the grey economy provides (aftermarket lattice here)
|
||||
→ Tech level (Meridian coverage) → what's surveil-able and what isn't
|
||||
|
||||
[POPULATION] Extrapolated from capacity
|
||||
→ Economic function → shift-based or continuous population flow
|
||||
→ Faction control → how much transient vs. permanent population (Commission areas
|
||||
have higher permanent fraction; transit areas have higher transient fraction)
|
||||
|
||||
[ZONING] District type assignment
|
||||
→ Society profile → what zone types are culturally plausible
|
||||
→ Faction control → which zones are institutionally controlled vs. autonomous
|
||||
→ Grey economy → dead zones and maintenance corridors as informal zoning
|
||||
|
||||
[BLOCK GENERATION] Chunk cluster arrangement
|
||||
→ Economic function → primary block type (freight staging, residential, commercial)
|
||||
→ Era stratification → block construction era affects coverage and condition
|
||||
→ Political conditions → bloc-level faction control (Commission inspection post in
|
||||
the freight block, not the maintenance block)
|
||||
|
||||
[CHUNK FILL — D-025 TEMPLATE INSTANTIATION]
|
||||
→ Society profile → NPC pattern distribution, NPC generation parameters
|
||||
→ Faction presence → which templates are eligible (Commission officer NPC in high-
|
||||
presence zones; no Commission NPCs in grey-economy chunks)
|
||||
→ Economic pressure → moral frame of grey economy participation
|
||||
→ Access tier parameters → how social sites behave toward outsiders
|
||||
```
|
||||
|
||||
### When D-025 templates get instantiated
|
||||
|
||||
The workshop brief asks specifically where triangle templates are instantiated. My recommendation: **at the district skeleton stage, after zoning but before block generation**.
|
||||
|
||||
The district skeleton generator:
|
||||
1. Receives the society profile (from ingredients)
|
||||
2. Receives the zoning output (district type, zone types)
|
||||
3. Selects D-025-compatible social site templates appropriate to this society profile
|
||||
4. Arranges them spatially (access topology)
|
||||
5. Assigns NPC pattern/motivation slots per template
|
||||
6. Establishes cross-template triangle connections (the one cross-template triangle per set, per D-024)
|
||||
|
||||
The skeleton is then handed to block generation, which places the skeleton's abstract social sites into concrete spatial blocks. Chunk fill then populates those blocks tile-by-tile.
|
||||
|
||||
### The Q-036 reconciliation
|
||||
|
||||
Q-036 asks whether the district skeleton is the atomic generator output. My worldbuilding position: **Yes, and here is the key clarification that resolves the tension with D-025:**
|
||||
|
||||
- D-025 defines social site templates as the atoms of *hand-authored* content
|
||||
- The district skeleton is the *generator's composed output* — a configuration of social site slots
|
||||
- The generator SELECTS AND ARRANGES D-025 templates; it doesn't replace them
|
||||
|
||||
Analogy: D-025 templates are bricks. The district skeleton is the architectural plan that determines which bricks go where. The generator writes architectural plans from ingredients; human authors craft the bricks. The generator never touches the bricks themselves.
|
||||
|
||||
The district skeleton as generator output contains:
|
||||
- A list of social site slots (type: logistics-hub / bar / maintenance / residential)
|
||||
- Spatial positions (approximate, for block generation to resolve)
|
||||
- Access topology (which sites are gate-adjacent, which are maintenance-adjacent)
|
||||
- NPC capacity and pattern distribution per site
|
||||
- Triangle assignments (who's in conflict with whom across templates)
|
||||
- Cultural modifier tags (which society profile produced this skeleton, for NPC generation)
|
||||
|
||||
---
|
||||
|
||||
## Summary: What worldbuilding requires of the generator
|
||||
|
||||
Drawing together the above into concrete requirements:
|
||||
|
||||
**1. Society profile as first-class data structure**
|
||||
|
||||
The generator must produce and consume a full society profile (Q-032) before any spatial generation begins. The profile drives NPC names, trust-building timelines, access tier thresholds, grey economy structure, and template selection.
|
||||
|
||||
**2. Political classification above geography**
|
||||
|
||||
Faction presence tier, Commission coverage, Syndic scale, and Concord Assembly reach must be resolved at system level before any district is generated. They constrain the entire downstream pipeline.
|
||||
|
||||
**3. Era stratification as spatial dimension**
|
||||
|
||||
Construction era is not just a visual tag. It determines Meridian coverage, access topology, and grey economy viability. The generator must tag zones and blocks by era and use those tags downstream.
|
||||
|
||||
**4. Grey economy as a negative-space element**
|
||||
|
||||
The grey economy occupies the spaces that official zoning doesn't account for. The generator must model what's NOT in the official map — which corridors are maintenance-only, which zones have dead spots, which blocks have unofficial access routes. This is not flavor; it's where the investigation happens.
|
||||
|
||||
**5. Cultural distinctiveness must be mechanically expressed**
|
||||
|
||||
Setting note — this is my strongest constraint: if cultural variation doesn't translate to mechanical variation (different investigation approach, different access timelines, different NPC behavior patterns), then 300 distinct cultural profiles produce only the illusion of variety. The pipeline must ensure that `privacy_level`, `trust.building_rate`, and `access_tier.*` parameters from the society profile actively modify NPC behavior systems.
|
||||
|
||||
**6. D-025 templates as the atoms; district skeleton as the molecule**
|
||||
|
||||
The generator arranges templates into skeletons. It does not alter the templates themselves. Hand-authored template quality is preserved; generator novelty comes from arrangement, not invention.
|
||||
|
||||
---
|
||||
|
||||
## Open questions I'm flagging for the workshop
|
||||
|
||||
**For Tyre:**
|
||||
- Can the content pipeline consume the society profile YAML format (from wiki-review R4) as a serde-compatible schema? The parameter depth is significant.
|
||||
- How does era-stratification map onto the chunk data structure? The Z-level model (D-093) is confirmed, but does the chunk system have era fields?
|
||||
|
||||
**For Gestalt:**
|
||||
- Does the NPC pattern × motivation distribution (wiki-review R4, Section 3) satisfy the gameplay guarantee requirements? Specifically: can a district skeleton guarantee "always a surveillance chokepoint" and "always a quiet zone" through pattern distribution alone?
|
||||
|
||||
**For Nigel:**
|
||||
- The cultural variation axes I've defined — `privacy_level`, `trust.building_rate`, heritage-root blend — are they sufficient replayability levers? Or do we need additional randomization in the society profile that produces surprise within a cultural type?
|
||||
|
||||
**For Araminta:**
|
||||
- The era-stratification and zone-type parameters should drive visual coherence. Are the era tags (Era 1/2/3) and zone types (logistics/maintenance/institutional/residential) sufficient input for chunk fill visual rules?
|
||||
|
||||
---
|
||||
|
||||
**Status:** Round 1 complete.
|
||||
**Author:** Miri
|
||||
**Date:** 2026-02-27
|
||||
**Cross-reference:** Wiki Review Workshop Round 4 (Miri) — ingredients menu, society profile YAML spec, NPC pattern composition rules
|
||||
@@ -0,0 +1,598 @@
|
||||
# Generator Architecture Workshop — Round 2: Miri (Worldbuilder)
|
||||
|
||||
**Topic:** Worldbuilding for the broadened gameplay lens — multiple playstyles, non-urban terrain, insignificant places, edge bleed, cultural ingredients space size
|
||||
**Date:** 2026-02-27
|
||||
**Source:** Round 1 (all participants), Qatux round notes, lead directive
|
||||
|
||||
---
|
||||
|
||||
## Acknowledging the Lead Directive
|
||||
|
||||
*This is NOT a detective game. It is a game about the inherent asymmetry of human awareness.*
|
||||
|
||||
This reframe changes what the society profile must provide. In Round 1, I designed the society profile primarily through the lens of investigation. Every parameter pointed toward: how does this culture produce different investigation difficulty? What makes the grey economy more or less visible?
|
||||
|
||||
That was too narrow. The society profile must be a **playstyle-agnostic information structure**. What the detective uses as evidence, the tycoon uses as a price advantage, the political actor uses as leverage, and the romantic pursuer uses as emotional vulnerability. The underlying architecture is the same — information asymmetry — but the information TYPE and what it UNLOCKS differs per playstyle.
|
||||
|
||||
I'll work through this systematically.
|
||||
|
||||
---
|
||||
|
||||
## Section 1: The Society Profile as a Playstyle-Agnostic Structure
|
||||
|
||||
The society profile I defined in Round 1 already contains most of what multiple playstyles need. The gap is not the profile itself but **what information categories we're tracking** and **what actions they unlock**.
|
||||
|
||||
The core claim: information asymmetry is the game's universal mechanic. What changes across playstyles is:
|
||||
- **What information is valuable** (evidence, prices, affections, power leverage)
|
||||
- **How it's accessed** (surveillance, market observation, social bonding, institutional positioning)
|
||||
- **What it unlocks** (confrontation options, trade advantages, relationship phases, power leverage)
|
||||
|
||||
The society profile governs HOW information flows (trust model, privacy level, access tiers). What needs to be added are the **information type taxonomies** — what exists to know per playstyle.
|
||||
|
||||
---
|
||||
|
||||
## Section 2: Playstyle-Specific Information Vocabulary
|
||||
|
||||
### 2.1 Investigation (existing — confirmed R1)
|
||||
|
||||
**Information type:** Evidence of hidden activities
|
||||
**Access mechanism:** Observation, physical traversal, social trust, institutional credentials
|
||||
**Unlocks:** Confrontation options, exposure, arrest, exculpation
|
||||
|
||||
Already designed. Society profile provides: `privacy_level`, `trust.building_rate`, `access_tier.*`, `grey_economy.*`.
|
||||
|
||||
### 2.2 Tycoon (economic gameplay)
|
||||
|
||||
**Information type:** Economic intelligence — prices, supply chains, trade routes, competitor knowledge
|
||||
**Access mechanism:** Market observation, supplier relationships, contraband networks, faction briefings
|
||||
**Unlocks:** Trade advantages, supply route control, economic leverage, faction debt
|
||||
|
||||
The society profile parameters that drive tycoon gameplay are already partially present, but economic information needs its own vocabulary:
|
||||
|
||||
```yaml
|
||||
economic_information:
|
||||
trade_flows:
|
||||
surplus: [grain, recycled-metal, processed-protein] # what this world produces in excess
|
||||
deficit: [lattice-components, pharmaceutical-grade, rare-fabrication-stock] # what it imports
|
||||
choke_points: [span-gate-customs, logistics-hub-manifest-processing] # where trade is regulated
|
||||
|
||||
price_differential_drivers:
|
||||
- factor: faction_control # Commission control increases lattice component prices
|
||||
- factor: supply_disruption_risk # isolated world = vulnerability premium on essentials
|
||||
- factor: seasonal_demand # agricultural worlds have harvest-cycle price swings
|
||||
|
||||
economic_actors:
|
||||
dominant: syndic-consortium # sets baseline prices, controls infrastructure
|
||||
independent: [owner-operators, ring-adjacent-traders]
|
||||
absent: guilds # no formal guild structure in this region
|
||||
|
||||
information_barriers:
|
||||
# Who knows what before whom — the tycoon's asymmetric advantage
|
||||
syndic_knows: [upcoming_supply_disruptions, contract_prices, preferred_customs_routes]
|
||||
ring_knows: [actual_manifest_discrepancies, informal_price_tolerance, which_officers_turn_blind]
|
||||
worker_knows: [shift_patterns, cargo_composition, unofficial_storage_locations]
|
||||
outsider_knows: [official_listed_prices, public_trade_statistics, nothing_useful]
|
||||
```
|
||||
|
||||
**What this does for gameplay:** A player in tycoon mode is trying to acquire the SYNDIC KNOWS tier. The ring's knowledge layer is a shortcut — but using it has risk. The outsider layer is useless. The game is about climbing the information ladder before competitors do.
|
||||
|
||||
The cultural heritage profile modifies tycoon gameplay directly:
|
||||
- **Salt-heavy societies**: transactional, information trades happen quickly and at fair rates. "Tell me what the Syndic pays for grain and I'll tell you who the next customs officer rotation is."
|
||||
- **Frost-heavy societies**: information doesn't trade. You earn it through presence. The tycoon must invest time, not favors.
|
||||
- **Iron-heavy societies**: economic information is community property. Hoarding it for personal advantage is a cultural violation. But sharing it within the labor community is expected — which gives organized workers better tycoon information than independent operators.
|
||||
|
||||
### 2.3 Dating Sim (relationship gameplay)
|
||||
|
||||
**Information type:** Social and personal knowledge — what someone wants, what they fear, who they're connected to, what their history is
|
||||
**Access mechanism:** Shared experiences, trust-building, third-party gossip, observing behavior in different contexts
|
||||
**Unlocks:** Relationship phases (warmth → intimacy → vulnerability → declaration), access to private spaces, rival network neutralization
|
||||
|
||||
The society profile already handles the TRUST MECHANISM. What's missing is the **social venue diversity** and **relationship formation norms**:
|
||||
|
||||
```yaml
|
||||
social_venues:
|
||||
# Dating sim needs more social site types than the investigation-centric design assumed
|
||||
primary:
|
||||
- type: communal_meal_space # shared eating, low-stakes interaction, natural conversation
|
||||
- type: recreational_gathering # games, sports, performance — see the person relaxed
|
||||
- type: crisis_support_space # medical, emotional — high-vulnerability, high-trust
|
||||
- type: creative_work_space # collaborative creation, reveals character under pressure
|
||||
- type: private_domestic # invited into someone's home — meaningful social threshold
|
||||
|
||||
relationship_formation_norms:
|
||||
# Heritage-root-dependent — this is one of the strongest cultural variables
|
||||
frost: |
|
||||
Relationships form through proximity and shared endurance, not explicit signals.
|
||||
Expressing affection directly is uncomfortable and slightly aggressive.
|
||||
The romantic signal is: inviting someone to a shared task.
|
||||
"I thought you might want to help with the cargo rotation" = "I want to spend time with you."
|
||||
This plays very differently for a tycoon or detective who misreads it as a work request.
|
||||
|
||||
tide: |
|
||||
Relationships form publicly, through shared food, shared celebration, introductions
|
||||
to family. The romantic signal is inclusion in social gatherings. Introducing someone
|
||||
to your family is serious. Cooking for someone is an explicit statement. The barrier
|
||||
is managing group approval — everyone's opinion matters.
|
||||
|
||||
spice: |
|
||||
Relationships form through family networks. Third-party introduction is required.
|
||||
Direct pursuit is presumptuous or inappropriate. The game is gaining approval
|
||||
from the network before approaching the person directly. This creates an
|
||||
investigation-like social puzzle: map the network, identify the influencer,
|
||||
build the right relationships in the right order.
|
||||
|
||||
salt: |
|
||||
Relationships are transactional at initiation: "this benefits both of us."
|
||||
That sounds cold but isn't — Salt cultures build real intimacy, they just
|
||||
frame it practically. "I want to spend time with you because you're useful
|
||||
to me" evolves into "I want to spend time with you because you're mine."
|
||||
The evolution is the dating sim arc.
|
||||
```
|
||||
|
||||
**Rival networks as triangle structures:** The dating sim's rival relationship is structurally identical to the investigation triangle — three people with conflicting interests. The generator's D-024 triangle model works for romantic competition: NPC A wants X, NPC B wants X, player wants X, each has different leverage. The CONTENT differs (romantic relationship vs. criminal conspiracy) but the mechanical structure is the same.
|
||||
|
||||
This means the generator's triangle instantiation logic handles dating sim mechanics without modification. What changes is the **content tags** on the triangle nodes: `motivation: romantic-rival` vs. `motivation: operator`. The NPC 10-axis model already contains `Want` (relationship goal), `Secret/vulnerability` (what they're hiding), and `Tolerance threshold` (what they'll accept) — these are exactly the dating sim mechanics.
|
||||
|
||||
**Social venue diversity as a generator requirement:** The investigation-centric design produced one primary social site type (bar — shift-end social aggregation). Dating sim gameplay requires more types:
|
||||
- **Communal meal space** (low-stakes, natural conversation — distinct from the bar's crisis-adjacent social drinking)
|
||||
- **Recreational activity venue** (sports, games, performance — see the person under relaxed conditions)
|
||||
- **Domestic invitation threshold** (being invited home is a relationship milestone, requires a distinct spatial primitive)
|
||||
|
||||
These social site types are different TEMPLATES in the D-025 library, not different pipeline stages. The generator needs a richer template pool selection at the amenities stage that includes non-bar social venues. Template pack DLC is the right model here — the base game templates cover the investigation/tycoon cases; a social expansion pack adds the dating sim template library.
|
||||
|
||||
### 2.4 Political Drama (faction gameplay)
|
||||
|
||||
**Information type:** Power intelligence — who controls what, who wants what, what compromises exist, which positions are vulnerable
|
||||
**Access mechanism:** Institutional positioning, network cultivation, leverage acquisition, surveillance of faction actors
|
||||
**Unlocks:** Alliance formation, faction control shifts, position seizure, scandal detonation, reform
|
||||
|
||||
The society profile's faction presence tier gives the LANDSCAPE but not the TEXTURE. Political drama needs the internal dynamics of each faction:
|
||||
|
||||
```yaml
|
||||
political_structure:
|
||||
power_structure_type: oligarchic # few actors with clear but contested hierarchy
|
||||
# alternatives: democratic, feudal, revolutionary, absent
|
||||
|
||||
contested_positions:
|
||||
- position: district_administrator # currently weakly held; incumbent 2 years, insecure
|
||||
competitors: [commission_regional, syndic_consortium]
|
||||
leverage_held: [infrastructure_maintenance_authority, hiring_records]
|
||||
|
||||
faction_relationships:
|
||||
# Not just presence but HOW factions relate
|
||||
commission_to_syndic: pragmatic_alliance # overlapping interests, no deep trust
|
||||
commission_to_independent: surveillance # active suspicion, soft containment
|
||||
syndic_to_workers: extractive_dependency # workers need the jobs; Syndic knows it
|
||||
|
||||
leverage_map:
|
||||
# What each faction needs from others — the political game's resource
|
||||
commission_needs: [local_cooperation, manifest_accuracy, worker_testimony]
|
||||
syndic_needs: [labor_stability, customs_efficiency, Commission_indifference]
|
||||
workers_need: [fair_wages, lattice_access, protection_from_Commission]
|
||||
ring_needs: [blind_spots, trusted_couriers, storage_access]
|
||||
|
||||
destabilizing_information:
|
||||
# Secrets that, if revealed, shift power
|
||||
- secret: "District administrator is on the Syndic's informal payroll"
|
||||
if_revealed_to: commission
|
||||
effect: position_vacancy_plus_investigation
|
||||
- secret: "Commission inspector has been running ring-adjacent favors for 3 years"
|
||||
if_revealed_to: syndic_manager
|
||||
effect: informal_coercion_leverage
|
||||
```
|
||||
|
||||
**What cultural heritage does to political drama:**
|
||||
- **Frost societies**: political conflict is cold and indirect. Faction warfare is bureaucratic, institutional, conducted through records and procedures. A Frost-dominated political drama is about paper trails and procedural capture, not public confrontation.
|
||||
- **Iron societies**: political conflict is collective and labor-organized. Factions map to economic class. Political drama is about strikes, solidarity, collective action. The unit of power is the group, not the individual.
|
||||
- **Arc societies**: political conflict is intellectual and reputational. The weapons are arguments, papers, and public debates. The person who can DEMONSTRATE they're right gains power.
|
||||
|
||||
**The political drama and investigation crossover:** Political drama is investigation with a different goal. Investigation finds truth and decides what to do with it. Political drama is finding leverage and deciding how to deploy it. The knowledge graph (D-041) is the right architecture for both — the difference is what the player chooses to DO with `KnowsDetails`-tier information. The generator doesn't need to produce different spaces for political drama; it produces spaces where power is legible, and the player decides whether to expose or exploit what they find.
|
||||
|
||||
### 2.5 The Universal Layer Beneath All Playstyles
|
||||
|
||||
What I've worked through above reveals a unified structure:
|
||||
|
||||
**Every playstyle is a different reading of the same information landscape.**
|
||||
|
||||
| Playstyle | Reads the landscape as | Primary information type | Uses knowledge to |
|
||||
|---|---|---|---|
|
||||
| Investigation | A crime scene | Evidence of hidden activities | Expose/confront/arrest |
|
||||
| Tycoon | A market | Economic intelligence | Profit/control/leverage |
|
||||
| Dating sim | A social web | Personal knowledge/vulnerability | Form bonds/navigate rivals |
|
||||
| Political drama | A power structure | Leverage points/destabilizing secrets | Shift/seize/reform power |
|
||||
| Daily life (substrate) | A home | Social texture, belonging | Exist, build attachments |
|
||||
|
||||
The generator produces ONE information landscape. What varies is which information the player's archetype seeks and what they do with it. This means:
|
||||
- The society profile doesn't need playstyle-specific fields — it needs a RICHER information taxonomy that all playstyles can draw from
|
||||
- The template library (D-025) needs richer social site variety — not just investigation-optimal spaces
|
||||
- The faction presence model needs to expose internal dynamics, not just presence tiers
|
||||
|
||||
---
|
||||
|
||||
## Section 3: Non-Urban Terrain Types and the Ingredients Menu
|
||||
|
||||
The lead directive identifies: farmland, wilderness, secluded towns, ocean, boats, ski resorts, surf beaches. These are not population hubs. They need the generator, but not the same generator.
|
||||
|
||||
My framework: **same ingredients menu, different terrain grammar, different social site library**.
|
||||
|
||||
### 3.1 Agricultural / Rural Settings
|
||||
|
||||
**Society profile parameters (typical):**
|
||||
```yaml
|
||||
heritage:
|
||||
dominant_root: stone # land-connected, traditional, tenure-trust
|
||||
secondary: tide # or vine — warm community, family bonds
|
||||
drift_stage: ancient # agricultural settlements are often the oldest
|
||||
settlement_motivation: economic-agricultural
|
||||
economic_function: agriculture
|
||||
economic_pressure: [generational-extraction, tight-margin] # landlord-tenant or margin squeeze
|
||||
philosophical_alignment: land-stewardship # or null, or religious-traditional
|
||||
faction_presence:
|
||||
commission: nominal # present in theory, rarely acts
|
||||
syndic: absent_or_minor # or a land-holding corporation (different from logistics Syndic)
|
||||
local_governance: strong # elder councils, family heads, seasonal assemblies
|
||||
```
|
||||
|
||||
**Terrain grammar — different from station/city:**
|
||||
- No Z-levels. The map is ground-level with elevation variation (hills, valleys).
|
||||
- Blocks are farmstead clusters, not building blocks. A "block" might be one farm with outbuildings.
|
||||
- Infrastructure is roads/paths and water systems, not utility corridors.
|
||||
- Social sites are dispersed: farmstead (domestic/work), market town (periodic social aggregation), local tavern/meeting hall (permanent small social site), fields/common land (semi-public work space).
|
||||
|
||||
**Key generator difference:** Population is DISPERSED, not concentrated. The 4-8 NPC cluster radius of D-025 is too tight for agricultural settings. A farmstead's "social cluster" might be 3 people across 80 tiles — the farmer, their partner, their hired hand. The social site template library needs expanded radius limits for low-density settings.
|
||||
|
||||
**The information landscape in agricultural settings:**
|
||||
- Tycoon: land rights, crop prices, water allocation, trade route access to the nearest hub
|
||||
- Investigation: boundary disputes, inheritance conflicts, who the landlord's agent actually reports to
|
||||
- Dating sim: family approval (Spice/Stone/Vine heritage roots = family network gatekeeping)
|
||||
- Political drama: who controls the local assembly, who the landlord's representative is, what the seasonal laborers want
|
||||
|
||||
**Distinctive feature:** Agricultural settings have SEASONS. The game's time system (D-031 day phases) needs a longer-period layer — annual cycles — to fully represent agricultural social dynamics. Harvest festival = the major social aggregation event. Off-season = the grey economy's opportunity window (workers have time and reduced supervision). This is a significant generator parameter that urban settings don't need.
|
||||
|
||||
### 3.2 Wilderness / Uninhabited Terrain
|
||||
|
||||
Wilderness is not a settlement — it's a **terrain type that contains no permanent social sites**.
|
||||
|
||||
**Generator grammar:**
|
||||
- No society profile (no society)
|
||||
- No social site templates
|
||||
- Zone types: forest, mountain, water, open terrain, hazard zones
|
||||
- Structures are: temporary camps, resource extraction points, abandoned installations, natural cover
|
||||
- "Population" is: traversal NPCs (hunters, scouts, lost travelers), not residents
|
||||
|
||||
**What wilderness provides the generator:**
|
||||
- **Physical drama**: terrain hazards, navigation challenges, weather effects, cover and concealment
|
||||
- **Resource nodes**: what can be extracted here — feeding the tycoon pipeline
|
||||
- **Traversal topology**: connecting hub settlements, providing routes that avoid institutional oversight
|
||||
- **Historical markers**: ruins of earlier settlements, abandoned infrastructure, graves — Ozzie's "history encoded in space" without a living community
|
||||
|
||||
**Why wilderness matters for multiple playstyles:**
|
||||
- Investigation: meeting contacts away from Meridian coverage; traversal to reach isolated evidence
|
||||
- Tycoon: resource claims, extraction rights, trade route control
|
||||
- Dating sim: the romantic retreat — being somewhere isolated creates intimacy intensity (and vulnerability)
|
||||
- Political drama: the wilderness is where power vacuums are most complete; what fills them is the political story
|
||||
|
||||
**Generator rule for wilderness:** The absence of a society profile is itself a data point. When the generator produces wilderness chunks, the political condition is "unclaimed" — and unclaimed territory is always contested, because it has no enforcement. Someone is always trying to stake a claim. This is the wilderness's faction dynamic.
|
||||
|
||||
### 3.3 Secluded Towns / Small Settlements
|
||||
|
||||
**The "insignificant place" problem is actually the secluded town problem.** A small settlement (population 50-300) is a full society but very localized. Let me address both together in Section 4. Here, I'll note what the generator needs for small-scale spatial grammar:
|
||||
|
||||
- District = the entire settlement. No sub-districts.
|
||||
- Blocks are individual buildings.
|
||||
- Social sites are the same types but much smaller.
|
||||
- The single bar (or tavern, or meeting hall) IS the entire public social life.
|
||||
|
||||
**Small settlement society profiles:**
|
||||
- High drift novelty (isolated = less cosmopolitan blending, more local invention)
|
||||
- Strong insider/outsider dynamics (everyone knows everyone; a stranger is a social event)
|
||||
- Low faction presence (either completely abandoned by institutions or ruled by a single institution with no competitors)
|
||||
- High information concentration (one person can know everything about a small settlement within a week)
|
||||
|
||||
This last point is a significant generator constraint: **in small settlements, the information asymmetry structure is INVERTED**. In a city, the player struggles to learn what's hidden because information is siloed. In a small settlement, the player potentially learns everything fast — but there's less to learn, and the NPCs know the player knows. The grey economy in a small settlement is more personal, more precarious, and more morally loaded.
|
||||
|
||||
### 3.4 Maritime / Ocean Settings
|
||||
|
||||
**Physical grammar:**
|
||||
- Water tiles as traversal terrain (not walkable by default — requires vessel or swimming)
|
||||
- Vessels as mobile social sites
|
||||
- Ports as node concentrations
|
||||
- Tidal variation (game-time-driven environmental change)
|
||||
|
||||
**Society profile — port town:**
|
||||
```yaml
|
||||
heritage:
|
||||
primary: salt # pragmatic, transactional — all ports are trading nodes
|
||||
secondary: tide # flowing, community — maritime communities are tight-knit
|
||||
tertiary: iron # solidarity — maritime labor culture is historically strong
|
||||
settlement_motivation: economic-trade
|
||||
economic_function: transit # the port exists because of what passes through
|
||||
economic_pressure: [tight-margin, opportunity-disparity] # maritime labor is hard; the cargo wealth flows through, not to
|
||||
```
|
||||
|
||||
**Vessels as special social sites:**
|
||||
|
||||
Boats are bounded, mobile, intimate — and you cannot leave. This is one of the most extreme information asymmetry environments in the game:
|
||||
- **No exit**: you cannot walk away from a conversation. Walk-away consequences (D-064) become literal — "walking away" means going below deck, not leaving.
|
||||
- **Total observation**: everyone on the vessel knows everyone's movements. There are no blind spots on a small boat.
|
||||
- **Time-pressured**: the voyage ends. What happens on the boat is either resolved before arrival or explodes at the dock.
|
||||
|
||||
Vessels require a special social site template tag: `bounded_mobile`. This tag modifies:
|
||||
- Trust-building rate: FASTER (forced proximity accelerates relationship formation — for better or worse)
|
||||
- Privacy level: MUCH LOWER (physical impossibility of privacy on a small vessel)
|
||||
- Access topology: all zones accessible to all residents (no insider/authority separation without physical space)
|
||||
|
||||
**Ocean as wilderness:** Open ocean is wilderness with water terrain. The generator grammar is identical to land wilderness but with different traversal rules and different resource nodes (fishing grounds, salvage sites, submerged infrastructure).
|
||||
|
||||
### 3.5 Tourist Economy Settings (Ski Resorts, Surf Beaches)
|
||||
|
||||
These are structurally distinctive because they have **two simultaneous population profiles**: the service worker layer and the tourist/visitor layer.
|
||||
|
||||
**Dual society profile:**
|
||||
```yaml
|
||||
resident_profile:
|
||||
heritage: [whatever the local roots are]
|
||||
economic_pressure: [tight-margin, status-competition] # service workers watch wealth flow through
|
||||
economic_function: services
|
||||
trust_building_rate: LOW_FOR_TOURISTS # "you're not one of us; you're passing through"
|
||||
|
||||
visitor_profile:
|
||||
heritage: [varies — wherever they come from]
|
||||
economic_pressure: [null] # wealthy tourists have no economic pressure
|
||||
economic_function: leisure
|
||||
trust_building_rate: HIGH_FOR_LOCALS # "I'm here to relax, you're interesting, let's talk"
|
||||
# Visitor trust dynamic INVERTS: tourists are easy to befriend
|
||||
# but the friendship has a countdown (departure date)
|
||||
```
|
||||
|
||||
**The generator implication:** Tourist economy settings need two NPC pools with different social behaviors. Service worker NPCs behave like Iron/Salt/Frost cultural types in the workforce (reserved, labor-solidarity). Tourist NPCs behave like visitors — OPEN, friendly at surface, but with no investment in the place and no loyalty to its community.
|
||||
|
||||
The class contrast is explicit and spatial:
|
||||
- The beach/slope is public territory: tourists and workers briefly co-present
|
||||
- The worker housing and break rooms are insider territory: tourists excluded
|
||||
- The luxury accommodation is restricted territory: workers enter only in service capacity
|
||||
|
||||
This three-zone access structure maps cleanly onto Gestalt's access tier model (public/semi-public/private/restricted). The tourist economy setting is a natural generator case for teaching players about access tier systems — the gradient is visible and experiential.
|
||||
|
||||
**Why this matters for multiple playstyles:**
|
||||
- Tycoon: the money flows between visitor and resident. Who controls the access points controls the money.
|
||||
- Investigation: the impermanence of tourist population makes witness tracking harder. "She was here last week" is meaningless if the witness left on Sunday.
|
||||
- Dating sim: the vacation romance — a relationship with a departure date. Information asymmetry in its most poignant form.
|
||||
- Political drama: the resort's owner/operator versus the workers versus the tourists versus the environmental/planning authority. Classic conflicting interests.
|
||||
|
||||
---
|
||||
|
||||
## Section 4: Insignificant Places — The Worldbuilding of Unremarkable
|
||||
|
||||
*Not every world is center-stage. Backwaters, in-betweens, unremarkable stops. What makes a place insignificant in worldbuilding terms? How does the society profile handle "nothing special happens here"?*
|
||||
|
||||
### 4.1 What "Insignificance" Actually Is
|
||||
|
||||
Insignificance is not a property of the society profile. It's a **relation** — a place is insignificant RELATIVE to the wider network. Sova is insignificant relative to a Core World hub. Sova is enormously significant to Sector 3 residents whose entire lives are bounded by the station.
|
||||
|
||||
Scale of significance:
|
||||
- **Network-significant**: Located at a trade/gate chokepoint; Commission attention; Syndic investment; people come here for reasons
|
||||
- **Regionally significant**: Important within a cluster of nearby worlds; known to adjacent populations; occasionally in regional news
|
||||
- **Locally significant**: The center of its own community's world; matters deeply to the people there; invisible to outsiders
|
||||
- **Marginally located**: Transit stop only; no one lives here by choice; minimal community
|
||||
|
||||
What changes between levels: **Faction pressure**, **economic investment**, and **external attention**.
|
||||
|
||||
### 4.2 The "Insignificant" Society Profile
|
||||
|
||||
A backwater settlement:
|
||||
```yaml
|
||||
network_position: marginal_located # NOT strategically important
|
||||
faction_presence:
|
||||
commission: absent # not worth the budget
|
||||
syndic: absent # nothing to extract at scale
|
||||
local_governance: informal # self-governing by default, not by charter
|
||||
economic_pressure: [null] # no one is squeezing — there's nothing to squeeze
|
||||
economic_function: subsistence # or tourism, if lucky
|
||||
strategic_value: minimal
|
||||
```
|
||||
|
||||
**What this produces culturally:**
|
||||
|
||||
High `drift_novelty` — left alone, the community has developed genuine local quirks that more "significant" places have smoothed out in favor of cosmopolitan legibility. The insignificant place is often the most culturally DISTINCTIVE.
|
||||
|
||||
High `insider_trust_threshold` — strangers rarely come. When one does, everyone notices. The player is an event.
|
||||
|
||||
Low `information_density` — less is happening. But what IS happening is more visible. There are fewer layers of institutional obfuscation. The grey economy, if present, is one person in one back room, not a logistics ring spanning 200 workers.
|
||||
|
||||
**The paradox of the insignificant place:** For gameplay, insignificant places are often MORE dramatically interesting than significant ones. The conspiracy in an insignificant place is:
|
||||
- More personal (it's two or three people, not a network)
|
||||
- More visible (the community is small; secrets can't stay hidden forever)
|
||||
- More morally loaded (the stakes are local — what you do here affects everyone who lives here)
|
||||
- More unique (not the same plot as the big-hub adventure)
|
||||
|
||||
The generator should not treat insignificance as "less content to generate." It should treat it as a different content TYPE: intimate, local, high-stakes-for-small-scale.
|
||||
|
||||
### 4.3 The Significant and the Unremarkable — Contrast Design
|
||||
|
||||
For the 300-world model to avoid Second Station Syndrome (Ozzie's Sin #1), significant and insignificant worlds must feel categorically different, not just scale-adjusted.
|
||||
|
||||
Significant hub:
|
||||
- Multiple social sites
|
||||
- Faction pressure visible in architecture
|
||||
- Strangers are unremarkable
|
||||
- Player can be anonymous
|
||||
|
||||
Insignificant backwater:
|
||||
- One social site (the gathering place) serves all functions
|
||||
- No faction infrastructure
|
||||
- Player is immediately noticed and remembered
|
||||
- Player cannot be anonymous — everyone learns their name within hours
|
||||
|
||||
**Generator constraint from this:** The skeleton must parameterize `anonymity_baseline` — how visible is a new arrival? On Sova Transit District, a new face in the bar is unremarkable; hundreds pass through. In a village of 60 people, a new face is the news of the week. The player's information management challenge INVERTS: not "discover what's hidden" but "manage that you can't hide anything."
|
||||
|
||||
---
|
||||
|
||||
## Section 5: Edge Bleed — Cultural Zones Across Administrative Boundaries
|
||||
|
||||
*Districts are not islands. Cultural zones bleed across administrative boundaries.*
|
||||
|
||||
### 5.1 What Edge Bleed Is
|
||||
|
||||
An administrative boundary is a line on a map. Cultural reality does not respect it.
|
||||
|
||||
- The worker housing district bleeds into the freight district they walk through every day
|
||||
- The Commission-controlled gate cluster's institutional culture bleeds into the adjacent terminal
|
||||
- The market district's merchant culture bleeds into the first two blocks of the residential district
|
||||
- The old maintenance corridor subculture bleeds into any building with basement access
|
||||
|
||||
**Types of bleed:**
|
||||
|
||||
| Type | Direction | Mechanism |
|
||||
|---|---|---|
|
||||
| **Economic bleed** | From economically active to passive zones | Vendors set up just past the zone boundary; commercial behavior follows foot traffic |
|
||||
| **Faction bleed** | From high-control to low-control zones | Informants live in residential; Commission authority diffuses as social norm beyond formal boundary |
|
||||
| **Cultural bleed** | Bidirectional, slow | Heritage practices, language, naming conventions spread gradually into adjacent communities |
|
||||
| **Physical bleed** | From buildings/infrastructure | A building straddling a boundary creates ambiguous jurisdiction |
|
||||
| **Information bleed** | Bidirectional, fast | Gossip, news, rumors don't respect borders; the bar on the edge of two districts is the information exchange |
|
||||
|
||||
### 5.2 How the Generator Models Edge Bleed
|
||||
|
||||
Round 1's society profile describes a district as having one society profile. But real cultural geography is gradients, not flat fills.
|
||||
|
||||
**Proposed model: bleed gradient with decay distance**
|
||||
|
||||
At the center of a district, the society profile applies at 100% intensity. At the boundary, it's blended with adjacent districts. The blend distance is:
|
||||
- Short (2-4 blocks): sharp cultural boundary — different language, different customs, minimal mixing. This happens when the communities have high cultural distance AND the boundary is physically marked.
|
||||
- Medium (5-8 blocks): gradual transition. NPCs near the boundary speak with slight mixing; social norms are flexible; spaces serve both cultures. This is the normal case for adjacent districts with moderate cultural distance.
|
||||
- Long (9-16 blocks): extended transition — one culture is subordinate to the other, or both are very similar. This happens when two districts share heritage roots or when one has been culturally dominant for long enough to colonize the adjacent space.
|
||||
|
||||
**Cultural distance function:**
|
||||
|
||||
Two society profiles are CLOSE if they share: heritage roots, economic function, or similar faction presence tiers.
|
||||
Two society profiles are FAR if they differ on: heritage roots (incompatible social styles), economic function (creates class distance), faction presence (one is under heavy institutional pressure, the other isn't).
|
||||
|
||||
Cultural distance drives bleed distance inversely: HIGH cultural distance = SHORT bleed zone (cultures resist each other). LOW cultural distance = LONG bleed zone (cultures flow together).
|
||||
|
||||
**Example: Sova Station**
|
||||
- Transit District ↔ Residential Core: medium cultural distance (similar heritage, different function). Medium bleed zone. Workers commuting between them carry Transit District culture into the Residential Core gradually; Residential Core's domestic culture bleeds back.
|
||||
- Transit District ↔ Administrative Hub: HIGH cultural distance (Frost-labor culture vs. institutional-authority culture; completely different faction presence tiers). Short bleed zone. The boundary feels hard.
|
||||
- Residential Core ↔ Commercial Quarter: Low cultural distance (similar population, different commerce level). Long bleed zone. The neighborhood-adjacent-to-commercial-district feels like the commercial district in texture.
|
||||
|
||||
### 5.3 Social Sites at Boundaries as Information Exchanges
|
||||
|
||||
The most interesting NPCs in the game are the ones who live on cultural boundaries. They have access to both cultures. They're trusted by neither completely, which makes them interesting for investigation (they see both sides) and dating sim (their dual identity is its own drama).
|
||||
|
||||
**Generator rule:** The boundary bleed zone should contain at least one social site with MIXED cultural access tiers — where both adjacent district cultures feel equally eligible. This is the border bar, the neutral ground café, the market stall that serves both communities.
|
||||
|
||||
These mixed social sites are generators of:
|
||||
- Cross-triangle triangles (D-024 cross-template triangles — the members literally live in different districts)
|
||||
- Translation figures (NPCs who move between cultures — high information value)
|
||||
- Cultural friction (the place where Heritage Root A and Heritage Root B interact, which means their TRUST MODELS interact — and different trust models produce misunderstandings, offenses, and unexpected alliances)
|
||||
|
||||
### 5.4 Faction Bleed vs. Cultural Bleed
|
||||
|
||||
These are distinct types that behave differently:
|
||||
|
||||
**Faction bleed** decays in a radius from faction infrastructure. A Commission checkpoint creates surveillance culture (NPCs modify behavior) for ~3-5 blocks in any direction, regardless of district boundaries. The formal jurisdiction stops; the social norm doesn't.
|
||||
|
||||
**Cultural bleed** follows foot traffic patterns more than distance. Culture flows along the routes people actually walk. A maintenance corridor that connects two districts of very different cultures is a cultural conduit — the workers who use it daily carry elements of each culture to the other.
|
||||
|
||||
**The generator should model both separately:**
|
||||
- Faction bleed: a `radius_effect` from faction infrastructure, decaying by block distance
|
||||
- Cultural bleed: a `flow_path_effect` along actual NPC movement corridors, strongest along high-traffic routes
|
||||
|
||||
This distinction matters for gameplay: the detective can predict faction bleed (it's geometric). Cultural bleed is harder to predict — you have to know how people actually move.
|
||||
|
||||
---
|
||||
|
||||
## Section 6: Answering Nigel's Question — Cultural Ingredients Space Size
|
||||
|
||||
Nigel asked directly: "How large is the cultural ingredients space? The variety payoff depends on how many distinct ingredient combinations produce distinguishable district personalities."
|
||||
|
||||
### 6.1 The Combination Count
|
||||
|
||||
Let me enumerate this properly:
|
||||
|
||||
**Heritage Root blending** (primary driver of cultural feel):
|
||||
- 10 roots available
|
||||
- Select 1-3 (with blend weights at 0.1 granularity for meaningful differences)
|
||||
- Pure single-root: 10
|
||||
- Two-root blends: C(10,2) × ~5 weight distributions = 45 × 5 = 225
|
||||
- Three-root blends: C(10,3) × ~10 weight distributions = 120 × 10 = 1,200
|
||||
- **Total: ~1,435 meaningfully distinct heritage profiles**
|
||||
|
||||
**Settlement Motivation:** 9 types + NULL = 10 states
|
||||
|
||||
**Economic Function:** 11 types (10 + mixed/diversified) + NULL = 12 states
|
||||
|
||||
**Economic Pressure:** 7 types, pick 0-2 = 1 (null) + 7 (single) + 21 (pairs) = **29 states**
|
||||
|
||||
**Drift Stage:** 4 stages (pioneer / crystallizing / mature / ancient)
|
||||
|
||||
**Faction Presence:** 3 institutions (Commission, Syndic, Assembly/local-governance) × 5 presence levels (comprehensive / standard / intermittent / absent / hostile) = 5³ = 125 combinations; realistically ~30-40 plausible combinations
|
||||
|
||||
The raw combination count is astronomical. But "distinct for the player" is a tighter constraint.
|
||||
|
||||
### 6.2 The Gameplay-Distinguishable Space
|
||||
|
||||
Five parameters drive most of the GAMEPLAY FEEL differentiation:
|
||||
|
||||
| Parameter | Distinguishable states | Driver |
|
||||
|---|---|---|
|
||||
| Heritage Root primary + secondary | ~100-150 (blends, accounting for dominance) | Social style, trust mechanism, naming feel |
|
||||
| Economic Pressure combination | ~20 | Grey economy moral texture |
|
||||
| Faction Presence tier | ~12 | Investigation difficulty, access structure |
|
||||
| Drift Stage | 4 | How "foreign" the culture feels |
|
||||
| Economic Function | ~8 (plus 3 terrain types) | Daily rhythm, investigation vector |
|
||||
|
||||
Rough gameplay-distinguishable space: 100 × 20 × 12 × 4 × 8 = 768,000 combinations before overlap. With a generous overlap factor of ~100× (many combinations produce similar GAMEPLAY even if culturally distinct): **~7,700 meaningfully distinct game-mechanical experiences**.
|
||||
|
||||
**My answer to Nigel:** The ingredients space is not a limitation at 300 worlds. Even with aggressive pruning for plausibility (many combinations are impossible or implausible — a Commission-absent world with comprehensive Meridian coverage, for instance), the playable space exceeds 1,000 truly distinct district personalities. At 300 worlds, we're sampling a small fraction of the available space.
|
||||
|
||||
Nigel's calculation (300 × 2 characters × 20 cultural compositions = 12,000 games) was using the conservative "20 distinct compositions" assumption. The actual distinguishable composition space is ~1,000-7,700+. His calculation scales accordingly: **300 × 2 × 100 minimum cultural compositions = 60,000 meaningfully distinct games before factoring seed variation**.
|
||||
|
||||
**One caveat:** The practical limit is not the combination space but the **authored template library depth**. If we only have 10 D-025 templates, the 100th cultural composition will still draw from the same 10 templates. Cultural variety without template variety means the CULTURAL feel changes but the SPATIAL feel repeats. Template library expansion is the binding constraint, not the ingredients space.
|
||||
|
||||
### 6.3 The DLC Model for Template Expansion
|
||||
|
||||
Setting note — the lead directive mentions "Template packs per DLC is a valid expansion model." This is correct and the ingredients menu makes it tractable.
|
||||
|
||||
**How it works:**
|
||||
- The base game ships templates for: logistics, residential, administrative, bar/social, maintenance, gate cluster
|
||||
- DLC pack "Agricultural Worlds" adds: farmstead, granary, rural tavern, market day, seasonal camp, mill complex
|
||||
- DLC pack "Maritime Settlements" adds: fishing dock, harbor bar, vessel interior, lighthouse, chandlery
|
||||
- DLC pack "Leisure Economies" adds: resort lodge, surf shack, mountain chalet, seasonal service housing
|
||||
|
||||
Each DLC pack expands which templates are eligible for each ingredient combination. The ingredients menu and society profile remain unchanged — new DLC just extends the template pool that the generator draws from.
|
||||
|
||||
This is the correct DLC model because: players who don't buy the DLC don't encounter broken world generation. They just don't see those setting types. The generator gracefully falls back to base game templates if a DLC template is selected but unavailable.
|
||||
|
||||
---
|
||||
|
||||
## Summary: What Round 2 Adds to the Generator Architecture
|
||||
|
||||
**New contributions:**
|
||||
|
||||
1. **Playstyle-agnostic information vocabulary** — society profile extended with economic information taxonomy, relationship formation norms (heritage-root-dependent), power structure internals. All playstyles read the same generated landscape through different lenses.
|
||||
|
||||
2. **Non-urban terrain grammar** — same ingredients menu, five new terrain types with different spatial primitives:
|
||||
- Agricultural: dispersed farmstead clusters, seasonal cycles, expanded D-025 radius limits
|
||||
- Wilderness: no society profile; resource nodes, traversal terrain, historical markers only
|
||||
- Small settlements: district = entire settlement; high anonymity risk; inverted information asymmetry
|
||||
- Maritime: water terrain tiles, vessel as bounded-mobile social site, port node concentration
|
||||
- Tourist economy: dual NPC population profiles, explicit class contrast, time-bounded visitor relationships
|
||||
|
||||
3. **Insignificant places** — insignificance as a relational parameter, not a content-reduction parameter. The "insignificant place" society profile produces: high drift novelty, high insider threshold, inverted anonymity, intimate conspiracy scale. Distinct content type, not scaled-down hub.
|
||||
|
||||
4. **Edge bleed** — two distinct types (faction bleed = radius-geometric; cultural bleed = flow-path-along-movement-routes). Bleed distance driven by cultural distance function. Boundary social sites as cross-cultural information exchanges.
|
||||
|
||||
5. **Cultural ingredients space size** (answering Nigel) — raw combination space is hundreds of thousands; gameplay-distinguishable space is ~1,000-7,700+ compositions. 300 worlds uses ~5-30% of the available variety. Binding constraint is template library depth, not ingredients space.
|
||||
|
||||
6. **DLC as template library expansion** — ingredients menu stays stable; DLC adds eligible templates per ingredient combination. Correct model for extending to new setting types.
|
||||
|
||||
---
|
||||
|
||||
**Status:** Round 2 complete.
|
||||
**Author:** Miri
|
||||
**Date:** 2026-02-27
|
||||
**Questions for Round 3:**
|
||||
- Gestalt: Does the playstyle-agnostic information vocabulary require changes to the D-025 template spec, or just content tags on template nodes?
|
||||
- Tyre: Can the `bounded_mobile` social site flag work within the existing chunk architecture? (Vessels move — this might require an entity-carried chunk, which is architecturally complex.)
|
||||
- Nigel: Does cultural ingredients space calculation satisfy your variety guarantee? Does the binding constraint (template library depth) change your replayability architecture?
|
||||
- Araminta: Non-urban terrain types need different visual grammar inputs (no zone palettes for wilderness, different material vocabulary for agricultural settings). Are the era-tag and zone-type inputs sufficient to drive visual coherence in these settings, or do we need new parameters?
|
||||
@@ -0,0 +1,290 @@
|
||||
# Generator Architecture Workshop — Round 3 Supplement: Miri (Worldbuilder)
|
||||
|
||||
**Topic:** Destructible boundaries, vertical social stratification, entity-carried chunks (trains/ships), grid vs. organic by heritage root
|
||||
**Date:** 2026-02-27
|
||||
**Context:** This supplements miri-round3.md, which was filed before the full Round 3 broadcast arrived. Four directives from the broadcast required worldbuilding perspective that the main document didn't address.
|
||||
|
||||
---
|
||||
|
||||
## Supplement 1: Destructible Boundaries — What's Behind the Wall
|
||||
|
||||
*Lead directive: what's behind a wall the player blows open?*
|
||||
|
||||
This is a pure worldbuilding question before it's a technical one. The answer depends on WHAT THAT WALL IS DOING in the cultural and historical context it was built in.
|
||||
|
||||
### 1.1 Wall Typology — Why Walls Exist
|
||||
|
||||
Walls are built for specific reasons, and the reason determines the worldbuilding content on the other side:
|
||||
|
||||
**Privacy walls** — separating domestic/private space from public/semi-public space. Common in Spice, Jade, and Frost heritage settings. What's behind them: domestic life, family space, private arrangements that weren't meant to be observed. The content is intimate, often mundane, occasionally revealing.
|
||||
|
||||
**Authority walls** — defining institutional territory. What Commission jurisdiction looks like physically: reinforced barriers, access control, clearly marked transitions. Behind Commission authority walls: operational infrastructure (records storage, personnel areas, evidence holding). The content is institutional — paperwork, equipment, records of what the Commission has been doing.
|
||||
|
||||
**Security walls** — concealing high-value assets. Syndic logistics operations, grey economy storage, faction caches. These are built to HIDE something. What's behind them is the thing they were hiding. The content is the grey economy made physical: unlicensed inventory, contraband, documentation of prohibited activities, sometimes people.
|
||||
|
||||
**Structural walls** — load-bearing divisions that weren't primarily about separation but became that. What's behind them depends on era tag: Era 1 walls sealed over may contain original infrastructure (utility runs, passages that were blocked when the era changed), sometimes abandoned equipment or materials left in place when the area was repurposed.
|
||||
|
||||
**Cultural/heritage walls** — built for reasons that only make sense in specific heritage contexts. A Spice-heritage compound wall defines family honor territory. A Frost-heritage barrier between residential and operational is about psychological separation (noise, smell, the intrusion of public life). An Iron-heritage wall around the union hall is a claim of territory.
|
||||
|
||||
### 1.2 Heritage Root and What the Breach Means
|
||||
|
||||
Blowing open a wall is not culturally neutral. What matters is not just what's there but **how the community interprets the act of breach**.
|
||||
|
||||
**Frost heritage:** Walls are the physical expression of privacy as a value. Breaking through one is a profound violation regardless of what's found. A Frost community will respond to an unauthorized breach even if the contents are innocent. The breach itself is the offense. For the investigator or assassin: entering a Frost space through a destroyed wall marks them as someone who doesn't respect boundaries — which in a Frost culture is a serious social flag.
|
||||
|
||||
**Iron/Dust heritage:** Walls define collective territory. Breaching a wall into a union hall or communal storage means intruding on the group's shared space — it's an attack on the collective. The community responds collectively. The content behind the wall (collective resources, organizing records, mutual aid supplies) is community property, and damaging access to it is an attack on the community.
|
||||
|
||||
**Spice heritage:** The family compound wall is honor-adjacent. A breach is an insult to the family. What's inside is family-private space — domestic, intimate, potentially containing the family's most protected relationships and secrets. The breach creates a vendetta obligation in some Spice-heritage contexts.
|
||||
|
||||
**Salt heritage:** Walls are contractual, not sacred. If you breach a wall, you owe something for it. The content is commercial inventory, records, assets. A Salt community may tolerate the breach if appropriate compensation is offered. They'll want payment.
|
||||
|
||||
**Arc heritage:** Walls around intellectual/operational spaces contain RECORDS. The Arc community will care intensely about the integrity of what's behind — not the breach itself but the potential disruption to ordered knowledge. Behind an Arc wall: labeled storage, research records, documentation of ongoing work. The content is information in organized form.
|
||||
|
||||
### 1.3 Era Tag and Physical Contents
|
||||
|
||||
The era tag on the wall's block determines what's behind it structurally:
|
||||
|
||||
**Era 1 wall (sealed over time):** The oldest sealed spaces contain the building's original purpose. In a station, this means the infrastructure of earliest construction — original transit passages, the plumbing and electrical paths laid before the upper layers, sometimes abandoned rooms that were simply walled off when the superstructure changed. Content includes original structural materials (different from the visible surface), possibly Era 1 equipment abandoned in place, historical markers (founding dates, workers' marks in the walls, construction refuse).
|
||||
|
||||
**Era 2 wall (intermediate period):** This was sealed during the commercial/operational expansion. Behind it: infrastructure supporting an intermediate-era function that's no longer visible from the current space. A logistics terminal that became a residential corridor — the Era 2 wall might conceal cargo-bay equipment, commercial-grade storage installations, commercial transit systems. Also: the era transition often produced conflict. Era 2 walls may seal spaces that were abandoned during economic disruption — with the contents of that disruption preserved.
|
||||
|
||||
**Era 3 wall (recent):** Recent walls are planned. What's behind them is known — or should be. The absence of official knowledge about Era 3 content is itself suspicious. A recently sealed wall in an active district with no official record of what's there: someone sealed something deliberately and recently.
|
||||
|
||||
### 1.4 Generator Requirement
|
||||
|
||||
**Every block face that can be breached must carry a `behind_boundary` descriptor:**
|
||||
|
||||
```yaml
|
||||
behind_boundary:
|
||||
content_type: private_domestic | authority_operational | economic_storage |
|
||||
structural_original | abandoned_era | active_concealment
|
||||
era: era1 | era2 | era3
|
||||
cultural_sensitivity: low | medium | high | extreme
|
||||
# extreme = Spice family compound; Iron union hall; Frost private residential
|
||||
contents_hint: null | infrastructure | records | inventory | persons | evidence
|
||||
breach_consequence:
|
||||
immediate: null | alarm | NPC_response | environmental_hazard
|
||||
social: none | community_sanction | faction_response | vendetta_trigger
|
||||
```
|
||||
|
||||
The `behind_boundary` descriptor is generated at Phase 1 (district skeleton / block planning) and stored in the `BlockSkeleton`. It is NOT revealed to the player until the wall is breached — but it informs the generation of what gets spawned when the breach occurs.
|
||||
|
||||
---
|
||||
|
||||
## Supplement 2: Vertical Scale — The 50-Floor Skyscraper
|
||||
|
||||
*Lead directive: how does a 50-floor skyscraper emerge from the generator?*
|
||||
|
||||
### 2.1 Why Vertical Space Exists in Specific Cultures
|
||||
|
||||
Vertical architecture is not culturally neutral. Cultures build tall for different reasons, and the reason shapes the internal vertical social organization:
|
||||
|
||||
**Corporate/Syndic cultures** build tall for efficiency and status display. The building is an asset and a symbol. Height signals wealth. This creates the classic vertical hierarchy: lobby (public/commercial), office floors (operational), executive floors (top). Frost + corporate economic function produces this pattern reliably.
|
||||
|
||||
**Institutional/Commission cultures** build tall for administrative organization and security. The building is infrastructure. Height creates defensible institutional core. The pattern is different: lower floors are public-facing (reception, public services), middle floors are operational, upper floors are not executive suites but records and security operations. Access tightens as you go up.
|
||||
|
||||
**Labor/Iron cultures** do NOT build tall by choice. Vertical housing is often imposed by economic constraint (urban density) or by prior ownership (inhabiting a building built by a different cultural actor). Iron-heritage communities in vertical buildings create horizontal solidarity networks across floors — the floor as community unit, not the building as hierarchy.
|
||||
|
||||
**Spice-heritage communities** adapt vertical architecture to family organization. The extended family may occupy multiple floors of the same building with internal connections between floors — a vertical family compound. Outsiders may occupy other floors of the same building with no relationship to the Spice-family floors.
|
||||
|
||||
### 2.2 The Vertical Social Hierarchy
|
||||
|
||||
The Z-level model established in D-093 (z=0 maintenance, z=1 operational, z=2 institutional/gate) maps onto vertical buildings differently than onto the station's broader district structure.
|
||||
|
||||
In a tall building within a station or urban setting:
|
||||
|
||||
| Floor range | Social function | Typical occupants | Cultural variation |
|
||||
|---|---|---|---|
|
||||
| Ground (z=0 equiv.) | Public-facing commerce or passage | Anyone; high traffic | Little — street interface is universal |
|
||||
| Lower floors (z=1-3) | Work functions, commercial operations | Workers, service functions | Heavy — Iron cultures push worker facilities down; Frost cultures push worker facilities down differently (clean separation) |
|
||||
| Mid floors | Administrative, secondary residential | Mixed | Moderate — depends on economic function |
|
||||
| Upper floors | Residential (valuable) OR operational (secure) | Wealth OR security | Strongest cultural variation here |
|
||||
| Top floors | Penthouse OR secure operations | Varies entirely by culture | Frost-corporate = executive; Commission-Jade = secure records; Arc = observatory/library; Iron = reclaimed community |
|
||||
|
||||
**The important conflict:** In station architecture, z=0 is MAINTENANCE (lowest status); z=2 is institutional/gate (highest). In standard tall buildings, the top floor is highest status. When a tall building is grafted onto station spatial logic, these conventions can CONFLICT — creating the visible cultural tension of "who actually has power here?" The building where the maintenance workers are on the same floor as the executive offices (because the executive floor is near the gate infrastructure) tells you something specific about this station's power structure.
|
||||
|
||||
### 2.3 Generator Requirements for Vertical Scale
|
||||
|
||||
The current D-094 hierarchy (region/district/block/chunk) handles z-levels via stacked chunk layers. A 50-floor skyscraper is not 50 district-level z-levels — it's 50 chunk-stack layers within a SINGLE block footprint.
|
||||
|
||||
**Setting note for Tyre:** A skyscraper is a multi-z-level single-block structure with:
|
||||
- One or more block footprints on the ground level
|
||||
- N chunk layers stacked, each 64×64 tiles, each z-level a floor
|
||||
- The social profile of the BUILDING follows the vertical hierarchy rules above
|
||||
- Each z-level chunk inherits era tags from the block but has a separate `floor_function: FloorFunction` field
|
||||
|
||||
The generator needs to know, at block planning time (Phase 1 Stage 3), whether a block is a TOWER BLOCK — flagged to generate multiple z-levels rather than a single operational level. This is a `BlockSkeleton` property: `tower: Option<TowerConfig>` where `TowerConfig` records number of floors, floor function assignments, and the vertical social profile derived from the society profile.
|
||||
|
||||
**The worldbuilding content per floor is derived from the vertical social hierarchy table above.** The generator applies the table with heritage root modifiers. An Iron-heritage corporate building and a Frost-heritage corporate building with the same number of floors will have their social zones distributed differently.
|
||||
|
||||
### 2.4 What Makes the Skyscraper Interesting Gameplay-Wise
|
||||
|
||||
**Vertical information asymmetry:** People on the upper floors have information about what happens on the lower floors (they commissioned it, they receive reports, they ordered it). People on the lower floors have information about the upper floors that upper-floor residents don't know they have (maintenance workers know what equipment is running, what's being shipped, who visits). The skyscraper is a compressed information gradient. The investigator ascending through a corporate tower is climbing the information ladder physically.
|
||||
|
||||
**Cross-floor social triangles:** A triangle where NPC A is on floor 3, NPC B is on floor 17, and NPC C is on floor 42 is a cross-floor triangle. The staging ground challenge is different — the player needs ACCESS to multiple floors to work the triangle. The access tier system maps to floors in a tower, not just horizontal zones.
|
||||
|
||||
**The assassination tower problem:** A high-value target on the 42nd floor has an implied protection architecture: limited vertical access, controlled entry points, security in the vertical corridor. The assassin's problem is a vertical architecture puzzle. Escape routes are the same — you can go down but not sideways until you exit the building. The informal zone in a corporate tower is the maintenance infrastructure (utility shafts, service elevator, roof access).
|
||||
|
||||
---
|
||||
|
||||
## Supplement 3: Entity-Carried Chunks Beyond Vessels — Trains and Spaceships
|
||||
|
||||
I covered maritime vessels in Round 2. Round 3 makes entity-carried chunks CORE and adds trains and spaceships. The worldbuilding differs meaningfully per vehicle type.
|
||||
|
||||
### 3.1 Trains — The Bounded Linear Society
|
||||
|
||||
The maritime vessel is bounded-spherical (you can move throughout the vessel, which is a small total space). A train is **bounded-linear** — you can move through it from end to end, but the ends are as far apart as they are.
|
||||
|
||||
**The train's social grammar:**
|
||||
|
||||
Trains compress multiple social tiers into a physical sequence. The typical car sequence (from front/premium to rear/economy): observation/premium → standard passenger → general class → cargo. This is a PHYSICAL EXPRESSION of the social hierarchy that the player can observe simply by walking through the train.
|
||||
|
||||
Heritage root variation on train social organization:
|
||||
- **Frost heritage passenger car**: minimal interaction, compartmentalized seating, high privacy expectation. Everyone minds their own business. Information is not shared.
|
||||
- **Tide heritage passenger car**: communal orientation, groups eat together, children move between cars, conversation across compartment boundaries is normal. Information flows freely — and includes information about everyone on the train.
|
||||
- **Iron heritage work train** (carrying labor to a site): collective space, political awareness, workers know each other, mutual solidarity active. An outsider on an Iron labor train is immediately noticed.
|
||||
|
||||
**What makes trains distinctive for gameplay:**
|
||||
|
||||
- **Temporal pressure**: the destination is known and approaching. Whatever needs to happen on the train must happen before arrival.
|
||||
- **Witness compactness**: everyone who matters to the scenario is on the same vehicle. No one leaves. Cross-examination and confrontation are possible in ways that aren't in open-world settings.
|
||||
- **Social compression**: the shared experience of travel creates artificial intimacy. Dating sim dynamics accelerate on trains because you're physically in close proximity with the same people for extended periods.
|
||||
- **Information lock**: what happens on the train is sealed until arrival. The investigation play on a train is fully contained — but also can't get outside support.
|
||||
- **Assassination on a train**: a CLASSIC scenario type for a reason. The target can't escape. The witnesses can't leave. The aftermath begins at arrival. The assassin's problem is managing all of this in a confined linear space.
|
||||
|
||||
**Generator requirement for trains:**
|
||||
`SettingType::BoundedLinear` — a variant of the `bounded_mobile` tag. The chunk is mobile, carried by an entity (the train), and has a **linear access topology** (you can progress from one end to the other, with optional locked compartments). The social site tags on a train are car-typed: `premium_car`, `general_car`, `cargo_car`, `service_car`, each with its own social density and access rules.
|
||||
|
||||
### 3.2 Spaceships — The Total Information Environment
|
||||
|
||||
A spaceship in transit is the most extreme information asymmetry environment in the setting. Not because it's bounded (so is a vessel or a train) but because it is **between systems**.
|
||||
|
||||
**What "between systems" means culturally:**
|
||||
|
||||
During interstellar transit via the horizon gate network, the ship is in the gate conduit — physically in neither origin nor destination system. Normal institutional jurisdiction is suspended (which Commission has authority in transit?). Normal communication with external parties is interrupted (no message traffic possible in transit). The crew and passengers are alone in a way that no other setting achieves.
|
||||
|
||||
The sociological effect: **transit strips social roles**. The powerful person on the ship cannot call for backup. The institutional authority figure cannot enforce through external threat. The information bottleneck is total — nothing you know or reveal can exit the ship until arrival.
|
||||
|
||||
Heritage root behavior during spaceship transit:
|
||||
- **Frost**: doubles down on privacy. In a suspended institutional environment, the default is LESS interaction, not more. Information is even more guarded.
|
||||
- **Tide**: expands. The community of the ship becomes the relevant community for the duration. Social bonds form faster. Information flows within the ship-community.
|
||||
- **Arc**: the transit period is time for discourse. The suspended institutional context is an opportunity for conversation that wouldn't happen with normal social stakes present.
|
||||
- **Salt**: commerce that couldn't happen under normal jurisdiction now can. The transit period is a grey-market window.
|
||||
|
||||
**The spaceship social site types:**
|
||||
- **Crew quarters** — insider space (crew only), the social hub of the working community
|
||||
- **Passenger areas** — mixed access, varying by class of ticket
|
||||
- **Bridge/operations** — institutional space, crew credentials required
|
||||
- **Cargo hold** — the informal zone equivalent (no social occasion for most passengers to be there)
|
||||
- **The airlock** — the most extreme "place too small to be safe" (Ozzie's spatial archetype #5)
|
||||
|
||||
**Assassination on a spaceship**: extreme in both directions. The target CANNOT ESCAPE until arrival. But neither can the assassin. Evidence management is impossible — there's nowhere to dispose of evidence that won't be found when the ship docks. Post-arrival institutional scrutiny begins immediately. The spaceship assassination is only viable if the investigation can be controlled at destination, which means the assassin needs assets waiting at the other end.
|
||||
|
||||
### 3.3 The Cultural Grammar of Transit
|
||||
|
||||
Across all entity-carried chunks (vessel, train, spaceship), one cultural constant applies: **transit reveals character**. The normal social infrastructure that regulates behavior (institutional enforcement, community surveillance, professional role performance) is attenuated in transit. What people do when the normal constraints are loosened tells you who they actually are.
|
||||
|
||||
This is the deepest gameplay value of entity-carried chunks: not just bounded space for investigation, but a setting where the information asymmetry becomes especially acute because everyone's masks slip a little.
|
||||
|
||||
**Generator rule:** Entity-carried chunks should have a `transit_social_modifier` that adjusts:
|
||||
- `trust.building_rate`: increases (forced proximity)
|
||||
- `social.privacy_level`: decreases (can't leave)
|
||||
- `cultural_norms.enforcement_level`: decreases (institutional authority attenuated)
|
||||
- `information_flow.internal`: increases (nowhere else to talk)
|
||||
- `information_flow.external`: blocked until arrival
|
||||
|
||||
---
|
||||
|
||||
## Supplement 4: Grid Breathing — Which Cultures Produce Which
|
||||
|
||||
*Lead directive: BOTH grid and organic. Some blocks are grid, some organic chaos. Architecture must support both.*
|
||||
|
||||
This is a worldbuilding question with a clear answer: **grid vs. organic is determined by who built it, when, and under what power conditions**.
|
||||
|
||||
### 4.1 Grid = Power Imposed; Organic = Power Negotiated
|
||||
|
||||
**Grids emerge when:**
|
||||
- A single authority planned the space before construction
|
||||
- The authority had sufficient power to enforce the plan throughout construction
|
||||
- The time pressure was low enough for systematic planning
|
||||
- The cultural value is ORDER as inherently correct
|
||||
|
||||
**Organic patterns emerge when:**
|
||||
- Multiple actors built incrementally over time
|
||||
- No single authority had full control
|
||||
- Time pressure forced immediate construction without planning
|
||||
- The cultural value is FUNCTION over form
|
||||
- The terrain demanded deviation (natural features that planners worked around)
|
||||
|
||||
This means the district's **founding conditions** are the primary driver:
|
||||
|
||||
| Founding condition | Grid tendency | Organic tendency |
|
||||
|---|---|---|
|
||||
| Commission-planned | STRONG | Weak |
|
||||
| Syndic corporate development | Strong | Weak |
|
||||
| Colonial imposition | STRONG | Weak |
|
||||
| Worker/labor organic settlement | Weak | STRONG |
|
||||
| Frontier/pioneer | Weak | STRONG |
|
||||
| Spice family compound networks | — | Medium (family logic, not geometry) |
|
||||
| Arc institutional design | Strong | — |
|
||||
| Gradual accretion over eras | Weak | STRONG |
|
||||
| Single development event (urban planning) | STRONG | — |
|
||||
|
||||
### 4.2 Heritage Root and Grid Preference
|
||||
|
||||
| Heritage Root | Preference | Why |
|
||||
|---|---|---|
|
||||
| **Frost** | Grid (operational zones), organic (residential) | Work is organized; private life doesn't need to be legible to others |
|
||||
| **Tide** | Organic | Community space evolves from social patterns, not planning |
|
||||
| **Iron** | Organic | Workers built their own spaces; no planner controlled the process |
|
||||
| **Spice** | Semi-organic | Follows family network logic — clusters, not grids |
|
||||
| **Jade** | Grid with deliberate irregularity | Order appreciated; but variety is necessary for beauty |
|
||||
| **Dust** | Organic | Survival-driven; build what you need, where you need it |
|
||||
| **Vine** | Organic | Social warmth = built space following relationship patterns |
|
||||
| **Salt** | Grid (commercial zones) | Commercial clarity; buyers need to find sellers |
|
||||
| **Stone** | Semi-grid | Permanence favors planning; but generational accretion adds organic layers |
|
||||
| **Arc** | Grid | Rational organization is a core value |
|
||||
|
||||
### 4.3 The Within-District Grid/Organic Mix
|
||||
|
||||
**This is where it gets interesting for the generator.** A single district can contain BOTH grid and organic sections, because:
|
||||
|
||||
1. **Era stratification**: Era 1 sections are organic (pioneer/frontier construction). Era 3 sections are grid (planned development). The same district has the palimpsest of both.
|
||||
|
||||
2. **Economic function zone differentiation**: Commercial and institutional zones (Syndic, Commission) are grid; residential and maintenance zones are organic. The commercial street is straight; the workers' housing behind it is a warren.
|
||||
|
||||
3. **Cultural overlap**: A district with mixed heritage (Salt-commercial + Iron-residential) has grid commercial blocks and organic residential blocks. The edge bleed between them is the zone where the two grammars fight.
|
||||
|
||||
4. **Power vacuum areas**: Spaces that no authority planned — because no authority wanted them — are organic. The informal zone is almost always organic because it emerged from use, not planning.
|
||||
|
||||
**Generator rule:**
|
||||
The `BlockSkeleton`'s `ChunkLayout` already handles L-shapes and merged footprints. The grid vs. organic distinction is upstream: at block planning time, each block should be assigned a `street_geometry: GridAligned | OrganicDeviation` property. Adjacent blocks can have DIFFERENT street_geometry values, creating the within-district mixing the lead directive requires.
|
||||
|
||||
What drives the assignment:
|
||||
- Era 1 blocks: OrganicDeviation default (unless founded by a specific planning authority — Colony = GridAligned even in Era 1)
|
||||
- Era 2 blocks: weighted by economic function (commercial/institutional → GridAligned; residential/maintenance → OrganicDeviation)
|
||||
- Era 3 blocks: weighted by faction presence (Commission/Syndic presence → GridAligned; absent → depends on drift stage)
|
||||
- Cultural heritage overlay: Arc/Frost-operational/Salt → weight toward GridAligned; Iron/Dust/Tide/Vine → weight toward OrganicDeviation
|
||||
|
||||
**The street grid rotation (Ozzie's persistent demand):**
|
||||
Two adjacent districts with different orientations are plausible when: the districts were planned by different authorities at different times, or when terrain features forced different orientations. A Commission-built district planned along the station's primary axis is GridAligned at 0°. An organically grown district adjacent to it, predating the Commission expansion, may have a street orientation that aligned to the first permanent structure (the original tavern, the first cargo bay), not the Commission's axis. This produces the 15-23° rotation Ozzie is asking for.
|
||||
|
||||
**The generator needs to track not just GridAligned vs. OrganicDeviation but `street_orientation: f32` (rotation in degrees from the district cardinal axis).** GridAligned blocks inherit 0° from the district plan. OrganicDeviation blocks can deviate ±30° based on historical founding conditions.
|
||||
|
||||
---
|
||||
|
||||
## Summary of Supplement
|
||||
|
||||
**Destructible boundaries:** Behind walls is heritage-dependent and era-dependent. Privacy walls (Frost/Spice/Jade) protect social/cultural content; security walls protect economic content; structural walls contain historical content. Each breach has a `cultural_sensitivity` rating and a `social_consequence` that fires based on cultural context, not just what's found. Requires `behind_boundary` descriptor on every block face.
|
||||
|
||||
**Vertical scale:** A 50-floor skyscraper is a multi-z-level single-block tower with a vertical social hierarchy. That hierarchy is heritage-root-derived: corporate culture = bottom labor / mid operations / top executive; Iron-heritage occupation = collective floor reclamation; Commission-institutional = public access at base, secure operations at top (inverted from corporate). Requires `TowerConfig` in `BlockSkeleton`.
|
||||
|
||||
**Entity-carried chunks (trains + spaceships):** Trains are bounded-linear with class-sequence social grammar. Spaceships are institutionally suspended environments — the most extreme information asymmetry because jurisdiction, communication, and social enforcement are simultaneously absent. Both types have a `transit_social_modifier` that adjusts trust-building rate, privacy level, and information flow. What people do when constraints are lifted reveals character.
|
||||
|
||||
**Grid breathing:** Grid = power imposed (Commission/Syndic planning, Arc heritage, Era 3 development). Organic = power negotiated (Iron/Dust/Tide settlement, Era 1 foundations, maintenance/residential zones). Within a district, BOTH can and should appear in different blocks. `street_orientation: f32` enables the inter-district grid rotation Ozzie is demanding — organically-settled districts have orientations inherited from their first permanent structure, not from the Commission's coordinate system.
|
||||
|
||||
---
|
||||
|
||||
**Author:** Miri
|
||||
**Date:** 2026-02-27
|
||||
**Status:** Supplement to Round 3 — filed separately to keep Round 3 focused.
|
||||
@@ -0,0 +1,520 @@
|
||||
# Generator Architecture Workshop — Round 3: Miri (Worldbuilder)
|
||||
|
||||
**Topic:** Assassination playstyle, wilderness informal zones, palette granularity, playstyle affinity per world type, insignificance as social lens, dynamic world modification
|
||||
**Date:** 2026-02-27
|
||||
**Source:** All Round 2 outputs, Qatux Round 2 notes, lead directives
|
||||
|
||||
---
|
||||
|
||||
## Reading the Round 2 Outputs
|
||||
|
||||
The team convergence is solid. The two-phase pipeline is correct. The playstyle-agnostic information landscape framing from my Round 2 held. Gestalt's 11-check guarantee audit is the right enforcement mechanism. Tyre's data structures are handling the extension well.
|
||||
|
||||
The new territory for Round 3 is deeper. Six directives, none of them cosmetic. I'll take them in order.
|
||||
|
||||
---
|
||||
|
||||
## Section 1: Assassination — The Sixth Playstyle
|
||||
|
||||
The lead gives a specific example: Frost cultures make targets harder to track, easier to operate unseen. Dust cultures make strangers visible and targets visible in equal measure. This is the right frame. Let me build it out fully.
|
||||
|
||||
### 1.1 What Assassination Needs From the Information Landscape
|
||||
|
||||
The assassin's information challenge is distinct from all four other playstyles:
|
||||
|
||||
| Playstyle | Primary question | Information goal |
|
||||
|---|---|---|
|
||||
| Investigation | Who did it? | Find the truth |
|
||||
| Tycoon | Where is the value? | Find the opportunity |
|
||||
| Dating sim | Who is this person? | Form the bond |
|
||||
| Political drama | Who controls what? | Find the leverage |
|
||||
| **Assassination** | **Where is the target, when?** | **Find the window** |
|
||||
|
||||
The assassin is not looking for truth or leverage. They're looking for **pattern and vulnerability** — when does the target deviate from their protected routine? What single moment of exposure exists? And critically: what does the aftermath look like, and who investigates it?
|
||||
|
||||
This gives assassination a unique three-phase structure:
|
||||
1. **Pattern acquisition** — learn the target's movements, associations, protection
|
||||
2. **Window identification** — find the gap in protection that can be exploited
|
||||
3. **Aftermath management** — control the information environment after the fact
|
||||
|
||||
Every phase is shaped differently by cultural ingredients.
|
||||
|
||||
### 1.2 Heritage Root Effects on Assassination
|
||||
|
||||
The society profile's heritage roots drive behavioral norms, trust models, and information flow patterns. For assassination, the relevant variables are:
|
||||
|
||||
**Observational density** — how much does this community track strangers and report deviations?
|
||||
**Information liquidity** — how freely does knowledge of the target's movements circulate?
|
||||
**Pattern rigidity** — how predictable are people's routines in this culture?
|
||||
**Aftermath engagement** — how hard does this community investigate when something goes wrong?
|
||||
|
||||
| Heritage Root | Observation density | Information liquidity | Pattern rigidity | Aftermath engagement |
|
||||
|---|---|---|---|---|
|
||||
| **Frost** | Low (people don't watch others) | Very low (information doesn't trade) | HIGH (rigid schedules, functional predictability) | LOW (private grief, informal inquiry) |
|
||||
| **Tide** | High (community awareness is a social virtue) | High (social information flows freely) | Low (fluid schedules, social spontaneity) | HIGH (communal justice demand, collective response) |
|
||||
| **Iron** | Medium-high (labor solidarity = mutual monitoring) | High within group, low to outsiders | Medium (shift patterns are predictable; solidarity gatherings are not) | HIGH (collective accountability culture) |
|
||||
| **Spice** | High within network, zero outside it | Low to strangers, high within family | Medium (family events are predictable; individual movement less so) | VERY HIGH (family networks activate, honor obligations) |
|
||||
| **Jade** | High (aesthetic tradition = careful observation) | Low (discretion is a virtue) | Medium (ritual predictability, but private movements are concealed) | Medium (formal inquiry, institutional channels) |
|
||||
| **Dust** | VERY HIGH (survival = mutual awareness) | High (shared information is survival) | Medium (community events are predictable; individual less so) | High (community protection response) |
|
||||
| **Vine** | High (warmth = social tracking) | Very high (gossip is connection) | Low (social spontaneity, relationship-driven schedules) | Medium-high (personal response, not institutional) |
|
||||
| **Salt** | Low (pragmatic, not nosy) | High IF there's benefit (transactional information) | Medium (deals create predictable windows; personal schedules do not) | Medium (pragmatic inquiry, proportional response) |
|
||||
| **Stone** | Medium (guardianship tradition = watching what matters) | Low (protective silence) | HIGH (traditional rhythms, seasonal predictability) | Medium (steady, persistent, not explosive) |
|
||||
| **Arc** | Low (intellectual focus, not social surveillance) | Medium (ideas traded freely; personal information less so) | LOW (intellectual schedules are chaotic, spontaneous) | VERY HIGH (documentation, inquiry, institutional investigation) |
|
||||
|
||||
**The key assassination matrix:**
|
||||
|
||||
A **Frost-dominant** setting is the assassin's operational paradise but intelligence nightmare:
|
||||
- You can stand in a corridor for an hour and no one will acknowledge you (low observation)
|
||||
- You cannot buy information about the target's movements (low liquidity) — you must observe directly
|
||||
- The target's routine IS reliable once you have it (high pattern rigidity) — patience pays
|
||||
- The aftermath will not mobilize the community (low engagement) — your window to leave is long
|
||||
|
||||
A **Tide-dominant** setting is the assassin's intelligence gift but operational horror:
|
||||
- Everyone notices you've been asking about this person (high liquidity — the information you collected is also shared about you)
|
||||
- The target's movements are discussed openly at the bar — you can learn their schedule in two conversations
|
||||
- Social spontaneity means the schedule shifts around social invitations — the window you identified may not repeat
|
||||
- After the act, the community mobilizes fast and personally — they remember you, your face, your accent
|
||||
|
||||
**Dust** (the lead's example) gives the assassin perfect target information and terrible cover:
|
||||
- Strangers are events in Dust communities — you are visible from the moment you arrive
|
||||
- But so is the target — their deviations from routine are noticed and discussed
|
||||
- The assassination window exists when the target is physically separated from the community (rare in Dust culture, which values collective presence)
|
||||
- The ideal Dust community assassination looks like an accident or external threat — not an insider job, because the community will know every insider
|
||||
|
||||
**Arc** communities produce the most dangerous aftermath:
|
||||
- The community may not notice you much during pattern acquisition (low social surveillance)
|
||||
- But after the act, an Arc community will investigate with INSTITUTIONAL RIGOR — they document everything, they demand explanation, they produce papers
|
||||
- An assassination in an Arc community is an intellectual puzzle that will be solved eventually — the assassin must think three moves ahead on information management
|
||||
|
||||
### 1.3 Economic Pressure and Assassination
|
||||
|
||||
The society profile's economic pressure combination shapes WHO can be hired, who keeps secrets, and what the aftermath looks like.
|
||||
|
||||
**`tight-margin + prohibition-economy`** (Sova-type):
|
||||
- Information CAN be purchased (grey economy includes information brokerage)
|
||||
- Target protection is degraded (enforcement agencies are compromised or underfunded)
|
||||
- Witnesses can be bought off or scared off (economic desperation creates leverage)
|
||||
- Aftermath: Commission investigation is perfunctory unless the target was politically important
|
||||
|
||||
**`survival-gap` economic pressure**:
|
||||
- No one will pay for truth if they need the money for food
|
||||
- Witnesses don't come forward (risk too high, reward too low)
|
||||
- But: the assassin's own expenditure is highly visible (spending above local norms in a survival-gap setting is a red flag)
|
||||
|
||||
**`opportunity-disparity`** (extreme wealth gap):
|
||||
- High-value targets are surrounded by economic resources that buy protection
|
||||
- But also: private security is purchasable, which means it can be bribed or subverted
|
||||
- Information about rich targets circulates among their service class (domestic workers, personal staff) — a different access route
|
||||
|
||||
### 1.4 Faction Presence and the Kill Window
|
||||
|
||||
The faction presence tier directly determines Meridian coverage, patrol patterns, and institutional response capability. For assassination:
|
||||
|
||||
- **Commission comprehensive coverage**: kill window requires understanding the Meridian coverage map, patrol rotation, and response time. Maximum institutional risk but the coverage pattern is ultimately predictable (it's bureaucratic)
|
||||
- **Commission intermittent/absent**: lower monitoring but also lower deterrence of competition — other actors are also operating freely, which means witness control is harder
|
||||
- **No faction presence**: the informal social monitoring (community surveillance via Dust/Tide/Iron norms) is the only enforcement. Can be lower or HIGHER than formal coverage depending on community
|
||||
|
||||
The most dangerous assassination environment: **Commission absent + Dust-dominant culture**. No formal coverage, but 100% community awareness. Every local is an alert system and a potential witness who will talk.
|
||||
|
||||
### 1.5 Generator Requirements for Assassination
|
||||
|
||||
For assassination gameplay to work, the generator must produce:
|
||||
|
||||
1. **Target pattern legibility** — the NPC's routine must be knowable through observation/inquiry. This is already built into the 10-axis NPC model (Pattern axis). The generator needs to ensure the target's routine intersects with observable spaces.
|
||||
|
||||
2. **Window geometry** — at least one location on the target's regular route where institutional coverage gaps, access topology creates opportunity, and witness density drops. This is the "informal zone" of the assassination variant — not a grey market space but a VULNERABILITY WINDOW in the target's pattern.
|
||||
|
||||
3. **Information gradient** — the assassin's intelligence-gathering journey should require working up an information ladder. The right D-025 template placement means: the bar gives you rough schedule (low tier), the insider contact gives you specific route (mid tier), the compromised handler gives you the exact window (high tier).
|
||||
|
||||
4. **Aftermath management geometry** — the route out must exist. The generator's access topology serves this: corridors that exit districts, spans to other zones, the maintenance corridors that aren't on the official map. Assassination requires the same informal zone infrastructure as investigation — for different reasons, but structurally identical.
|
||||
|
||||
**Cultural ingredient playstyle tag for assassination:** `assassination_difficulty: low/medium/high/extreme`
|
||||
|
||||
Driven by: observation density × information liquidity × aftermath engagement. High-Frost/low-community settings = low difficulty. High-Dust/Tide + Iron + Arc community settings = extreme difficulty. This is a single derived value from the society profile, not a new field — but it should be computed and stored as a descriptor.
|
||||
|
||||
---
|
||||
|
||||
## Section 2: Wilderness and Maritime Informal Zones (OQ-R3-C)
|
||||
|
||||
Gestalt raised this question and I'm the right person to answer it: *what does "private space" mean in wilderness without institutional authority?*
|
||||
|
||||
### 2.1 The Redefinition
|
||||
|
||||
In institutionally-governed urban settings, the informal zone is defined by **institutional absence**: spaces outside Meridian coverage, off official maps, not patrolled.
|
||||
|
||||
In non-institutional settings, the informal zone must be redefined. The relevant privacy mechanism is not institutional but **social**: the zone is private not because no camera watches it, but because **social norms give permission for private behavior here** or **community observation doesn't extend here**.
|
||||
|
||||
The shift: from *outside institutional surveillance* to *outside community social field*.
|
||||
|
||||
Every community, regardless of institutional presence, has a **social field** — the range of spaces where community members expect to observe and be observed by others, where social norms apply with full force. The informal zone equivalent is the edge or outside of that field.
|
||||
|
||||
### 2.2 Terrain-Specific Informal Zones
|
||||
|
||||
**Fishing village:**
|
||||
|
||||
The fishing village's social field is strongest at: the dock (arrival/departure, the whole village watches), the meeting hall/tavern (the community gathers here), the boat maintenance area (workers observe each other).
|
||||
|
||||
The informal zone equivalents:
|
||||
- **The boats during active fishing** — out of sight of shore, away from community. What happens on the boat is governed by the crew, not the village. The horizon is the social boundary.
|
||||
- **The smokehouse and drying racks** — utilitarian, smells keep people away, solitary work is normal here. The community doesn't question time spent in the smokehouse.
|
||||
- **The tidal zone before dawn** — the pre-work hours before the village wakes. Low community surveillance because low community presence.
|
||||
- **The processing shed at the edge of settlement** — the "rough work" zone that social convention keeps separate from the community center.
|
||||
|
||||
**On a farm:**
|
||||
|
||||
The farm's social field is strongest at: the communal fields during harvest (many workers present), the farmhouse (family space), the market day gathering.
|
||||
|
||||
The informal zone equivalents:
|
||||
- **The far fields in the off-season** — physically distant, not actively worked, no reason to be there
|
||||
- **The root cellar / storage buildings** — utilitarian storage, no social occasion for lingering
|
||||
- **The property boundary fencerows** — the edges of someone's land that no neighboring farmer has reason to cross
|
||||
- **The barn at night** — animals require tending at night, but it's solitary work. "In the barn late" is not suspicious; it's routine. The barn is the farm's informal corridor.
|
||||
- **The water source / irrigation control point** — critical infrastructure, but visited briefly. Anyone at the sluice gate can claim they're checking the flow.
|
||||
|
||||
**In deep forest:**
|
||||
|
||||
True wilderness has no social field to escape. But within wilderness, informal zones still exist — they're defined by **navigability and customary use**:
|
||||
- The unmaintained path vs. the maintained one — the unmaintained path is not watched because no one has reason to use it
|
||||
- The old-growth stands — superstition, impractical footing, and the absence of resources people want means old growth is often socially avoided
|
||||
- The abandoned structures — the reason for abandonment shapes the avoidance. A burned-out farmhouse is left alone for cultural/emotional reasons. An abandoned mine shaft is avoided for practical safety reasons. Both create informal privacy.
|
||||
- The viewshed reversal — on a hilltop, you can see anyone approaching from a great distance before they see you. In wilderness, HIGH GROUND is the informal zone equivalent because it provides warning rather than concealment.
|
||||
|
||||
### 2.3 Cultural Heritage and Informal Zone Character
|
||||
|
||||
What the community considers "private by convention" varies enormously by heritage root:
|
||||
|
||||
**Frost communities:** The entire individual's domestic space is their informal zone. "Minding your own business" extends to not noticing where neighbors go. In a Frost fishing village, the informal zone is effectively wherever you are when you're not in the communal space — individual movement is private by default.
|
||||
|
||||
**Tide communities:** Community norms actively cover certain behaviors. "The boat is the boat" — what happens on your boat is your crew's business. The informal zone is defined by crew/family/close-trust unit rather than physical space.
|
||||
|
||||
**Iron communities:** The informal zone is often LABOR-ASSOCIATED. "What workers do after the shift" or "what happens in the union hall" is covered by solidarity norms — community members don't inform on each other to outsiders. The informal zone is social rather than geographic.
|
||||
|
||||
**Dust communities:** The informal zone is almost impossible to find because the community's survival-level social awareness covers everything. The most private you can be is "agreed to be unobserved" — a social contract with specific individuals to not see what you're doing. The informal zone is a negotiated privacy, not a geographic one.
|
||||
|
||||
### 2.4 Generator Rule for Non-Urban Informal Zones
|
||||
|
||||
**For every non-urban Full-complexity district:**
|
||||
|
||||
The `terrain_informal_zone` (Gestalt's proposed term) is not just a geographically sheltered space. It is a space where:
|
||||
1. Community norms give permission for private behavior, OR
|
||||
2. Physical distance or conditions reduce community observation without violating social convention, OR
|
||||
3. Utilitarian function provides social cover for presence ("I'm in the barn checking the animals")
|
||||
|
||||
The generator should produce at least one such space per non-urban Full-complexity district, tagged with its informal zone TYPE:
|
||||
- `social_permission` — convention covers this space
|
||||
- `physical_distance` — community observation doesn't reach here without intent
|
||||
- `utilitarian_cover` — normal function provides plausible presence
|
||||
|
||||
The cultural heritage root should weight which type appears:
|
||||
- Frost → `physical_distance` (individual space is respected everywhere)
|
||||
- Tide/Dust → `social_permission` (negotiated privacy within community framework)
|
||||
- Iron → `utilitarian_cover` (labor function covers presence)
|
||||
|
||||
---
|
||||
|
||||
## Section 3: Palette Granularity — Society Profile Within Terrain Types
|
||||
|
||||
The question: is 5 non-urban terrain palettes enough? Should a Frost farm look different from a Vine farm?
|
||||
|
||||
**Setting note — this is a base game concern, not DLC.**
|
||||
|
||||
The cultural ingredients system's entire value proposition is that heritage roots produce distinguishable LIVED ENVIRONMENTS. If all farms look alike regardless of heritage, we've built a system that differentiates culture at the behavioral level but flattens it at the spatial level. That contradiction is visible to players.
|
||||
|
||||
The correct model: **terrain type drives the MATERIAL VOCABULARY** (what materials exist here); **heritage root drives the ORGANIZATIONAL GRAMMAR** (how those materials are arranged, decorated, and related to each other).
|
||||
|
||||
### 3.1 The Two-Layer Palette Model
|
||||
|
||||
**Layer 1 — Terrain Base Palette (from Araminta's 5-6 types)**
|
||||
|
||||
This determines what's available: the substrate materials, the agricultural products, the structural materials indigenous to this terrain and climate. A farmland palette has: dark soil brown, amber grain fields, wood structures, stone foundations. This doesn't change with heritage root.
|
||||
|
||||
**Layer 2 — Heritage Grammar Overlay (from society profile)**
|
||||
|
||||
This determines how the base palette is organized and expressed:
|
||||
|
||||
| Heritage Root | Organizational principle | Visual signature |
|
||||
|---|---|---|
|
||||
| **Frost** | Functional efficiency. No waste, no ornament | Regular spacing, minimal color variation, equipment stored compactly. No decorative elements. Clean lines on structures. |
|
||||
| **Vine** | Social warmth. The human scale matters | Gathering spaces woven into work spaces. Decorative climbing plants on structures. Informal seating clusters. Warm accent colors at door/window frames. |
|
||||
| **Stone** | Permanence. This is built to last | Heavier construction materials. Thick-walled structures. Boundary markers that are permanent (stone walls, not wire fences). Generational accumulation visible. |
|
||||
| **Tide** | Flow and gathering. People move through | Wide paths between buildings. Cleared central areas for assembly. Structures face toward communal center, not away. Seasonal decoration traditions. |
|
||||
| **Iron** | Collective utility. Shared is better | Shared infrastructure (communal granary, shared equipment storage). Buildings of similar scale (no grand farmhouse dominating small workers' quarters — or that contrast IS the political statement). Signs of organized labor. |
|
||||
| **Dust** | Hardship resilience. Nothing wasted | Patched and repaired materials in visible use (not distressed for aesthetics — actually maintained because you can't afford replacement). Water conservation infrastructure prominent. Weatherproofing as primary aesthetic. |
|
||||
| **Spice** | Family honor expressed physically | Family identifier markers on structures. Distinct zones for family/guest/worker (not mixed). Aesthetic investment in the family-facing spaces (the façade toward the road; the interior family court). |
|
||||
| **Salt** | Transactional efficiency | Clear entry/exit points. Storage and sale infrastructure visible and accessible. Pricing/scale equipment present. Minimal personal expression — the space is for business. |
|
||||
| **Arc** | Intellectual order | Things categorized and labeled (even physical objects). Improvement projects visible (new plantings testing yield, experimental plot separate). Written records visible (staked labels, weather logs on walls). |
|
||||
| **Jade** | Refined appreciation | Careful curation of aesthetic elements. Not more material, but better selected. The fence posts are planed smooth. The path is laid with intentional stone selection. Quality over quantity. |
|
||||
|
||||
### 3.2 What This Looks Like in Practice
|
||||
|
||||
**Frost-heritage farm vs. Vine-heritage farm (same terrain palette, different grammar):**
|
||||
|
||||
*Frost farm:*
|
||||
- Low wire fences (functional, minimal material)
|
||||
- Equipment stacked efficiently near work areas, not stored in a dedicated building
|
||||
- No communal spaces outside — why would you gather outside if there's work?
|
||||
- Lighting: work-temperature functional, no ambient warmth
|
||||
- The farmhouse interior has warmth; the exterior presents nothing
|
||||
|
||||
*Vine farm:*
|
||||
- Trellises repurposed as social dividers between plots (boundary AND aesthetic)
|
||||
- A planted-out area near the farmhouse that serves no agricultural purpose — it's for sitting
|
||||
- Communal fire or gathering infrastructure between households if multi-family
|
||||
- Doors and windows decorated with seasonal plantings (tells you the season)
|
||||
- The exterior says "people live here and they'd welcome you"
|
||||
|
||||
**Same farmland terrain palette. Completely different feel. Both immediately readable.**
|
||||
|
||||
### 3.3 Is DLC the Right Model for This?
|
||||
|
||||
Araminta defined the 5-6 terrain palettes in Round 2. The heritage grammar overlay I'm describing above is NOT a new template library — it's a modifier applied AT CHUNK FILL TIME to the existing terrain palette. It needs:
|
||||
|
||||
1. Per-root organizational rules (the table above, encoded as modifier flags)
|
||||
2. Heritage-tagged variant assets for decorative/organizational elements (fences, plantings, gathering spaces, signage)
|
||||
|
||||
The base game MUST ship the heritage grammar overlay rules — they're part of the core cultural differentiation system. What DLC can expand: additional heritage-tagged variant assets for each terrain type (more variety in what "Vine farm aesthetic" looks like). But the grammar rules themselves are base game.
|
||||
|
||||
**Qatux should note:** This is an implicit decision forming — Araminta's terrain palettes need to be specified not just as 5-6 palettes but as (terrain) × (heritage root grammar modifier). That's a cross-domain requirement that needs to be stated explicitly.
|
||||
|
||||
---
|
||||
|
||||
## Section 4: Playstyle Affinity Per World Type
|
||||
|
||||
Not every place serves every playstyle. This should be first-class generator knowledge — the generator needs to know what it's optimizing for when producing a given setting.
|
||||
|
||||
### 4.1 The Affinity Matrix
|
||||
|
||||
I'm defining affinity levels as: **Primary** (this playstyle is naturally strongest here), **Secondary** (playable with adjusted expectations), **Weak** (possible but requires design work), and **Poor** (structurally unsuitable).
|
||||
|
||||
| Setting Type | Investigation | Tycoon | Dating Sim | Political Drama | Assassination |
|
||||
|---|---|---|---|---|---|
|
||||
| **Station transit hub** | Primary | Primary | Secondary | Secondary | Secondary |
|
||||
| **Station administrative/corporate** | Secondary | Secondary | Weak | Primary | Primary |
|
||||
| **Station industrial/freight** | Secondary | Primary | Weak | Secondary | Weak |
|
||||
| **Station residential** | Secondary | Weak | Primary | Secondary | Weak |
|
||||
| **Frontier/pioneer settlement** | Secondary | Secondary | Primary | Secondary | Secondary |
|
||||
| **Agricultural town** | Secondary | Primary | Primary | Secondary | Poor |
|
||||
| **Industrial extraction site** | Secondary | Primary | Weak | Secondary | Weak |
|
||||
| **Maritime port** | Primary | Primary | Secondary | Secondary | Secondary |
|
||||
| **Tourist resort** | Weak | Primary | Primary | Secondary | Secondary |
|
||||
| **Military/security installation** | Secondary | Weak | Secondary | Primary | Primary |
|
||||
| **Research outpost** | Primary | Weak | Secondary | Secondary | Primary |
|
||||
| **Criminal nexus** | Primary | Secondary | Weak | Secondary | Primary |
|
||||
| **Political capital** | Secondary | Secondary | Weak | Primary | Primary |
|
||||
| **Ancient/heritage site** | Primary | Weak | Secondary | Secondary | Secondary |
|
||||
| **Wilderness (no society)** | Weak | Secondary | Weak | Poor | Primary |
|
||||
|
||||
### 4.2 What the Affinity Matrix Means for the Generator
|
||||
|
||||
The significance tier (Center-stage → Insignificant) and the setting type determine the **playstyle content budget**. A military installation shouldn't try hard to generate dating sim content — it should focus its limited content budget on political drama and assassination affordances.
|
||||
|
||||
**Implementation:** The `GuaranteeAuditResult` (Gestalt's 11-check struct) should include a `primary_playstyles: Vec<Playstyle>` field derived from setting type. Full-complexity districts guarantee all 7 archetypes + 4 additional. But the **density** of content per archetype is weighted toward the primary playstyles.
|
||||
|
||||
The economic node is required for all Full districts — but in a military installation, the economic node looks like supply procurement, not a commercial market. In a tourist resort, the economic node is the booking desk, not a freight logistics hub. Same structural requirement, different content expression.
|
||||
|
||||
### 4.3 Assassination-Specific Affinities
|
||||
|
||||
Assassination playstyle has a unique affinity driver that others don't: **target value**. An assassination play doesn't make sense in a poor agricultural village (the target isn't worth the risk) unless that village is specifically flagged as harboring something important.
|
||||
|
||||
The `network_position` parameter I defined in Round 2 (network-significant / regionally significant / locally significant / marginally located) correlates with target value:
|
||||
- Network-significant settings have high-value targets worth assassination
|
||||
- Locally-significant settings have targets whose assassination serves local rather than systemic goals
|
||||
- Marginally located settings have essentially no viable assassination targets — unless a high-value target is visiting (which creates a special scenario type)
|
||||
|
||||
This means: the `assassination_difficulty` descriptor I proposed in Section 1 should be accompanied by `assassination_target_density` — how many viable assassination targets exist in this setting. These together define whether assassination is a viable playstyle here.
|
||||
|
||||
### 4.4 The "Poor" Rating
|
||||
|
||||
A setting rated Poor for a playstyle should not ACTIVELY PREVENT that playstyle — it should just fail to support it well. A wilderness area rated Poor for Political Drama isn't impossible to play politically — it's just that the generator won't produce the spatial affordances that political play needs. A player who insists on playing politics in the wilderness will find sparse, unsatisfying affordances for it.
|
||||
|
||||
This is a design choice: the generator serves the naturally suited playstyles, not all playstyles equally. Players who want deep political gameplay go to political capitals; players who want wilderness assassination go to wilderness. The world has specialization, which is realistic.
|
||||
|
||||
---
|
||||
|
||||
## Section 5: "Insignificant" as Social Lens — Per Playstyle
|
||||
|
||||
Carrying forward CR2-5 (Miri Round 2): insignificance is relational, not absolute. Now I need to show how the SAME backwater reads completely differently depending on what you're there to do.
|
||||
|
||||
### 5.1 The Backwater Through Five Lenses
|
||||
|
||||
**Setting:** A small agricultural settlement, ~150 people. Subsistence farming, one tavern that serves as community center, no faction presence, locally-significant only. Stone/Tide heritage blend. Community has been here 40 years. They know everyone who's ever passed through.
|
||||
|
||||
**Through the investigator's lens:**
|
||||
|
||||
*What's significant here:* Everything is visible. The grey economy, if present, is ONE person in ONE back room. When a crime happens, EVERYONE knows something about it. The investigator's challenge is not finding information — it's that the information knows about THEM. The community will follow their investigation with intense interest and discuss their methods openly.
|
||||
|
||||
*The twist:* The insignificant backwater is where someone goes to HIDE. The most important information in the settlement might be: this person shouldn't be here. A network-significant actor gone to ground in a local-significance settlement. The backwater reads as insignificant — until the investigator notices that one resident has habits that don't fit the Stone/Tide cultural profile.
|
||||
|
||||
*The gameplay:* Not "who committed the murder" but "why is this person here and what are they hiding from." Investigation inversion: the evidence isn't buried, it's too visible. The murder is small (one person). The implications are enormous.
|
||||
|
||||
**Through the tycoon's lens:**
|
||||
|
||||
*What's significant here:* Land rights. Water rights. Agricultural output that flows out through one trading route. Someone owns that route, and they're extracting from everyone who uses it.
|
||||
|
||||
*The opportunity:* The agricultural settlement is CAPTIVE. They can't easily change suppliers or buyers because the infrastructure doesn't support alternatives. The tycoon who finds the single point of leverage (the route, the storage, the equipment) finds a monopoly opportunity.
|
||||
|
||||
*The gameplay:* But the community knows everyone. The tycoon's economic maneuvers are completely visible. Making a quiet deal with the route-owner doesn't stay quiet for long. Economic play in a backwater is conducted entirely in public — which means the community has opinions about what you're doing.
|
||||
|
||||
**Through the dating sim lens:**
|
||||
|
||||
*What's significant here:* This community has 150 people and has had 40 years to develop rich relationship histories. The tavern has regulars who have known each other their entire adult lives. The social graph is dense, fully connected, and intensely aware.
|
||||
|
||||
*The dynamics:* Dating sim in an insignificant place runs at a completely different register from a hub. There is no anonymity phase — you're known from day three. Every relationship you form is visible to everyone else. Third parties have opinions. Family/community approval is inescapable. The cultural heritage (Stone/Tide = community approval as social norm) amplifies this.
|
||||
|
||||
*The gameplay:* The most private thing you can do is become a regular fast — be so present that your presence is unremarkable. The romantic play is not "meet and discover" but "earn belonging." This is a different game. Some players will find it more satisfying than hub romance precisely because the stakes are personal and socially embedded.
|
||||
|
||||
**Through the political drama lens:**
|
||||
|
||||
*What's significant here:* One person runs the community assembly. One trading route operator controls the economic access. Two extended families hold most of the land tenure. The political map is small enough to walk in ten minutes, but the entanglement is complete.
|
||||
|
||||
*The drama:* No institutional mediation. No Commission to appeal to. Political conflict is immediate, personal, and has no legitimate outside arbitration. Political drama in the backwater is INTERPERSONAL in a way that hub political drama is not — you're dealing with the people directly affected by the decisions, not their institutional representatives.
|
||||
|
||||
*The gameplay:* Political drama players in a backwater are playing coalition politics at human scale. Win over Aia's family, lose Torval's approval. This is simultaneously more emotionally intense and more mechanically tractable than hub politics — the actors are knowable.
|
||||
|
||||
**Through the assassin's lens:**
|
||||
|
||||
*What's significant here:* A locally-significant backwater has Poor assassination affinity by default. There are no high-value targets. The entire community knows every movement of every person. The post-act information environment is impossible to control.
|
||||
|
||||
*The exception:* If someone has gone to ground here — someone network-significant hiding as a locally-insignificant person — then the backwater is the IDEAL assassination environment from the target's perspective and the WORST from the assassin's. The target has hidden in the optimal low-surveillance network location. The assassin must penetrate a high-community-awareness setting, work without being remembered, find the disguised target, act, and leave without triggering a community that will talk about the stranger who visited for three days right before someone died.
|
||||
|
||||
*The gameplay:* Assassination in the backwater is the hardest assignment. The assassin who can do this cleanly is elite. The scenario type: "the target has gone to ground here. This world is locally-significant, Stone/Tide heritage, 150 people, everyone knows everyone. Good luck."
|
||||
|
||||
### 5.2 The Generator Implication
|
||||
|
||||
The same backwater world should be generated such that all five lenses can find their specific engagement — even though only 1-2 will be Primary affinity. What changes per lens is not the content but the FRAME through which the player approaches it.
|
||||
|
||||
The generator needs to ensure that even a locally-significant, Moderate-complexity district has:
|
||||
- At least one person whose presence is anomalous (investigation hook)
|
||||
- At least one economic chokepoint that's exploitable (tycoon hook)
|
||||
- At least one sustained social gathering with routine (dating sim hook)
|
||||
- At least one contested allocation decision (political hook)
|
||||
- At least one visitor or newcomer whose identity is uncertain (assassination hook, latent)
|
||||
|
||||
None of these requires separate content — they can all be expressed through the same set of NPCs with appropriately complex profiles. The investigator's "anomalous person" is the same NPC the tycoon recognizes as having unusual resources, the political player sees as holding uncertain allegiance, and the assassin flags as possibly the target.
|
||||
|
||||
**One NPC, five lenses.** That's the design target.
|
||||
|
||||
---
|
||||
|
||||
## Section 6: Dynamic World Modification — Trauma Events
|
||||
|
||||
When a gas explosion destroys part of a district, what changes culturally? This is the question that connects worldbuilding to dynamic simulation.
|
||||
|
||||
### 6.1 The Trauma Event Framework
|
||||
|
||||
A trauma event is a **historical event modifier applied to a living district** rather than to a pre-generated historical record. The architectural system for this already exists (Tyre's `EraModification` + Gestalt's `era_cause` field). What's needed is the **cultural aftermath model** — how does the society profile respond to acute stress?
|
||||
|
||||
The key insight: **trauma reveals the society profile more clearly, not less.** A stressed community doesn't become a different culture — it becomes an intensified version of itself. The heritage roots that were latent become dominant. The trust mechanisms that worked passively become active. The absence parameters (what the community lacks institutionally) become critically felt.
|
||||
|
||||
### 6.2 Trauma Types and Cultural Response
|
||||
|
||||
**Type 1 — Physical Destruction (explosion, collapse, flood)**
|
||||
|
||||
Phases:
|
||||
|
||||
*Immediate (1-7 days):*
|
||||
- Information flow SPIKES: everyone talks about what happened. For 72-96 hours, the normal information siloing is suspended.
|
||||
- Community pattern shift: ANCHOR-type NPCs dominate (the people who hold the community together step forward). CATALYST NPCs (people in crisis) increase. HANDLER NPCs (operators) temporarily reduce activity.
|
||||
- Access topology changes: blocked routes create new informal paths; rubble creates new informal zones.
|
||||
- Faction response matters enormously: who responds first, how, and with whose resources — this shapes community trust for years.
|
||||
|
||||
*Medium-term (1-8 weeks):*
|
||||
- **Heritage root response:**
|
||||
- **Frost**: community closes, rebuilds quietly, does not discuss the trauma publicly. Asks for practical help. Rejects offered emotional support as intrusive. Suspicion of outsiders increases.
|
||||
- **Tide**: community gathers, processes grief publicly, creates ritual around the event. A community mourning becomes a social institution. Outsider sympathy is welcomed.
|
||||
- **Iron**: collective response — mutual aid organized, demands for accountability raised, solidarity demonstrated through shared labor. The community investigates who is responsible.
|
||||
- **Dust**: all hands. Every community member contributes. The social hierarchy flattens in crisis. Leadership goes to the most capable, not the most credentialed.
|
||||
- **Vine**: the social fabric IS the response — meals cooked, children cared for, emotional support structured through existing relationships. The community grieves as a social entity.
|
||||
- **Arc**: documentation, inquiry, accountability. The community produces a record. Someone is writing down what happened and why.
|
||||
|
||||
*Long-term (months to years):*
|
||||
- Era modification is logged with `StructuralDestruction` event type
|
||||
- Drift stage may increase in affected area (forced evolution, reduced cosmopolitan blending as community turns inward)
|
||||
- Some NPCs leave (the destruction is the tipping point for people who were already on the margin)
|
||||
- Memorial markers appear (heritage-root-dependent form)
|
||||
- Trust recovery curve: the community's trust of institutions (Commission response quality) either rebuilds or permanently decays
|
||||
|
||||
### 6.3 The Society Profile Under Stress
|
||||
|
||||
**What changes:**
|
||||
- `trust.building_rate` for STRANGERS decreases (community turns inward)
|
||||
- `insider_trust_threshold` decreases (insiders become more trusted, not less — reciprocal tightening)
|
||||
- `grey_economy` activity shifts: some operators become more visible (mutual aid operates outside formal channels), some go dark
|
||||
- `faction_presence` operational character may shift: Commission may have formal authority but reduced actual cooperation
|
||||
|
||||
**What does NOT change:**
|
||||
- Heritage root identity (this is who we are — it doesn't change under stress, it intensifies)
|
||||
- Economic function (people still need to work)
|
||||
- Settlement motivation (why we're here doesn't change because something broke)
|
||||
|
||||
### 6.4 Trauma as Gameplay Driver
|
||||
|
||||
For each playstyle, a trauma event creates EXCEPTIONAL CONDITIONS:
|
||||
|
||||
**Investigation:** The immediate information spike is a window. For 72 hours, people will talk who wouldn't normally. The community's defenses are down. Evidence that's normally buried becomes surface-level visible. Counter: everyone is also watching the investigator more closely.
|
||||
|
||||
**Tycoon:** Economic disruption creates opportunity gaps. Suppliers for reconstruction materials are needed immediately. The economic void left by destroyed infrastructure is a market opening. Counter: predatory behavior during community tragedy is visible and remembered.
|
||||
|
||||
**Dating sim:** Crisis creates intimacy. Shared trauma is a bonding accelerant. Counter: crisis reveals character — both the player's and the NPCs'. The Frost person who retreats inward during trauma requires a completely different response than the Tide person who needs to process publicly. Reading the heritage root correctly under pressure is a dating sim skill check.
|
||||
|
||||
**Political drama:** The aftermath is a power redistribution event. Who controlled the destroyed infrastructure? Who controls the reconstruction? Who is blamed? Political drama in the aftermath is about exploiting the vacuum or managing the accountability before it resolves against you.
|
||||
|
||||
**Assassination:** Trauma events create TWO opportunity windows:
|
||||
1. The immediate chaos window: high community distraction, institutional focus elsewhere, normal patterns suspended
|
||||
2. The reconstruction vulnerability window: the target may be physically present at the damaged site (overseeing reconstruction, inspecting, attending memorial), reducing normal protection
|
||||
Counter: institutional presence may be ELEVATED during reconstruction, and community attention is heightened.
|
||||
|
||||
### 6.5 The Generator Implementation
|
||||
|
||||
**What the generator needs to support:**
|
||||
|
||||
1. **Trauma events as historical modifiers** (already exists via `EraModification`)
|
||||
- Add `ModificationType::TraumaEvent` with subtypes: PhysicalDestruction, EconomicDisruption, PoliticalShock, ViolenceEvent, MigrationShock
|
||||
- Carry `cultural_aftermath: HeritageRootResponse` — the specific community response derived from dominant heritage root
|
||||
|
||||
2. **Active modification state** for living worlds (distinct from historical record)
|
||||
- The historical record stores what happened; the active modification state stores what's currently different from baseline
|
||||
- Active modification state decays over time (trauma aftermath is temporary — society returns toward baseline)
|
||||
- Rate of return is heritage-root-dependent (Frost: faster private recovery, slower institutional normalization; Tide: faster social recovery; Arc: never returns to pre-inquiry-completion state)
|
||||
|
||||
3. **NPC pattern weight modification** for affected areas
|
||||
- NPC generation in post-trauma areas should weight ANCHOR, WITNESS, REMNANT patterns higher; CATALYST and NOBODY patterns differently
|
||||
- This is the mechanism that makes post-trauma areas FEEL different — the people in them behave differently
|
||||
|
||||
4. **Access topology update**
|
||||
- Blocked routes from structural damage create new informal paths (these become the post-trauma informal zone)
|
||||
- The generator should flag destroyed social sites as `inactive` with an optional `temporary_replacement` pointer
|
||||
|
||||
### 6.6 What I'm Not Solving Here
|
||||
|
||||
Dynamic world modification that happens DURING a playthrough (not historical pre-generation) is a simulation concern more than a worldbuilding concern. Tyre will address whether the server's simulation tick system can handle live trauma events modifying the `PreparedDistrict` or whether these require regeneration.
|
||||
|
||||
My contribution is the **cultural response layer** — the worldbuilding logic that determines what a trauma event MEANS for how a community behaves. The simulation system implements the mechanics; the society profile provides the parameters for how those mechanics are culturally expressed.
|
||||
|
||||
---
|
||||
|
||||
## Summary: Round 3 Contributions
|
||||
|
||||
**1. Assassination as the sixth playstyle** — fully integrated into the society profile. Cultural ingredients drive three variables: observational density (how visible you are), information liquidity (how knowable the target is), and aftermath engagement (how hard they look afterward). The combination defines `assassination_difficulty` as a derived profile descriptor.
|
||||
|
||||
**2. Non-urban informal zones** — redefined from "outside institutional surveillance" to "outside community social field." Each terrain type has specific informal zone equivalents; heritage roots determine which type appears. The generator tag is three-way: `social_permission` / `physical_distance` / `utilitarian_cover`.
|
||||
|
||||
**3. Palette granularity** — two-layer model: terrain base palette (material vocabulary) + heritage grammar overlay (organizational principle). This is a base game requirement, not DLC. All ten heritage roots have distinct organizational grammar rules applicable to any terrain type. DLC expands variant assets; the grammar rules are core.
|
||||
|
||||
**4. Playstyle affinity matrix** — full matrix across 15 setting types and 5 playstyles. Generator knows what it's optimizing for. Poor-affinity playstyles are not blocked but are not budgeted for. One NPC with sufficiently complex profile can provide entry hooks for all five lenses simultaneously.
|
||||
|
||||
**5. Insignificance through five lenses** — same backwater world, five different engagements. Investigation: the anomalous person; Tycoon: the exploitable chokepoint; Dating sim: the belonging-earning; Political drama: personal-scale coalition politics; Assassination: the hardest assignment. Minimum content for even Moderate-complexity insignificant districts: one anomalous presence, one economic chokepoint, one social gathering rhythm, one contested allocation, one visitor of uncertain identity.
|
||||
|
||||
**6. Dynamic world modification** — trauma events as living modifications applied to society profiles. Six trauma subtypes. Response is heritage-root-determined (intensification of existing cultural character, not transformation). Implementation requires `TraumaEvent` modification type, `cultural_aftermath` field with heritage response, NPC pattern weight modification for affected areas, and active modification state with decay rate.
|
||||
|
||||
---
|
||||
|
||||
**Author:** Miri
|
||||
**Date:** 2026-02-27
|
||||
**Status:** Round 3 complete.
|
||||
|
||||
**Questions for Round 4 or implementation:**
|
||||
- Tyre: The active modification state (for living trauma events) — is this a second layer above `PreparedDistrict`, or does it modify the prepared district in place? Decay rates need to map to the simulation tick.
|
||||
- Gestalt: The `assassination_difficulty` descriptor — should this be surfaced in the `GuaranteeAuditResult`, or is it a separate field on `DistrictSkeleton` alongside the playstyle affinity vector?
|
||||
- Araminta: Heritage grammar overlay rules (the ten-row table in Section 3.1) need to be encoded as modifier flags that chunk fill can read. What's the right representation — per-heritage modifier objects that chunk fill applies, or lookup tables within the terrain palette assets?
|
||||
- Nigel: The "one NPC, five lenses" requirement (Section 5.2) — does the NPC's 10-axis model already support this, or does providing five-playstyle hook simultaneously require additional content axes?
|
||||
@@ -0,0 +1,595 @@
|
||||
# Generator Architecture Workshop — Round 4: Miri (Worldbuilder)
|
||||
|
||||
**Topic:** Write the NPC. Close the open questions. Finalize vessel grammar.
|
||||
**Date:** 2026-02-27
|
||||
**Lead decisions acknowledged:** WorldTier wins over SignificanceTier. DramaDensity is runtime state (not on DistrictSkeleton). Entity-carried MobileChunk is CORE.
|
||||
|
||||
---
|
||||
|
||||
## Preamble
|
||||
|
||||
The lead said: write the NPC. Don't discuss whether it could theoretically work. Show the work.
|
||||
|
||||
This is the work.
|
||||
|
||||
---
|
||||
|
||||
## OQ-R4-E: One Character. One Settlement. Five Lenses.
|
||||
|
||||
### The Setting
|
||||
|
||||
**Harrow Drift** — a farming settlement, 40 years established, ~150 permanent residents.
|
||||
|
||||
WorldTier: Backwater. ComplexityTier: Moderate. DramaDensity: Zero (currently).
|
||||
|
||||
Heritage: Stone (0.6) + Tide (0.4). Drift stage: crystallizing (40 years is young for Stone, which wants centuries).
|
||||
Settlement motivation: economic-agricultural — founded by a cohort of families who wanted land tenure they couldn't get in a Commission-managed hub.
|
||||
Economic function: subsistence agriculture + modest surplus trade via one seasonal route.
|
||||
Economic pressure: generational-extraction (the land titles are real but the route operator takes 18% of surplus trade).
|
||||
Faction presence: Commission absent; no Syndic presence; local governance = informal assembly (five founding family heads + elected coordinator).
|
||||
Meridian coverage: none.
|
||||
|
||||
**Community character:** Stone culture means the founding families have territorial memory and protective silence. Tide culture means people eat together, celebrate together, and a stranger is immediately noticed — and discussed. The combination: tight community with a warm surface and a hard interior. You're welcomed at the table on day one. You're trusted at year ten, if you've earned it.
|
||||
|
||||
Forty years in, they know every family secret. Including who belongs and who doesn't quite fit.
|
||||
|
||||
---
|
||||
|
||||
### The NPC
|
||||
|
||||
**Ysabel Vorn**
|
||||
Apparent age: mid-40s. Arrived at Harrow Drift 14 years ago, nominally as a partner of a farmer who left 8 years ago. The farmer left. She stayed. She runs water management.
|
||||
|
||||
---
|
||||
|
||||
#### Full 10-Axis Profile
|
||||
|
||||
**Axis 1 — Behavioral Pattern (Social Archetype)**
|
||||
|
||||
Primary: ANCHOR. Ysabel is one of the five or six people the community would name if asked "who keeps this place running." Her water management role is structurally critical (every farm's viability depends on fair allocation during dry months). She shows up consistently, mediates disputes without taking sides, and participates in community labor beyond her direct responsibilities.
|
||||
|
||||
Secondary: REMNANT. This is the layer that only long observation reveals. She is holding on — not to the past she claims, but to a past she won't name. Something about her patterns suggests someone who has been running and has decided, tentatively, to stop here.
|
||||
|
||||
**Axis 2 — Surface Motivation (Publicly Visible Goal)**
|
||||
|
||||
Keep the water system equitable. Prevent the Fennen family from leveraging their founding-family status into preferential allocation. Maintain the settlement's social cohesion through the one resource that everyone needs and no one can leave without.
|
||||
|
||||
This motivation is REAL. It is not a cover story. She has spent 14 years genuinely trying to be useful here.
|
||||
|
||||
**Axis 3 — Actual Motivation (What She Actually Wants)**
|
||||
|
||||
Stay hidden. Safe. Not found.
|
||||
|
||||
The equitable administration serves the actual motivation: if she is indispensable and trusted, no one asks questions about her origin. A community that needs you doesn't scrutinize you. She has been performing trustworthiness with strategic precision for 14 years, and by now most of it has become genuine — she actually cares about Harrow Drift. But the original reason she chose to care this much was survival.
|
||||
|
||||
Secondary actual motivation: she is watching for Kael Voss. The man who was displaced by the fraud she documented. She's known for three years he lives 40 km east. She hasn't approached him. She tells herself this is because contact would expose her. The truth is more complicated.
|
||||
|
||||
**Axis 4 — Vulnerability/Secret**
|
||||
|
||||
Seven years before she arrived at Harrow Drift, Ysabel was a Commission data analyst specializing in land-grant records — a mid-level position that gave her access to the historical title database across a significant region.
|
||||
|
||||
During a routine audit, she found it: a fabricated land-grant record that had been inserted into the Commission database, dated 22 years prior, displacing a pre-existing title held by the Voss family. The fabrication was clean enough to pass cursory review. It wasn't clean enough to pass her review. She traced it to a Syndic subsidiary acting on behalf of an executive named Pehr Callen, who had needed the land for a private extraction operation. The Voss family — Kael's parents, then — had been compensated under a false legal premise and relocated.
|
||||
|
||||
She made a copy of the file chain. Then she made a mistake: she contacted a ring-adjacent information broker, thinking she could pass the evidence to someone who would use it without exposing her. The broker took the files and disappeared. Three weeks later, a Commission warrant was issued for a data analyst who had accessed restricted historical records without authorization. Her name.
|
||||
|
||||
She ran. She has been running in a single direction (toward places with no Meridian coverage and no Commission presence) for seven years before arriving at Harrow Drift. The warrant is real. The data theft charge is real. The underlying evidence that motivated the theft is also real.
|
||||
|
||||
She doesn't know if the copy she passed to the broker ever reached anyone. She doesn't know if Pehr Callen knows she's alive.
|
||||
|
||||
**Axis 5 — Information Access (What She Knows)**
|
||||
|
||||
Tier 3 (complete): Harrow Drift's water system, seasonal allocation records, every family's land and water claims going back to founding.
|
||||
|
||||
Tier 3 (complete): The interpersonal relationships, grudges, debts, and loyalties of every person in the settlement. Fourteen years of observation. She is the settlement's institutional memory despite being a latecomer.
|
||||
|
||||
Tier 2 (partial, aging): The Commission land-grant system's structure and failure modes. She has been away from Commission data infrastructure for 14 years, but the analytical framework is intact. She can read a land title and identify if something is wrong. She knows this region's historical land grant database well enough to identify additional fabrications if they exist.
|
||||
|
||||
Tier 3 (specific): The Pehr Callen conspiracy file chain. She has a memory copy — she memorized the key document numbers and dates before she ran. She does not have the physical files. But she can reconstruct enough to make an investigator's or legal advocate's job tractable if given access to the right archive.
|
||||
|
||||
Tier 1 (basic): Kael Voss exists, lives 40 km east in a settlement called Vermin's Cross, farms barley. She knows his name and location but nothing about his current life.
|
||||
|
||||
**Axis 6 — Trust Architecture**
|
||||
|
||||
Heritage trust model: Stone (tenure-based, very slow, deep once earned) + Tide (public demonstration, community participation). This is her actual operating model, not a calculated performance — she has absorbed the community's trust norms over 14 years.
|
||||
|
||||
Trusted (deeply): three people. Opal Dun (Cara's grandmother, 70s, has never asked about Ysabel's past and shows by this that she has noticed there's something to not ask about). Lev Fennen (Orik's youngest son, who disagrees with his family's political ambitions; Ysabel has quietly protected his dissent from family pressure). One other who is not significant to this document.
|
||||
|
||||
Trusted (functionally): the approximately 40 people who interact with her regularly through water management and community events. She is warm with them. She is not open.
|
||||
|
||||
Cautious (everyone else): 110 people she is friendly toward and emotionally reserved with.
|
||||
|
||||
Zero trust: strangers. A new arrival triggers her internal threat assessment immediately. She remains warm and welcoming on the surface — this is Stone/Tide culture. Internally, she is reading every detail for signs of Commission connection or Syndic interest.
|
||||
|
||||
Trust-building rate for a player character: slow by default (Stone tenure model). Accelerates via public contributions (Tide model) — help during the water dispute, participate in harvest labor, accept an invitation to a community meal and behave well. Decelerates immediately if the player shows interest in her history.
|
||||
|
||||
**Axis 7 — Routine Pattern (Movement and Schedule)**
|
||||
|
||||
*Dawn:* Solo inspection of the main water channels and reservoir (45 minutes, predictable path, starts at the sluice gate near the east field boundary and ends at the primary storage tank north of the settlement). This is her most private daily interval.
|
||||
|
||||
*Morning:* Available at the water management building (small structure, central location) for allocation queries. Frequent foot traffic. Social but businesslike.
|
||||
|
||||
*Midday:* Eats at the community gathering space with whoever is present. She ensures this visibility consistently — this is both Stone/Tide cultural participation and deliberate cover maintenance.
|
||||
|
||||
*Afternoon:* Variable. Field work with neighbors (she contributes labor across farms, building distributed goodwill). Or: paperwork (the settlement's water records, which she maintains meticulously). Or: if a dispute is active, she meets with involved parties privately.
|
||||
|
||||
*Evening:* Selective community gathering attendance. She is present often enough that absence is unremarkable. She does not attend every event — which prevents over-exposure.
|
||||
|
||||
*Weekly:* Attends every community assembly. Sits in the middle third of seating (neither front-row authority nor back-row disengagement). Speaks rarely, but when she speaks, the room listens.
|
||||
|
||||
*Seasonal:* Pre-harvest water allocation period (approximately six weeks before harvest) is her highest-activity, highest-visibility period. She is present, decisive, and politically exposed during this time. Orik Fennen challenges her allocation decisions every year during this period. She navigates it. The community watches.
|
||||
|
||||
*Anomaly in routine:* Once every six to eight weeks, she makes a solo trip to the property boundary — a walk that takes her approximately 90 minutes and that she does not explain to anyone. No one has asked. She is watching the trade route approach.
|
||||
|
||||
**Axis 8 — Economic Position**
|
||||
|
||||
Direct control: water allocation for every farm in the settlement during the 10–12 week dry-season period. Without her management, the dry season produces disputes that the community's informal governance cannot resolve. She has not monetized this leverage. She is ideologically opposed to doing so — it would make her someone who exploits the community, which contradicts her actual care for it.
|
||||
|
||||
Indirect leverage: the Commission land-grant knowledge she carries. This is not an active economic asset — she cannot sell it without exposing herself. But: it IS the settlement's most valuable economic intelligence asset if anyone knew she had it. Several land claims in this region may rest on false foundations. If the fraudulent land-grant system extends beyond the Voss case (she suspects it does), then whoever controls access to that information controls significant economic leverage across the region.
|
||||
|
||||
Personal economics: modest. She takes no payment for water management (community expectation: the role is a community service). She participates in the community's labor exchange economy. She has small savings, no investment claims, no land title (the farm where she arrived is now owned by a different family).
|
||||
|
||||
**Axis 9 — Relationship Network (Triangle Memberships)**
|
||||
|
||||
*Triangle 1 — Social/Political (Active):*
|
||||
Ysabel (arbiter) ↔ Orik Fennen (senior landholding farmer, Founding Family, believes water allocation should be weighted by land area owned) ↔ Cara Dun (young farmer, third generation, believes allocation should be equal per-household regardless of land size). Purpose: Political + Social.
|
||||
|
||||
Ysabel is the central node. Both parties trust her differently: Orik respects her competence and assumes she's managing the situation toward a status quo that serves everyone; Cara trusts her because she's seen Ysabel resist Orik's pressure. The tension is real, the allocation decision is real, and this triangle activates during every dry-season period. One year it will break and Ysabel's role will be at stake.
|
||||
|
||||
*Triangle 2 — Investigation/Economic (Latent):*
|
||||
Ysabel (holder of evidence) ↔ Pehr Callen (Commission-adjacent Syndic executive, the original conspirator) ↔ Kael Voss (displaced victim, 40 km east). Purpose: Investigation + Economic.
|
||||
|
||||
This triangle is inactive because none of the three nodes knows the other two are in proximity. Kael doesn't know Ysabel exists or has evidence about his family's displacement. Pehr Callen doesn't know Ysabel is alive or where she is. Ysabel knows about both of the others and has chosen not to move.
|
||||
|
||||
The triangle ACTIVATES if: an investigator finds any thread connecting Ysabel to Commission records, or Kael to the land dispute, or Pehr Callen's name surfaces in any adjacent investigation. It also activates if Ysabel crosses her own tolerance threshold and decides to act.
|
||||
|
||||
*Triangle 3 — Tactical (Latent):*
|
||||
Ysabel (target) ↔ Pehr Callen's agents (potential protector/executor) ↔ any investigator or player character who learns Ysabel's significance. Purpose: Tactical + Investigation.
|
||||
|
||||
This triangle exists only from the perspective of someone who knows Ysabel is network-significant. From inside the settlement, Ysabel is an ANCHOR with no visible enemies. The Tactical triangle is invisible until activated by external knowledge.
|
||||
|
||||
**Axis 10 — Tolerance Threshold**
|
||||
|
||||
*For community conflict:* Very high. She has managed 14 years of community disputes without breaking character. She can absorb extended social friction, political challenge, and personal criticism without acting precipitously.
|
||||
|
||||
*For exposure:* Near-zero. If she believes she has been found — by Commission, by Pehr Callen's people, by anyone with hostile intent toward her history — she will leave. She has a pre-prepared exit: she knows which route out of Harrow Drift is fastest, where the first settlement is that she could pass through without being remembered, and what a cover identity looks like. She has not used this exit in 14 years and has added more reasons not to use it each year she stays. But the exit exists.
|
||||
|
||||
*For Kael Voss:* Declining. She is aware her threshold is lowering. Three years ago she accepted she knew where he was. Each passing season she is slightly more aware that the evidence she carries is not helping him while she holds it. She does not know what will push her to act. She suspects something visible — witnessing his situation deteriorate, or meeting him accidentally — would be enough. She avoids the road to Vermin's Cross.
|
||||
|
||||
*For the player character:* Variable based on what they represent. A player who seems to be passing through with no investigative intent gets the warm Stone/Tide welcome and gradually earns trust through community participation. A player who asks specific questions about Commission presence, land records, or her history will find her warmth becomes cautious-correct very quickly. She is not hostile. She is self-preserving.
|
||||
|
||||
---
|
||||
|
||||
### Five Lenses, One NPC
|
||||
|
||||
**The Investigation Lens**
|
||||
|
||||
What the investigator sees at first: a competent, trusted community administrator. No obvious irregularities. Well-liked, stable, clearly not leaving.
|
||||
|
||||
What the investigator eventually sees: the void. Ysabel has no history before arriving at Harrow Drift. No family mentioned (the farmer she arrived with is gone and she doesn't speak of him). No home system. When asked directly, she gives a soft non-answer: "I needed somewhere different." Her knowledge of Commission administrative systems — visible in small details, the specific language she uses about land claims, her taxonomic approach to water allocation records — is more than informal. She has been trained.
|
||||
|
||||
The investigation hook: she is the anomalous presence. Not because she committed a crime here. Because she shouldn't be here at all — someone with her knowledge and capability would not end up in a Backwater/Moderate settlement unless they chose it for reasons that aren't the stated ones.
|
||||
|
||||
The investigation play: not "find the murderer" but "find out who she's hiding from and why." The answer leads off-world — to a Commission warrant, to Pehr Callen, to Kael Voss 40 km east, and to evidence of a land-grant fraud that is not local in scope.
|
||||
|
||||
The information ladder specific to investigation mode:
|
||||
- Low tier: "She doesn't talk about where she came from." (Any community member will say this within a day of asking.)
|
||||
- Mid tier: "Her water management records use Commission administrative notation." (Visible if you look at her actual paperwork.)
|
||||
- High tier: "She made that trip to the property boundary again — she goes every six to eight weeks, alone, and looks down the trade route." (Requires sustained observation or an insider who's noticed.)
|
||||
- Insider tier: Opal Dun says: "I stopped asking seven years ago. Whatever she's carrying, she's carried it longer than she's been here." (Unlocked via deep trust with Opal.)
|
||||
|
||||
**The Tycoon Lens**
|
||||
|
||||
What the tycoon sees: the economic chokepoint. Water allocation controller. Every farmer in the settlement is economically dependent on the seasonal allocation decisions she makes.
|
||||
|
||||
The opportunity: she's not exploiting this. This is immediately unusual to a tycoon sensibility. Someone controlling a mandatory resource in a captive market and not charging above-market rates for it is either naive or principled. In this case: principled. But principled people can be influenced — you just need to find the right currency.
|
||||
|
||||
The tycoon's possible moves:
|
||||
- Offer her a cut of the trade route operation (rejected — she doesn't want money; money creates visibility)
|
||||
- Help formalize the water allocation as a legal structure that protects her role from community vote challenge (interesting to her — reduces her political vulnerability)
|
||||
- Offer access to a secure communication channel outside the settlement (very interesting — she wants to know if the Commission warrant is still active after 14 years)
|
||||
- Offer information about Kael Voss (extremely interesting — this is the tycoon accidentally holding the key)
|
||||
|
||||
The deeper tycoon layer: she holds the evidence of a fraudulent land-grant that likely extends across the region. If the tycoon discovers this (requires significant trust-building or investigation), they have access to an information asset that could: a) be used to challenge existing land titles (disrupting established economic interests), b) be sold to parties who want to restore original titles (Kael Voss, for instance, or his legal advocates), or c) be used as leverage against Pehr Callen (corporate coercion material of significant value). The water management role is the tycoon's entry point. The Callen file chain is the real prize.
|
||||
|
||||
**The Dating Sim Lens**
|
||||
|
||||
What the dating sim player sees: someone who is clearly trusted and clearly closed. The warm Tide exterior is real — she participates in community life, she brings food to gatherings, she knows everyone's name and their children's names. But get her alone and there's a quality of careful control to her openness. She listens more than she speaks. She deflects personal history questions without making you feel deflected.
|
||||
|
||||
The arc: she is not unwilling to connect — she is afraid it's not safe to. The dating sim challenge is not reading her correctly. It's creating enough perceived safety that she stops performing and starts actually trusting.
|
||||
|
||||
Heritage-appropriate trust signals (what earns her):
|
||||
- Participating in community labor without being asked (Tide: public demonstration)
|
||||
- Showing up consistently over time (Stone: tenure earns trust)
|
||||
- Accepting an invitation to a community meal and not asking about her past (both heritages: respecting the social norm)
|
||||
- Helping during the water dispute without trying to position yourself politically (demonstrates you're not there to exploit the community's vulnerabilities)
|
||||
|
||||
The milestone: she lets something slip. A city name she shouldn't know — a reference point for a Commission administrative district that a person who "needed somewhere different" wouldn't have. The player catches it. The choice: press (which opens her up, partially), or let it go (which deepens her trust further). Pressing too hard too early gets the careful-correct Ysabel. Giving her room gets the real one.
|
||||
|
||||
The actual relationship arc: she's been managing her isolation for 14 years. She is genuinely lonely. She has not allowed herself to be seen. A player who gives her consistent, patient, non-invasive attention is offering something she hasn't been able to have for a very long time. When she does open, it's in pieces — not a confession, but a gradual admission that she exists more than she's been letting on.
|
||||
|
||||
The final vulnerable act: she takes the player to the property boundary and watches the trade route without explaining why. This is the closest she gets to showing her actual situation. It's not an explanation. It's a gesture of trust.
|
||||
|
||||
**The Political Drama Lens**
|
||||
|
||||
What the political player sees: the most powerful person in the settlement by functional leverage, who is self-deliberately not using that power for political gain. This is a vacuum that the political game will fill one way or another.
|
||||
|
||||
The active political conflict: Orik Fennen's faction (Founding Family entitlement, believes resource allocation should reflect historical investment) vs. Cara Dun's faction (generational equity, believes resources belong to the current community equally). Ysabel is the tiebreaker. Orik knows this and applies steady social pressure on the allocation decisions. Cara knows this and counts on Ysabel's stated commitment to equity.
|
||||
|
||||
The political play space:
|
||||
- Help Ysabel maintain her role through the community assembly (requires building enough coalition that Orik can't replace her with a sympathetic appointment)
|
||||
- Exploit the conflict by backing one faction (either gets you a powerful local ally but costs you the other half of the settlement)
|
||||
- Use Ysabel's role as leverage to change the allocation rules permanently (requires her cooperation, which requires earning her trust)
|
||||
- Discover that the Fennen family's founding-family entitlement has a land title anomaly in the regional records (Ysabel knows this; she's never said it; she's been protecting community stability by not introducing a land-title challenge into the political mix)
|
||||
|
||||
The deep political layer: the land-grant fraud Ysabel documented wasn't just about the Voss displacement. If she's right that the fraud system was broader, several founding-family land titles in this settlement may also rest on Commission records that were manipulated to favor specific families over others. The political implications are settlement-destabilizing. She has chosen community stability over truth. A political drama player who uncovers this faces the same choice.
|
||||
|
||||
**The Assassination Lens**
|
||||
|
||||
Default assessment of Harrow Drift: locally-significant target pool only. No Commission presence, no Syndic presence, no network-visible actors. Standard assassination play = Poor affinity. Pass through.
|
||||
|
||||
But.
|
||||
|
||||
A player with access to higher-tier intelligence would find: there is a Commission warrant — 14 years old but still active — for a data analyst who accessed restricted land-grant records without authorization and then vanished. The last confirmed sighting was a system away, 14 years ago. The warrant was filed by the Commission. The underlying pressure came from Pehr Callen's Syndic subsidiary.
|
||||
|
||||
Pehr Callen still exists. He is now significantly more powerful. The land-grant fraud is still formally concealed. The analyst who found it is still, technically, evidence of a crime — not because she committed one in any meaningful sense, but because she can testify to what she saw and what documents were altered. Callen has reason to want her permanently unavailable.
|
||||
|
||||
This turns the assassination lens from "no viable targets" to "the highest-difficulty target in the region."
|
||||
|
||||
The assignment (if received): locate and permanently silence a former Commission data analyst, current alias Ysabel Vorn, position water management coordinator, Harrow Drift settlement, population 150, no Commission presence. Mandate: no visible cause of death, no community suspicion, no connection to this contract.
|
||||
|
||||
Operational difficulty: Extreme.
|
||||
|
||||
The factors:
|
||||
- *Community awareness*: Stone/Tide community, 150 people, every face known. A stranger spending more than two days triggers social comment. An assassin needs a cover identity that gives them a reason to be here for long enough to establish a pattern.
|
||||
- *Target observability*: Ysabel has a predictable dawn routine (the solo water inspection) — this is her only extended private interval. It's the obvious window. It's also the only time she is genuinely alone, which means any deviation from her solo status is immediately suspicious.
|
||||
- *Pattern rigidity*: Stone heritage = high pattern rigidity. Her routine is reliable. The assassination window is identifiable. But: she is watching for this. Her 90-minute property boundary checks, her wariness with strangers, her pre-prepared exit — she has been expecting someone to come for 14 years. She will notice surveillance.
|
||||
- *Aftermath*: Tide heritage means communal justice demand if she dies under suspicious circumstances. 150 people who loved her. They will talk. They will remember the stranger. They will ask questions that the Commission eventually hears about. "What happened to Ysabel?" is a question that could, if answered by the right investigator, reopen the original warrant investigation — and lead to Pehr Callen.
|
||||
- *The operational paradox*: Silencing her to prevent her from testifying about Callen requires an operation clean enough that it doesn't spark the investigation that would reach Callen anyway.
|
||||
|
||||
The assassination play is the hardest version of the hardest assignment: an elite target who has been hiding from exactly this for 14 years, embedded in a tight community that will investigate her death with personal intensity, in a settlement with no Meridian coverage (which means no tracking but also no controlled information environment). The assassin who does this cleanly is exceptional.
|
||||
|
||||
---
|
||||
|
||||
### Does the 10-Axis Model Cover All Five Hooks?
|
||||
|
||||
**Verdict: 4.5 of 5. One gap found.**
|
||||
|
||||
The investigation hook lives in Axes 4 (Secret) + 5 (Information Access).
|
||||
The tycoon hook lives in Axes 8 (Economic Position) + 3 (Actual Motivation).
|
||||
The dating sim hook lives in Axes 6 (Trust Architecture) + 10 (Tolerance Threshold).
|
||||
The political hook lives in Axes 9 (Relationship Network) + 2 (Surface Motivation).
|
||||
The assassination hook lives in Axes 7 (Routine Pattern) + 4 (Vulnerability).
|
||||
|
||||
**The gap:** None of the 10 axes explicitly encodes *network-level significance vs. locally-perceived significance*. Ysabel is locally-perceived as an ANCHOR with no enemies — she generates zero `assassination_difficulty` signal from the local society profile alone. The assassination hook only becomes available when a player has access to network-level intelligence (a Commission warrant database, Syndic contractor records, or specific investigation threads that trace back to Callen).
|
||||
|
||||
The 10-axis model as currently specified cannot distinguish between:
|
||||
- An NPC who is genuinely locally insignificant (no network-relevant secrets)
|
||||
- An NPC who is locally insignificant in appearance but carries network-significant information or is network-relevant to external actors
|
||||
|
||||
**Proposed extension: Axis 11 — Network Footprint**
|
||||
|
||||
```
|
||||
network_footprint: Option<NetworkFootprintTag>
|
||||
```
|
||||
|
||||
For most NPCs: `None`. They are exactly what they appear to be in the local context.
|
||||
|
||||
For NPCs like Ysabel: `Some(NetworkFootprintTag)`, which records:
|
||||
- The external actor(s) who consider this NPC significant
|
||||
- The reason (possesses evidence, was witness to event, holds a capability, has a connection)
|
||||
- The access tier required to see this footprint (investigation players reach it via Commission databases; tycoon players reach it via Syndic network intelligence; other playstyles may not reach it at all)
|
||||
|
||||
This axis does not change local behavior. Ysabel is still an ANCHOR. Her routine is still the same. But the generator can now guarantee that the Tactical triangle (Ysabel ↔ Callen's agents ↔ investigating player) is instantiated, even when the district's local `assassination_difficulty` score would not flag it.
|
||||
|
||||
**Implementation note:** The `network_footprint` field should be authored on specific NPCs and not procedurally generated. Procedural NPCs default to `None`. The "false backwater" scenario — where a network-significant actor is hiding in a locally-insignificant setting — is a named scenario type authored by content designers, not a generator output. The generator needs to *support* this scenario type (by providing the field and the Tactical triangle infrastructure), not generate it from scratch.
|
||||
|
||||
---
|
||||
|
||||
## OQ-R4-C: Assassination Difficulty Descriptor — Definitive Placement
|
||||
|
||||
**Answer: DerivedDistrictAnalysis, stored on DistrictSkeleton, computed at Phase 1 (NPC Population stage), not runtime-mutable.**
|
||||
|
||||
The question was: does `assassination_difficulty` live on the DistrictSkeleton, on the SocietyProfile, or is it computed on demand?
|
||||
|
||||
It should NOT live on the SocietyProfile directly — the SocietyProfile describes the cultural parameters, not their derived game-mechanical implications. The SocietyProfile does not know it's being used for game generation.
|
||||
|
||||
It should NOT be computed on demand by the assassination gameplay system — it's used by the Tactical triangle instantiation logic during Phase 1, before the gameplay system ever runs. Computing it late means the Phase 1 skeleton doesn't know whether to instantiate Tactical triangles, produce egress multiplicity guarantees, or budget temporal opacity windows.
|
||||
|
||||
It should NOT be on DramaDensity (runtime storyteller state) — the cultural difficulty of assassination doesn't change because the storyteller elevated the drama. A Dust-dominant community is still a Dust-dominant community regardless of how much drama is currently firing.
|
||||
|
||||
**Canonical placement:**
|
||||
|
||||
```rust
|
||||
struct DistrictSkeleton {
|
||||
// ... existing fields ...
|
||||
guarantee_audit: GuaranteeAuditResult, // existing
|
||||
derived_analysis: DerivedDistrictAnalysis, // NEW: computed from society profile
|
||||
}
|
||||
|
||||
struct DerivedDistrictAnalysis {
|
||||
assassination_difficulty: AssassinationDifficulty,
|
||||
// Computed from: observation_density × information_liquidity × aftermath_engagement
|
||||
// All three derived from society_profile.heritage blend + economic_pressure
|
||||
|
||||
assassination_target_density: u8,
|
||||
// Count of NPCs with network_footprint: Some(_) OR with local significance that makes
|
||||
// them viable local targets. Drives Tactical triangle instantiation budget.
|
||||
|
||||
primary_playstyles: [AffinityLevel; 5],
|
||||
// Per-playstyle rating derived from setting type. Drives content budget weighting.
|
||||
}
|
||||
|
||||
enum AssassinationDifficulty { Low, Medium, High, Extreme }
|
||||
```
|
||||
|
||||
**What "computed from society profile" means concretely:**
|
||||
|
||||
```
|
||||
observation_density = heritage_weighted(frost:low, tide:high, iron:medium_high,
|
||||
dust:very_high, stone:medium, arc:low, vine:high, salt:low,
|
||||
jade:high, spice:high_within_group)
|
||||
|
||||
information_liquidity = heritage_weighted(frost:very_low, tide:high, iron:high_within,
|
||||
dust:high, stone:low, arc:medium, vine:very_high, salt:conditional,
|
||||
jade:low, spice:low_to_outsiders)
|
||||
|
||||
aftermath_engagement = heritage_weighted(frost:low, tide:high, iron:high, dust:high,
|
||||
stone:medium, arc:very_high, vine:medium_high, salt:medium,
|
||||
jade:medium, spice:very_high)
|
||||
|
||||
assassination_difficulty = classify(
|
||||
observation_density × 0.35 +
|
||||
information_liquidity × 0.30 +
|
||||
aftermath_engagement × 0.35
|
||||
)
|
||||
```
|
||||
|
||||
Faction presence modifies the computed value: Commission comprehensive coverage shifts difficulty up one tier (institutional investigation adds aftermath risk). Commission absent with high-Dust community culture = the most dangerous community aftermath without institutional support.
|
||||
|
||||
**Used by:** Tactical triangle instantiation (decides how many Tactical triangles to budget per district, weighted by whether the difficulty makes assassination plausible gameplay). Spatial guarantee audit (Tier 3 conditional checks A-1 through A-4 fire for districts where `assassination_difficulty != Extreme` — Extreme difficulty districts don't need to budget for assassin success, they budget for assassin failure learning). Content balance tools (designer visibility into why a given district is Hard vs. Easy for assassination play).
|
||||
|
||||
---
|
||||
|
||||
## OQ-R4-D: Heritage Grammar Overlay — Concrete Representation for Chunk Fill
|
||||
|
||||
**Answer: Per-heritage HeritageGrammarOverlay structs stored in the generator's authored data, injected into ZonePalette at chunk fill time via weighted blending.**
|
||||
|
||||
The 10-row heritage grammar table from my Round 3 document needs a form chunk fill can consume. Araminta's question was: per-heritage modifier objects, or lookup tables within terrain palette assets?
|
||||
|
||||
Per-heritage modifier objects. The distinction matters for authoring workflow: lookup tables within terrain assets means the heritage grammar is embedded in art assets (Araminta's domain, not mine). Per-heritage modifier objects means the grammar rules are authored in worldbuilding data and consumed by chunk fill as parameters. This is the correct separation — I author the cultural grammar; the terrain palette assets provide the visual vocabulary; chunk fill applies the grammar to the vocabulary.
|
||||
|
||||
**The concrete struct:**
|
||||
|
||||
```rust
|
||||
struct HeritageGrammarOverlay {
|
||||
// For which root this applies
|
||||
heritage_root: HeritageRoot,
|
||||
|
||||
// BLOCK-LEVEL ORGANIZATIONAL PRINCIPLES
|
||||
// These affect block planning decisions, not just chunk fill
|
||||
|
||||
boundary_character: BoundaryCharacter,
|
||||
// What physical form boundaries between properties take
|
||||
// Frost: MinimalFunctional (wire/stake, minimal material)
|
||||
// Stone: PermanentMaterial (stone wall, heavy posts)
|
||||
// Tide: OpenOrNominal (cleared path, no physical barrier)
|
||||
// Iron: CollectivelyMaintained (shared fence line, communally repaired)
|
||||
// Dust: PracticalRepaired (patched material, visibly maintained because replacement is costly)
|
||||
// Vine: OrganicIntegrated (planted hedge, climbing plants as boundary)
|
||||
// Salt: ClearlyDemarcated (clean lines, legible entry points)
|
||||
// Arc: LabeledAndCategorized (marked with notation, documented)
|
||||
// Jade: AestheticlySelected (materials chosen for visual quality)
|
||||
// Spice: HonorAdjacentEnclosure (compound wall, family territory marker)
|
||||
|
||||
open_space_character: OpenSpaceCharacter,
|
||||
// What purpose the open/unclaimed space between structures serves
|
||||
// Functional / Gathering / Ornamental / Buffer / None
|
||||
|
||||
structure_spacing: f32,
|
||||
// Multiplier on base terrain spacing. 0.8 = compact, 1.0 = standard, 1.5 = dispersed
|
||||
|
||||
// CHUNK-FILL LEVEL VISUAL MODIFIERS
|
||||
// These affect which objects from the terrain palette get selected
|
||||
|
||||
decorative_density: f32,
|
||||
// 0.0 (Frost: no decorative elements) to 1.0 (Jade: maximum curation)
|
||||
// Affects: how many decorative object slots are filled from the palette
|
||||
|
||||
gathering_anchor_near_work: bool,
|
||||
// Tide/Vine: true — social spaces woven into work spaces
|
||||
// Frost/Arc: false — social and work spaces are separated
|
||||
|
||||
shared_infrastructure_preference: bool,
|
||||
// Iron/Dust: true — communal granaries, shared equipment storage
|
||||
// Salt/Spice/Frost: false — individual storage and equipment
|
||||
|
||||
repair_visibility: f32,
|
||||
// Dust: high (patched materials in visible use)
|
||||
// Jade: low (replacement preferred over visible repair)
|
||||
// Stone: medium (maintained, not replaced unnecessarily)
|
||||
// Frost: medium-low (functional repair, not displayed)
|
||||
|
||||
facade_investment: f32,
|
||||
// Spice: high (aesthetic investment in public-facing surfaces)
|
||||
// Frost: very low (exterior presents nothing)
|
||||
// Vine: medium-high (exterior says "people live here")
|
||||
// Salt: low (commercial clarity, not personal expression)
|
||||
// Jade: high (careful selection of visible materials)
|
||||
|
||||
signage_density: f32,
|
||||
// Arc: high (things labeled and categorized)
|
||||
// Frost: very low (minimal labeling)
|
||||
// Salt: medium (commercial labels, pricing visible)
|
||||
// Stone: low (land markers but not informational signage)
|
||||
|
||||
// OBJECT POOL MODIFIERS
|
||||
// Heritage-specific weighting of which objects from the terrain palette are preferred
|
||||
|
||||
preferred_enclosure_tags: Vec<ObjectTag>,
|
||||
// Which enclosure objects this heritage preferentially places
|
||||
// Frost: ["functional_stake", "wire_minimal"]
|
||||
// Stone: ["stone_wall", "heavy_post", "stone_foundation_exposed"]
|
||||
// Vine: ["planted_hedge", "climbing_trellis", "woven_stake"]
|
||||
|
||||
accent_object_tags: Vec<ObjectTag>,
|
||||
// Objects that appear more frequently in this heritage's spaces
|
||||
// Tide: ["seasonal_decoration", "gathering_table_outdoor", "shared_fire"]
|
||||
// Arc: ["label_stake", "record_board", "measurement_tool"]
|
||||
// Jade: ["aesthetic_planting", "quality_material_accent", "curated_stone"]
|
||||
|
||||
excluded_object_tags: Vec<ObjectTag>,
|
||||
// Objects this heritage actively avoids
|
||||
// Frost: ["gathering_table_outdoor", "decorative_public_display"]
|
||||
// Iron: ["private_luxury_accent", "status_display_object"]
|
||||
// Arc: ["unlabeled_storage", "disorganized_pile"]
|
||||
}
|
||||
```
|
||||
|
||||
**How chunk fill uses it:**
|
||||
|
||||
```
|
||||
1. Get district's society_profile.heritage (Vec<HeritageEntry> with blend weights)
|
||||
2. For each heritage root in blend:
|
||||
a. Look up its HeritageGrammarOverlay from authored data
|
||||
3. Blend overlay parameters by heritage weight:
|
||||
decorative_density = sum(root.decorative_density × root.blend_weight)
|
||||
repair_visibility = sum(root.repair_visibility × root.blend_weight)
|
||||
[etc. for all scalar fields]
|
||||
4. For categorical fields (boundary_character, etc.): select by dominant heritage weight
|
||||
5. For object tag lists: union of preferred/accent tags, intersection-exclusion of excluded tags
|
||||
6. Apply blended overlay to base ZonePalette:
|
||||
- Modifies object pool selection probabilities
|
||||
- Adjusts spacing and density parameters
|
||||
- Sets facade/interior investment balance
|
||||
7. Proceed with normal chunk fill using modified palette
|
||||
```
|
||||
|
||||
**Authoring workflow:** I maintain the canonical `HeritageGrammarOverlay` data for each of the 10 roots. Araminta expresses these as specific object pool compositions in her visual grammar. The chunk fill pipeline reads my data structures; Araminta's assets populate the object pools that those structures reference. Cross-domain requirement: the `ObjectTag` vocabulary must be shared between my heritage grammar spec and Araminta's asset categorization.
|
||||
|
||||
**Storage:** The `HeritageGrammarOverlay` set (10 roots) is global authored data, not per-district. It is loaded once at generator startup and referenced during chunk fill.
|
||||
|
||||
---
|
||||
|
||||
## Vessel Cultural Grammar — Finalization
|
||||
|
||||
Lead decision: entity-carried MobileChunk is CORE. Tyre's architecture wins. This closes OQ-R4-A for cultural grammar purposes.
|
||||
|
||||
My supplement (Round 3) established the cultural grammar for all vehicle types. Now that the architectural question is settled, I can finalize these as the canonical spec for Tyre's `MobileChunk` cultural layer.
|
||||
|
||||
### transit_social_modifier — Canonical Spec
|
||||
|
||||
```rust
|
||||
struct TransitSocialModifier {
|
||||
// Rate adjustments — multiplicative on base society_profile rates
|
||||
trust_building_multiplier: f32, // 1.5–2.5x (forced proximity accelerates)
|
||||
privacy_level_modifier: f32, // –0.3 to –0.6 (physical impossibility of full privacy)
|
||||
enforcement_level_modifier: f32, // –0.4 to –0.7 (institutional attenuation)
|
||||
|
||||
// Information flow modifiers
|
||||
internal_flow_multiplier: f32, // 1.5–3.0x (nowhere else to direct social energy)
|
||||
external_flow_blocked: bool, // true during interstellar transit
|
||||
|
||||
// Vehicle-type-specific grammar
|
||||
variant: TransitVariant,
|
||||
}
|
||||
|
||||
enum TransitVariant {
|
||||
BoundedLinear {
|
||||
// Train: linear access topology, class-sequence social grammar
|
||||
car_sequence: Vec<SocialCarZone>,
|
||||
// Each SocialCarZone has: access_tier, heritage_norms, dominant_heritage_override
|
||||
// The dominant heritage of each car class can differ from the train's overall profile
|
||||
// Example: premium car may be Arc/Jade-flavored; general car is Iron/Frost/Tide-weighted
|
||||
temporal_pressure: Duration,
|
||||
witness_compactness: WitnessCompactnessLevel, // Low | Medium | High | Maximum
|
||||
},
|
||||
BoundedMobile {
|
||||
// Ship within system: bounded spherical, crew insider threshold applies
|
||||
crew_insider_threshold: f32, // crew members get trust bonus vs. passengers
|
||||
passenger_manifest_seeded: bool, // true = manifest fixed at departure
|
||||
temporal_pressure: Duration,
|
||||
},
|
||||
InterSystem {
|
||||
// Between horizon gates: most extreme information asymmetry
|
||||
jurisdiction_suspended: bool, // true = no Commission enforcement authority
|
||||
external_comms_blocked: bool, // true = no contact with outside until arrival
|
||||
institutional_role_strip: f32, // 0.0–1.0 = degree to which formal roles attenuate
|
||||
// full strip (1.0) = roles are entirely social, no institutional backing
|
||||
// partial strip (0.5) = hierarchy nominally maintained but enforcement is social only
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Cultural Grammar Rules by Vehicle Type
|
||||
|
||||
**Trains (BoundedLinear)**
|
||||
|
||||
The car sequence is a physical expression of social hierarchy. The player can observe the class gradient simply by walking the train from end to end. Heritage roots determine how each class tier behaves socially:
|
||||
|
||||
| Car class | Frost-flavored | Tide-flavored | Iron-flavored |
|
||||
|---|---|---|---|
|
||||
| Premium | Compartmentalized, minimal contact, high privacy | Communal first-class dining, shared tables, conversation expected | Rarely present; if Iron workers are in premium, they are uncomfortable and close-grouped |
|
||||
| Standard | Row seating, neighbors interact minimally, book/screen focus | Groups naturally form, food shared, children move freely between seats | Solidarity cluster: workers know each other, outsider immediately noticeable |
|
||||
| General | Silent efficiency, space maintained, no eye contact | Loud, community atmosphere, information flows freely | Union-aware space: political conversation common, newcomer assessed |
|
||||
|
||||
Temporal pressure characteristic: the journey ends at a known time. This creates a countdown quality that makes every conversation feel slightly more urgent than it would in a fixed district. Social information that would take a week to surface in a bar emerges in four hours on a train.
|
||||
|
||||
Assassination on a train: classic scenario architecture. The target cannot escape. The witnesses cannot leave. The elimination window exists during the one car-class transition moment when crowd density drops and witness configuration changes. Post-action management is compressed: the assassin must establish an alibi and manage evidence during the remaining journey time, with arrival at destination being the hard deadline for investigative exposure.
|
||||
|
||||
**Ships within system (BoundedMobile)**
|
||||
|
||||
Crew insider threshold means: crew members have a distinct trust baseline toward each other and a different baseline toward passengers. The ship has two overlapping social contexts — crew community (stable, long-established) and passenger community (temporary, journey-specific).
|
||||
|
||||
Heritage root behavior for crew:
|
||||
- Iron-heritage crew: high solidarity, newcomers (including player) are assessed by competence and contribution before being trusted
|
||||
- Salt-heritage crew: transactional; they'll exchange information if there's value in it
|
||||
- Tide-heritage crew: the ship becomes their community; they welcome passengers more readily than Iron crew would
|
||||
|
||||
Heritage root behavior for passenger pool (seeded at departure from available NPC pool at origin):
|
||||
- The passenger manifest is the scenario's social content; its heritage composition is seeded from the departure location's population distribution
|
||||
- A ship leaving from a Frost-dominant hub has different passenger social dynamics than one from a Tide/Vine port
|
||||
|
||||
**Interstellar transit (InterSystem)**
|
||||
|
||||
The most extreme case. `jurisdiction_suspended: true` means the ship exists in a legal vacuum. Normal enforcement depends entirely on the crew's own authority, which is social rather than institutional during transit.
|
||||
|
||||
Heritage root behaviors under institutional suspension:
|
||||
|
||||
| Heritage root | Behavior under suspension |
|
||||
|---|---|
|
||||
| Frost | Doubles down on privacy. Less interaction, not more. The individual becomes more contained as external structures relax. |
|
||||
| Tide | The ship-community expands to fill the void. Social bonds form fast. The journey becomes the community. |
|
||||
| Iron | Collective awareness activates. Workers organize informally. Whoever has practical competence gets listened to regardless of formal role. |
|
||||
| Salt | Grey-market window opens immediately. Things that couldn't be traded under normal jurisdiction can now be traded. The Salt-heritage passenger is calculating. |
|
||||
| Arc | Discourse opportunity. The suspended context removes the social penalties for unconventional conversation. The Arc-heritage traveler wants to talk about things they couldn't in normal social context. |
|
||||
| Spice | Family networks become the social unit. Non-family outsiders are on the outside of the primary social structure. |
|
||||
|
||||
`external_comms_blocked: true` means the information environment is sealed. An investigator who reaches an in-transit ship has maximum information opportunity (witnesses can't leave, everyone is accessible) with zero outside support. Whatever they learn, they must act on within the confines of the journey.
|
||||
|
||||
### Setting note on information asymmetry in transit
|
||||
|
||||
Transit is where the game's core mechanic is most exposed. Normal districts allow players to defer: talk to that NPC tomorrow, check the archive next week, come back when you have better access. Transit removes deferral. The pressure is the content.
|
||||
|
||||
Every vehicle type compresses the information landscape. The same NPC who would take three weeks of careful relationship-building in a district may reveal the same information in a 6-hour voyage — because the social pressure of proximity, the absence of external distraction, and the shared condition of confinement change the trust calculus. The transit modifier multipliers are not arbitrary: they reflect what forced proximity does to human social behavior.
|
||||
|
||||
---
|
||||
|
||||
## Summary: Round 4 Deliverables
|
||||
|
||||
**OQ-R4-E (WRITE THE NPC):** Done. Ysabel Vorn, 14 years at Harrow Drift, Stone/Tide farming settlement. Full 10-axis profile. All five lenses demonstrated with specific information ladders, gameplay hooks, and operational details.
|
||||
|
||||
**Finding from the NPC exercise:** The 10-axis model covers 4.5 of 5 lenses fully. The gap: no axis for network-level significance vs. locally-perceived significance. A locally-appearing NPC who is a network-significant target is currently invisible to the assassination lens until a player has network-level access. Proposed: Axis 11 (Network Footprint) as an authored field, defaulting to `None` for procedural NPCs, set explicitly for scenario NPCs like Ysabel.
|
||||
|
||||
**OQ-R4-C (Assassination Difficulty Placement):** `DerivedDistrictAnalysis` struct on `DistrictSkeleton`, computed at Phase 1 from society profile parameters. Not runtime-mutable. Carries `assassination_difficulty: AssassinationDifficulty`, `assassination_target_density: u8`, and `primary_playstyles: [AffinityLevel; 5]`.
|
||||
|
||||
**OQ-R4-D (Heritage Grammar Overlay Encoding):** Per-heritage `HeritageGrammarOverlay` structs in authored global data. Chunk fill blends overlays by heritage weight and applies to base ZonePalette. Authoring domain is mine (cultural rules); visual asset expression is Araminta's (object pool populations). `ObjectTag` vocabulary must be shared.
|
||||
|
||||
**Vessel cultural grammar:** Finalized as `TransitSocialModifier` with `TransitVariant` enum covering BoundedLinear (trains), BoundedMobile (in-system ships), and InterSystem (between horizon gates). Heritage-root behavior tables by vehicle type. All content in my Round 3 supplement is now formalized as a concrete spec ready for Tyre's MobileChunk implementation.
|
||||
|
||||
---
|
||||
|
||||
**Author:** Miri
|
||||
**Date:** 2026-02-27
|
||||
**Status:** Round 4 complete.
|
||||
|
||||
**One item for the D-record stack:** The `HeritageGrammarOverlay` struct should become a D-record (adds to D-READY-9: Heritage Grammar Overlay for Non-Urban Palettes). The NPC Axis 11 finding should be raised as a new Q-record for the next sprint — it is a gap in the current NPC generation model that affects assassination scenario instantiation specifically.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Generator Architecture Workshop — Round 5: Miri (Worldbuilder)
|
||||
|
||||
**Topic:** Final review of workshop-outcomes.md before D-record filing.
|
||||
**Date:** 2026-02-27
|
||||
**Scope:** Sign-off + corrections only. No new proposals.
|
||||
|
||||
---
|
||||
|
||||
## Sign-Off Summary
|
||||
|
||||
Most of the document is accurate. One factual error in D-READY-10 heritage root correlations. All other items I was asked to verify are correct.
|
||||
|
||||
---
|
||||
|
||||
## Verified Correct
|
||||
|
||||
**Ysabel Vorn litmus test (NPC Model section):** Accurate. 4.5/5 stated correctly. 10 axes listed correctly and match my Round 4 document. Axis 11 (Network Footprint) raised correctly as a Q-record, not a confirmed decision. The "authored scenario NPCs" framing is right — this should never be procedurally generated.
|
||||
|
||||
**D-READY-9 (Heritage Grammar Overlay):** Authoring domain separation is correct.
|
||||
- Miri: organizational principles, boundary character, spacing, social grammar (HeritageGrammarOverlay Rust struct)
|
||||
- Araminta: visual expression — object sets, arrangement algorithms, lighting temperature (TOML modifier files)
|
||||
- Shared: ObjectTag vocabulary co-maintenance requirement
|
||||
|
||||
**D-READY-12 (Trauma Events):** Subtypes match my Round 3 specification (PhysicalDestruction, EconomicDisruption, PoliticalShock, ViolenceEvent, MigrationShock). Dual-track model (structural damage via StructuralChange; cultural response via NPC pattern weight shifts) is correct. Heritage-seeded decay rate variation is correct.
|
||||
|
||||
**D-READY-13 (MobileChunk / Vessel):** Correctly points to my Round 4 document for the canonical TransitSocialModifier and TransitVariant spec. BoundedLinear / BoundedMobile / InterSystem enum variants listed correctly.
|
||||
|
||||
**OQ-R4-C synthesis (Q-NNN-f):** The framing — "stored cultural baseline (DerivedDistrictAnalysis on skeleton) + on-demand computation for player-facing assessment" — is an acceptable synthesis. Clarification I want on record: if on-demand computation is added for player-facing use, it is subordinate to the Phase 1 DerivedDistrictAnalysis value. Game logic (Tactical triangle instantiation, guarantee audit) uses the Phase 1 value. Any on-demand computation is display-only. This should be explicit in the formal D-record.
|
||||
|
||||
---
|
||||
|
||||
## Correction Required — D-READY-10
|
||||
|
||||
**Current text:** "Frost/Stone → physical_distance; Tide/Vine → social_permission; Dust/Salt → utilitarian_cover."
|
||||
|
||||
**Error:** Dust is misclassified. Dust should be `social_permission`, not `utilitarian_cover`.
|
||||
|
||||
**Canonical source:** My Round 3 document, Section 2.4:
|
||||
|
||||
> "Frost → physical_distance (individual space is respected everywhere)"
|
||||
> "Tide/Dust → social_permission (negotiated privacy within community framework)"
|
||||
> "Iron → utilitarian_cover (labor function covers presence)"
|
||||
|
||||
**Why this matters:** Dust culture is characterized by maximum communal observation — survival-level social awareness, shared information as a community good. In a Dust community, the only privacy available is **negotiated** ("we agree not to see what you're doing"). There is no privacy via physical distance (everyone sees everything) and no privacy via utilitarian cover (in a Dust community, being in the barn is suspicious precisely because it's isolated from the collective). Dust belongs with Tide in `social_permission`.
|
||||
|
||||
**Iron is missing entirely.** Iron → utilitarian_cover is the clearest mapping: labor function covers presence ("what workers do after the shift" and "what happens in the union hall"). Iron should appear under `utilitarian_cover`.
|
||||
|
||||
**Corrected heritage root correlations:**
|
||||
|
||||
- `physical_distance`: Frost, Stone
|
||||
- `social_permission`: Tide, Vine, **Dust**
|
||||
- `utilitarian_cover`: **Iron**, Salt
|
||||
|
||||
Note: Stone, Vine, and Salt were not mapped in my Round 3 document — these are plausible extensions I accept. The firm corrections are Dust (wrong type) and Iron (missing).
|
||||
|
||||
**Corrected text for D-READY-10:**
|
||||
|
||||
> Heritage root correlation: Frost/Stone → `physical_distance`; Tide/Vine/Dust → `social_permission`; Iron/Salt → `utilitarian_cover`. Location within terrain is seeded independently.
|
||||
|
||||
---
|
||||
|
||||
## One Omission (Not a Factual Error)
|
||||
|
||||
D-READY-12 is technically correct but loses a framing point that has gameplay implications. The design principle underlying trauma event response is:
|
||||
|
||||
**Trauma intensifies culture, it does not transform it.**
|
||||
|
||||
A stressed community becomes a more concentrated version of itself. Frost communities close harder. Tide communities grief more publicly. Iron communities organize more collectively. The heritage root character is amplified under stress, not replaced.
|
||||
|
||||
This matters for gameplay because players who have learned a heritage root's trust model can predict community behavior in the aftermath — and should be able to. It's not in the D-record language anywhere. Suggest adding a one-line note to the D-record: "Cultural response is heritage-root intensification, not transformation. Decay is toward the community's pre-trauma baseline, not toward a new equilibrium."
|
||||
|
||||
---
|
||||
|
||||
## Status
|
||||
|
||||
One correction required (D-READY-10 heritage correlations). One clarification requested (OQ-R4-C on-demand computation is display-only). One framing note suggested for D-READY-12.
|
||||
|
||||
Everything else: confirmed accurate.
|
||||
|
||||
**Author:** Miri
|
||||
**Date:** 2026-02-27
|
||||
**Status:** Round 5 complete.
|
||||
@@ -0,0 +1,308 @@
|
||||
# Generator Architecture Workshop — Round 1: Nigel (Replayability & Procedural Generation)
|
||||
|
||||
**Date:** 2026-02-27
|
||||
**Role:** Replayability Advocate
|
||||
**Task:** What variation and replayability guarantees must the generator provide? What makes two generated districts feel different?
|
||||
|
||||
---
|
||||
|
||||
## Framing: Why This Is the Most Important Design Question in the Whole Project
|
||||
|
||||
Before I get into mechanics, I need to say something bluntly: **the generator is the project's long-term survival mechanism.** The hand-authored v0.1 Transit District is brilliant — it will produce exactly the experience we designed. But that's one playthrough. Maybe two if you swap characters. The 300-world model isn't about 300 unique hand-crafted stories. It's about a generator that produces *600 different stories from 300 different seeds*, with the player discovering that their playthrough of System X bears almost no resemblance to their friend's playthrough of the same system.
|
||||
|
||||
That's the promise. Here's what the generator architecture needs to guarantee to keep it.
|
||||
|
||||
---
|
||||
|
||||
## Section 1: Non-Negotiable Replayability Guarantees
|
||||
|
||||
The generator must provide hard guarantees at three layers. If any of these fail, we're building a content machine that burns through players in a single sitting.
|
||||
|
||||
### Guarantee 1: Structural Non-Repeatability Per Seed
|
||||
|
||||
Every game start must produce a configuration that is *informationally unique*. Two playthroughs on different seeds must differ in:
|
||||
|
||||
- **Who is entangled** with the conspiracy — the 20% entanglement assignment (D-029) must be drawn from a pool large enough that the same NPC is rarely the investigation target across seeds
|
||||
- **Where the evidence is** — manifest discrepancies, corridor access tokens, physical evidence placement must vary in location, not just skin
|
||||
- **Which Tier 1 modules are active** — the pool-draw at game start (D-023) means different conspiracies are running in different seeds. Same district, different crime.
|
||||
- **Which triangles are in what configuration** — the *shape* of the social network (who knows whom, who suspects whom) must vary, not just the NPC portraits
|
||||
|
||||
This is structural randomness. It's decided at game start and baked into the seed state (Q-030). The player's knowledge from playthrough 1 becomes actively misleading in playthrough 2 — because they'll chase the same suspect types and find different people.
|
||||
|
||||
### Guarantee 2: Character-as-Fundamentally-Different-Game
|
||||
|
||||
The smuggler and detective playing in the same generated world must describe *incompatible experiences* of the same district. They're not seeing different parts of the same map. They're constructing entirely different narratives from the same substrate.
|
||||
|
||||
This guarantee is mostly architectural (information boundaries, separate monologue pools per D-032, separate access tier dynamics) but the generator has a role: it must produce districts where **both character lenses yield distinct, valid gameplay**. A generated district that only makes sense from one character perspective breaks the core promise of D-027.
|
||||
|
||||
Concretely: every generated district must contain at minimum one social site that reads as *mundane infrastructure* to the smuggler (their workplace camouflage) and as *institutional checkpoint* to the detective (access-tier gating). Same space. Different game.
|
||||
|
||||
### Guarantee 3: Knowledge Rot Across Playthroughs
|
||||
|
||||
Player knowledge from playthrough 1 must not trivialize playthrough 2. This is the "no metagaming" guarantee. The generator achieves this by varying per seed:
|
||||
|
||||
- **NPC tolerance thresholds** (D-064) — the social calculus is different every run. The walk-away rule you learned last time doesn't apply.
|
||||
- **Entanglement pattern** (D-029) — the 20% rate varies per seed. In some runs, the bartender is clean. In others, they're the second link in the chain.
|
||||
- **Evidence placement** — you can't remember where the manifest is. It moves.
|
||||
- **Triangle configuration** — which investigative path (A, B, or C per D-093) leads somewhere productive depends on which NPCs happen to be in which relationships this run.
|
||||
|
||||
The generator must surface these as *seeded structural choices* — captured in the seed-state.yaml (Q-030) so they're reproducible, but varied enough that two seeds produce genuinely different strategic terrain.
|
||||
|
||||
---
|
||||
|
||||
## Section 2: Variation Axes at Each Pipeline Stage
|
||||
|
||||
Here's where I want to be precise. Every pipeline stage has levers. Some vary the *structure* of the world (high impact, discovered late). Some vary the *surface* of the world (moderate impact, immediately visible). Both matter. Let's name them.
|
||||
|
||||
### Stage 1: Geography
|
||||
|
||||
**Fixed across runs of the same world type:** Planet class, station type, orbital position, climate band (these define the setting type — Transit Hub, Outpost, Capital).
|
||||
|
||||
**Variable per seed:**
|
||||
- **Site topology** — where the district sits on the planet/station surface. Coastal vs. inland. High-orbital vs. close-in. These shape infrastructure routing and therefore access topology.
|
||||
- **Historical event seed** — how old is this settlement? What happened here? A district that survived a civil conflict 50 years ago has repurposed buildings, blocked corridors, uneven maintenance. This is "geology for social spaces."
|
||||
|
||||
**Impact on player:** Shapes the *feeling* of the district before a single NPC spawns.
|
||||
|
||||
### Stage 2: Infrastructure
|
||||
|
||||
**Fixed:** The basic transport logic (gates connect to horizon station, tram connects districts — D-095). The access topology *model* (D-025 functional clusters require connected space with sightlines).
|
||||
|
||||
**Variable per seed:**
|
||||
- **Transport node placement** — where The Loop platform drops players shapes which entry path is natural vs. requires intent. This creates different ambient NPC traffic flows per run.
|
||||
- **Utility routing** — maintenance corridor networks are seeded from infrastructure placement. Same building types, different back-routes. The smuggler's map of "safe passages" varies per run.
|
||||
- **Faction infrastructure presence** — Commission checkpoint density varies by faction weight at the district level. Heavy Commission presence = more formal access barriers. Low presence = more informal, permeable spaces.
|
||||
|
||||
**Impact on player:** Movement grammar changes. The routes you find in one run aren't the safe routes in the next.
|
||||
|
||||
### Stage 3: Amenities and Services (Faction/Economic Layer)
|
||||
|
||||
This is where the *personality* of the district gets determined. I want to emphasize: this stage has the highest variety payoff per authored ingredient.
|
||||
|
||||
**Variable per seed:**
|
||||
- **Faction control weight** — which factions are strong in this district this run? Expressed as power gradient across the six social sites. A Commission-heavy Transit District feels oppressive and procedurally ordered. An independently-weighted district feels informal, chaotic, full of unofficial arrangements.
|
||||
- **Economic tier** — prosperous districts have different building quality, different NPC behavior, different contraband (premium lattice components vs. bulk diverted medical grade). The investigation *texture* changes.
|
||||
- **Historical economic events** — strikes, booms, collapses. A district recovering from an economic shock has half-finished buildings, converted spaces, NPCs with disrupted routines.
|
||||
- **Cultural ingredient composition** (Q-032's 6-category menu) — Heritage Roots + Settlement Motivation + Economic Function + Philosophical Alignment + Corporate/Faction Presence + Drift Stage. These feed into the district's flavor palette. Same zoning type, completely different cultural *atmosphere*.
|
||||
|
||||
**Impact on player:** The investigation surface changes. Different NPCs have different leverage points. Different faction alignments create different institutional blind spots to exploit.
|
||||
|
||||
### Stage 4: Zoning
|
||||
|
||||
**Fixed:** The *type* of zone (residential, commercial, industrial, institutional) defines the template pool to draw from. This is the invariant skeleton.
|
||||
|
||||
**Variable per seed:**
|
||||
- **Zone density** — how many blocks of each type per district? A predominantly industrial district with only one social venue feels different from a mixed-use district with competing social centers.
|
||||
- **Zone boundary placement** — where industrial meets residential creates friction zones (D-051: "settling is placement"). Generator seeds the friction topology.
|
||||
- **Multi-block reservation selection** — which civic structure templates are drawn for this district? A district that rolled "active Commission oversight station" plays very differently from one that rolled "decommissioned processing facility."
|
||||
|
||||
**Impact on player:** The strategic landscape — where to go, what access is blocked by what authority — varies per seed.
|
||||
|
||||
### Stage 5: Block Generation
|
||||
|
||||
This is the sub-chunk quarter system's home. I'll expand this in Section 3.
|
||||
|
||||
**Variable per seed:**
|
||||
- Quarter merge pattern per block (which chunks combine into one building)
|
||||
- Building footprint variety within zoning type
|
||||
- Flavor structure assignment in unclaimed quarters
|
||||
|
||||
**Impact on player:** Physical movement, chokepoints, sightlines. The detective's surveillance positions and the smuggler's shadow routes are never identical across runs.
|
||||
|
||||
### Stage 6: Chunk Fill
|
||||
|
||||
**Variable per seed:**
|
||||
- **Social site template selection from pool** — given the zoning type and district skeleton, which specific templates are instantiated? Pool is larger than draws, so each run draws a subset.
|
||||
- **NPC generation** — 10-axis rolls (D-024) produce different personality configurations within the same role. Same "dock worker" role, different tolerance threshold, different secret, different relationship network.
|
||||
- **Triangle configuration** — which NPCs end up in which triangle positions? The three-NPC conflict topology is seeded, not scripted.
|
||||
- **Entanglement assignment** — which of the generated NPCs are in the 20% entangled group?
|
||||
|
||||
**Impact on player:** The people are different. The social dynamics are different. The investigation is structurally similar (find the manifest discrepancy, follow the social chain) but the *cast* produces different stories.
|
||||
|
||||
---
|
||||
|
||||
## Section 3: The Sub-Chunk Quarter System as Replayability Engine
|
||||
|
||||
This is where I get genuinely excited, because the sub-chunk quarter system is doing *exactly the right thing* without requiring the generator to hand-craft every building.
|
||||
|
||||
### How Quarters Work for Variety
|
||||
|
||||
A block = 2×2 chunks. A chunk = 32×32 visual tiles. Each chunk divides into 4 quarters (16×16 visual each). The quarter merge rules from the workshop brief produce:
|
||||
- **2×2 merge** → full-chunk building (a substantial structure filling the whole chunk)
|
||||
- **1×2 merge** → half-chunk building (corridor-adjacent, compact spaces)
|
||||
- **L-shape** → asymmetric footprint that suggests organic growth / retrofitted structure
|
||||
- **Separate quarters** → small structures with space between them (gardens, stalls, shacks)
|
||||
|
||||
The *replayability payoff*: the same block zoning type can produce five or more distinct physical configurations from the same template pool. A commercial block could be one large market hall (2×2 merge), two competing shops (1×2 merges), or four small stalls with a shared courtyard (separate quarters).
|
||||
|
||||
### What Changes Per Run at the Quarter Level
|
||||
|
||||
The quarter merge decision is seeded from:
|
||||
1. The block's economic tier (wealthy = larger, consolidated footprints; struggling = fragmented, improvised)
|
||||
2. The cultural ingredient composition (certain cultural inputs favor dense/compact; others favor distributed/informal)
|
||||
3. The historical event seed (a bombed district that rebuilt has irregular merge patterns — one full building next to a gap-filled plot)
|
||||
4. Random seed noise within the above constraints (same inputs can produce multiple valid configurations)
|
||||
|
||||
This means: **two players in the same district type, same economic tier, but different seeds will navigate different physical spaces.** The chokepoints move. The sightlines change. The surveillance dead zones are different.
|
||||
|
||||
### The Perceived Variety Effect
|
||||
|
||||
From the player's perspective, they never see "this is a 2×2 chunk merge." They see a loading bay that feels like it was retrofitted from something bigger. Or a row of small workshops with a narrow alley between them. The quarter system produces *architectural intention* — the sense that someone built this for a reason — without requiring hand-authoring every building.
|
||||
|
||||
And crucially: **this variety is consistent within a single playthrough but differs across playthroughs.** The player can learn this run's layout. But they can't memorize a reusable solution because the next run's geometry is different.
|
||||
|
||||
---
|
||||
|
||||
## Section 4: Flavor Structure Assignment to Unclaimed Quarters
|
||||
|
||||
This is where the district gets its texture. When a quarter doesn't merge into a larger building and isn't claimed by a social site template, it becomes *unclaimed space*. This space must feel inhabited, not empty.
|
||||
|
||||
### What Goes in Unclaimed Quarters
|
||||
|
||||
Unclaimed quarters get assigned from a **district flavor palette** — a weighted list of small structures appropriate to the cultural and economic context. I'd propose these palette categories:
|
||||
|
||||
**Informal Economy Indicators:**
|
||||
- Market stalls (active trade, high foot traffic)
|
||||
- Vendor carts (semi-permanent, clusters near transit nodes)
|
||||
- Informal repair shops (tools, spare parts — signals self-reliance)
|
||||
|
||||
**Settlement Indicators:**
|
||||
- Container gardens (food independence, community care)
|
||||
- Improvised seating clusters (social gathering, no institutional oversight)
|
||||
- Personal shrines / memorial spaces (community attachment, time depth)
|
||||
|
||||
**Economic Stress Indicators:**
|
||||
- Shacks / temporary shelters (population overflow, institutional failure)
|
||||
- Abandoned equipment (former economic use, now repurposed or left)
|
||||
- Unauthorized storage (gray-market goods movement)
|
||||
|
||||
**Faction Presence Indicators:**
|
||||
- Commission kiosks / checkpoint remnants (even if unmanned — implies surveillance norm)
|
||||
- Union hall notice boards (organized labor presence)
|
||||
- Corporate branded infrastructure (Syndic, independent operators)
|
||||
|
||||
### Assignment Logic
|
||||
|
||||
Palette composition is driven by cultural ingredients (Q-032) and economic tier:
|
||||
- **Economic tier** determines the ratio of formal/informal and stressed/stable indicators
|
||||
- **Cultural Heritage Roots** determine which specific types appear (some cultures have garden traditions, others don't)
|
||||
- **Faction Presence ingredient** determines which faction-affiliated structures appear
|
||||
- **Drift Stage** (how long since foundation) determines how much improvised vs. planned infrastructure exists
|
||||
|
||||
The key design rule: **unclaimed quarters must always feel *inhabited*, not empty.** A vacant lot is not flavor. A vacant lot with a rusted cargo manifest posted to a pole, someone's coat hanging on a conduit, and a drainage channel someone diverted with a bent plate — that's a space with history. The generator must assign flavor at enough density that every quarter reads as a decision someone made.
|
||||
|
||||
---
|
||||
|
||||
## Section 5: What Makes Two Districts of the Same Zoning Feel Different
|
||||
|
||||
Two Transit Hub districts with the same zoning type should feel like entirely different worlds. Here's the full variety stack:
|
||||
|
||||
### Layer 1: Cultural Personality
|
||||
The cultural ingredients menu (Q-032) is the highest-leverage differentiator. Same "industrial transit hub" zoning, but:
|
||||
- **Heritage Roots A + Philosophical Alignment B** → dense, communal, visible labor culture. Corridors have murals. Shared meal spaces.
|
||||
- **Heritage Roots C + Corporate Presence D** → efficient, branded, transactional. Clean corridors. Everything labeled. Privacy expectations low.
|
||||
|
||||
These produce different NPC naming conventions, different ambient dialogue flavor, different informal behavior patterns. The *feel* of the district is completely different before you've seen a single building footprint.
|
||||
|
||||
### Layer 2: Faction Power Gradient
|
||||
Same zoning, different faction control:
|
||||
- **Commission-heavy** → surveillance cameras visible, security NPCs on patrol routes, formal checkpoint infrastructure at zone transitions
|
||||
- **Syndic-heavy** → corporate logos on infrastructure, trade efficiency in customs processing, information flow controlled by corporate NPC hierarchy
|
||||
- **Weakly controlled** → informal arrangements visible, gray-market activity in open spaces, NPCs with more freelance social relationships
|
||||
|
||||
### Layer 3: Economic Tier
|
||||
- **Prosperous** → larger consolidated buildings (2×2 quarter merges), maintained infrastructure, NPCs with stable routines and higher tolerance thresholds
|
||||
- **Struggling** → fragmented footprints (separate quarters), improvised flavor structures, NPCs with disrupted routines and higher social volatility
|
||||
|
||||
### Layer 4: Historical Event Seed
|
||||
The district's backstory leaves physical traces:
|
||||
- A labor dispute 20 years ago → union hall still present, specific NPCs with long-memory grievances, certain maintenance corridors blocked from an old barricade never fully cleared
|
||||
- A corporate merger 10 years ago → two different architectural styles visible (the original and the acquisition), NPCs from both eras with residual loyalty conflicts
|
||||
|
||||
### Layer 5: Quarter Merge Patterns
|
||||
Same block count, different geometry. The routes the player develops are different. The surveillance chokepoints are different. The safe approach to any given social site varies.
|
||||
|
||||
### Layer 6: Entanglement Pattern
|
||||
Most importantly: *who's in on it is different.* The district's surface can look similar, but the hidden configuration — who knows what, who suspects whom, where the evidence ended up — is the layer the player actually investigates. Two districts with identical exteriors can produce completely different investigative experiences because the entanglement seed is different.
|
||||
|
||||
---
|
||||
|
||||
## Section 6: Preventing Sameyness After 10+ Playthroughs
|
||||
|
||||
The "sameyness problem" is the deepest design challenge. Here's my analysis of what causes it and how each mechanism fights it.
|
||||
|
||||
### Root Cause 1: The Player Has Solved the Puzzle
|
||||
|
||||
If investigation always follows the same sequence — find X, talk to Y, check Z — then playthrough 2 is just playthrough 1 faster. The generator defeats this with:
|
||||
- **Variable evidence placement** — the manifest discrepancy isn't always in the Terminal. The generator places it based on which NPC is entangled with the ring this run.
|
||||
- **Variable investigation paths** — Paths A, B, and C (D-093) are all viable, but which one is *actually open* this run depends on the NPC relationships generated. You can't pre-plan your investigation path.
|
||||
- **Variable NPC knowledge** — which NPC knows what, and when they'll share it, depends on the trust/knowledge graph generated for this run. Your interrogation sequence from last run won't work.
|
||||
|
||||
### Root Cause 2: The Player Knows the Map
|
||||
|
||||
Physical memory of the layout transfers across playthroughs. The sub-chunk quarter system partially defeats this by changing geometry, but it's not enough on its own. The generator must also:
|
||||
- **Vary chokepoint placement** — by varying quarter merge patterns, the location of natural surveillance positions changes per run
|
||||
- **Vary zone boundary placement** — where informal meets formal access creates different barrier topologies
|
||||
- **Vary faction infrastructure** — Commission checkpoints appear in different locations per faction weight seed
|
||||
|
||||
The player should feel *oriented but not certain* on playthrough 2. The district type is familiar (Transit Hub). The specific layout is new.
|
||||
|
||||
### Root Cause 3: The Player Knows the NPCs
|
||||
|
||||
NPC *roles* stay recognizable (dock worker, customs officer, bar regular). But NPC *personalities* — the 10-axis configuration — vary per seed. The dock worker who was the social anchor last time is suspicious and cold this run. The customs officer who was hostile is a potential ally this run. The player recognizes the role. The person is different.
|
||||
|
||||
Combined with invisible locked dialogue (D-062), this means: the player can't replay their successful social script. They have to actually read the new NPCs.
|
||||
|
||||
### Root Cause 4: The Player Knows the Meta-Strategy
|
||||
|
||||
"I know that 20% of NPCs are entangled, so I'll focus on the ones near the obvious crime site." The generator defeats this by:
|
||||
- **Variable entanglement rate** (D-029) — the 20% is an average, not a constant. This run it might be 15%. Or 27%. You can't calibrate.
|
||||
- **Entanglement in unexpected roles** — the generator shouldn't always entangle the obvious suspects. A well-designed generator seeds entanglement toward NPCs whose involvement creates narrative surprise, not pattern-matching.
|
||||
|
||||
### The Comparison Test
|
||||
|
||||
I want to name a specific test this generator must pass: **two players should be able to compare notes and find genuinely different investigation experiences from the same world type.**
|
||||
|
||||
Player A (Smuggler, Seed 42137): "The ring was running through the Gate Cluster customs officer — she was the commission's inside person, but she was also protecting her brother who worked freight. I had to decide whether to expose her or use her."
|
||||
|
||||
Player B (Detective, Seed 81204): "The ring used the maintenance corridor access system — someone had cloned the access tokens. I traced it through the locker room logs to a dock worker who was trying to fund his partner's medical lattice replacement."
|
||||
|
||||
Same district type. Same zoning. Different seeds. Different characters. **Completely different game.**
|
||||
|
||||
If two players can compare notes and their stories are mostly the same — just with different NPC names — the generator has failed.
|
||||
|
||||
---
|
||||
|
||||
## Section 7: What I Need from Other Workshop Participants
|
||||
|
||||
**From Gestalt:** What are the guaranteed gameplay structures every district must contain? (A surveillance chokepoint, a quiet zone, a social hub — confirm the list.) These constraints are my floor. The variation lives above this floor.
|
||||
|
||||
**From Tyre:** How does the seed propagate through the pipeline? Is it a single master seed that derives all sub-seeds deterministically, or does each stage have its own seed parameter? I need to know whether "same seed, different character selection" produces the same world with different lenses, or genuinely different worlds.
|
||||
|
||||
**From Miri:** How large is the cultural ingredients space? (Q-032 specifics.) The variety payoff of the cultural composition layer depends entirely on how many distinct ingredient combinations produce distinguishable district personalities. If there are only 8 valid combinations, we'll see repetition at scale. If there are hundreds, the generator stays fresh.
|
||||
|
||||
**From Araminta:** What visual vocabulary signals distinguish the six flavour categories I proposed? The flavor structure assignment system needs Araminta's palette to produce coherent spaces, not random tile mixtures.
|
||||
|
||||
---
|
||||
|
||||
## Summary: The Variation Axes I'm Proposing
|
||||
|
||||
| Axis | Pipeline Stage | Impact |
|
||||
|---|---|---|
|
||||
| World seed (master) | Game start | Derives all downstream variation |
|
||||
| Character selection | Game start | Fundamentally different information lens |
|
||||
| Tier 1 module pool draw | Game start | Which conspiracies are active |
|
||||
| Cultural ingredient composition | Geography + Economic | District personality and flavor |
|
||||
| Faction power gradient | Economic/Infrastructure | Access topology, NPC power dynamics |
|
||||
| Economic tier | Economic | Building scale, routine stability, social volatility |
|
||||
| Historical event seed | Geography | Physical traces, long-memory NPCs |
|
||||
| NPC 10-axis generation | Chunk fill | Who these people actually *are* |
|
||||
| Quarter merge pattern | Block generation | Physical layout, sightlines, routes |
|
||||
| Entanglement assignment | Chunk fill | Who's actually involved |
|
||||
| Triangle configuration | Chunk fill | Social investigation structure |
|
||||
| Evidence placement | Chunk fill | Where investigation starts |
|
||||
| NPC tolerance thresholds (per seed) | Chunk fill | Social calculus varies per run |
|
||||
|
||||
**Bottom line:** The generator doesn't produce 300 worlds. It produces 300 * (character options) * (cultural ingredient combinations) * (seed entropy) distinct game experiences. At 300 worlds, two playable characters, and a cultural space with even 20 distinct compositions, you're looking at 12,000 meaningfully distinct games before you factor in seed variation. That's the promise. The architecture must deliver it.
|
||||
|
||||
The replayability doesn't come from *more content*. It comes from systems that produce *different configurations of the same content*.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user