Files
settled-reach/.pql/changelog/tickets/2026-03.sql
T
jpmschweitzerandClaude Opus 4.8 5adf2b8bd7 migrate ticket changelog to record_id identity (D-26)
Rewrites the committed .pql/changelog/ from the old T-NNN-keyed format to
the record_id identity model (pql 1.10.0, D-26): tickets keyed by a ULID
record_id, the friendly T-NNN in ticket_idmap, references and hashes
recomputed. Produced by the one-off cmd/migrate-ids in the pql repo.

Other clones: install pql >= 1.10.0, then rm .pql/pql.db && pql plan
rebuild to regenerate the local DB from this changelog.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 09:29:34 +02:00

515 lines
256 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRY1HCDQP7974SV5TP0', 'story', '06FB0TNSRYSBCPK60TQ60MMRVM', 'Workplace template - logistics hub', 'Template definition: cargo handlers, supervisors, shift workers. Work routines, break room, cargo bay. 6-8 NPCs.', 'done', 'critical', NULL, NULL, NULL, '2026-02-10 23:25:13', '2026-03-03 16:12:25', NULL, 'ba8c779b40598be5a0fe39f1ffab22c6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZET6MRSYMGKWW35GG', 'task', '06FB0TNSRXNT4SCS3MEPCKGPXG', 'Protocol version handshake implementation', 'Add version negotiation to IPC handshake. Server sends protocol_version in initial message, client validates compatibility. Fail fast on mismatch with clear error message.\n\nEffort: XS\nCodebase: server + client\nSource: Architecture Review Audit 2026-02-11\nSprint: 1\n\nSPLIT (Sprint 19): Split into #555 (server, team: server) and #556 (client, team: ci). Do not assign this ticket to any sprint.', 'done', 'critical', NULL, NULL, NULL, '2026-02-11 20:26:40', '2026-03-03 16:12:25', NULL, 'd0094850d566870872dedc717001baa1', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWC8M1K4WANVWXQXBW', 'story', '06FB0TNSRXX5X1PDENVXC4DRJ4', 'Chunk data structure', 'Define chunk size (16x16 or 32x32 tiles). Chunk storage, indexing, coordinate mapping.', 'done', 'high', NULL, NULL, NULL, '2026-02-10 23:22:01', '2026-03-03 16:12:37', NULL, '6204681799edf84925843be146093f3a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXBRGE96KBK591B9X0', 'story', '06FB0TNSRXX5X1PDENVXC4DRJ4', 'Chunk generation system', 'Procedural chunk generation from templates. Bounded map = only generate chunks within boundary.', 'done', 'high', NULL, NULL, NULL, '2026-02-10 23:22:01', '2026-03-03 16:12:37', NULL, 'a70defba8998482f071275068feb5f54', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRX3Y0NA9YWKE0W1YD4', 'story', '06FB0TNSRXX5X1PDENVXC4DRJ4', 'Chunk loading/unloading', 'Load chunks around player. Unload distant chunks. Memory management. Performance budget.', 'done', 'high', NULL, NULL, NULL, '2026-02-10 23:22:01', '2026-03-03 16:12:37', NULL, 'aa665fd2df2306c5a5eb653e2c33a81b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWGKQRVQ1YZAQ4GSH0', 'story', '06FB0TNSRY5H0R4JCAJ6MY4X44', 'Gate corridor template', 'Gate infrastructure as social chokepoint. Customs, cargo, coffee vendor, commuter traffic. Observation-rich.', 'done', 'high', NULL, NULL, NULL, '2026-02-10 23:22:44', '2026-03-03 16:12:39', NULL, 'c3462c1664ce3ce4249d02947e958c68', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYC8HYSQPKQTBE8BXM', 'story', '06FB0TNSRY5H0R4JCAJ6MY4X44', 'Functional cluster spatial specs', '15-40 tile connected spaces per social site. Internal sightlines. Public/private zones. 2-3 z-levels per cluster.', 'done', 'high', NULL, NULL, NULL, '2026-02-10 23:22:44', '2026-03-03 16:12:39', NULL, 'd287475f370738fda958334c139c1bce', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZZE9EMMHTQBZZ6BP0', 'story', '06FB0TNSRY5H0R4JCAJ6MY4X44', 'Hand-crafted location authoring', 'Build key locations in Godot tilemap editor. Workplace, bar, smuggling hideout. Embed in procedural district.', 'done', 'high', NULL, NULL, NULL, '2026-02-10 23:22:44', '2026-03-03 16:12:39', NULL, 'e778824b8d3fe44aeb1605acc7a92854', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXSJ0EFJYYMSGH9A6C', 'task', NULL, 'Chunk loading/unloading: streaming around player position', 'Implement chunk streaming: load chunks near the player, unload distant chunks, manage memory budget.
For v0.1 the district is hand-authored and bounded (256x256 visual tiles per D-094 = 8x8 chunks of 32 tiles each). For production v0.3+ the district is procedurally generated and potentially unbounded. This ticket builds the streaming infrastructure that works for both.
Scope:
1. Add a ChunkLoadRadius resource (configurable, default: load chunks within N chunks of player position).
2. Implement a chunk streaming system that runs every N ticks: query player TilePosition -> compute ChunkCoord -> load/unload chunks around that position in WalkabilityMap.
3. For v0.1: pre-load the entire hand-authored district at startup (all chunks fit in memory for a 256x256 map). The streaming system exists but the radius covers the full map. This means no streaming stutter for v0.1 while the architecture is ready for v0.3 borderless generation.
4. Track loaded/unloaded state per chunk (ZLevelLoadState::Loaded / Skeleton / Ungenerated already defined in server/src/simulation/generator.rs — use these).
5. Add a test: spawn player, walk to edge of loaded region, verify adjacent chunks are loaded and distant chunks are unloaded.
Integration points:
- WalkabilityMap: server/src/simulation/movement.rs (ChunkCoord, ChunkData already internal)
- Generator types: server/src/simulation/generator.rs (ZLevelLoadState)
- Player position: TilePosition component, PlayerCharacter marker
Blocked by: #576 (tile-type layer). Coordinate with #577 (tile loader) on startup sequencing.', 'done', 'high', NULL, 'server', NULL, '2026-03-03 16:09:31', '2026-03-03 16:14:10', NULL, '3ca223d741cb4f5fa93626f42bc5f1c3', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRX1NB5ST9THFS8AYBM', 'story', NULL, 'Location YAML tile format and content loader integration', 'Define the tile data format for hand-authored location YAML files and implement the server-side loader that populates WalkabilityMap from ContentStore on production startup.
Two deliverables:
1. TILE FORMAT — Add a tiles section to the Location type in server/src/content/types.rs. Format: array of string rows where each character is a tile code (F=floor, W=wall, V=void, R=restricted). Example: tiles: [''FFFFF'', ''FWWWF'', ''FFFFF'']. Agree the exact format with Araminta before she begins authoring — this is a hard blocker for the visual tile tickets. Document the format in a brief comment block in types.rs.
2. LOADER — Implement load_location_tiles(location: &Location, origin: TilePosition, map: &mut WalkabilityMap) in server/src/content/loader.rs or a new server/src/content/tile_loader.rs. Each row in tiles[] maps to a y-offset from origin; each character maps to an x-offset. W tiles set walkable=false + kind=Wall; F tiles set walkable=true + kind=Floor; V and R tiles set walkable=false with their respective kinds.
3. STARTUP WIRING — Connect ContentStore -> WalkabilityMap in the production startup path (server/src/main.rs). After NPCs and triangles are spawned from ContentStore, call load_location_tiles for each Location that has tile data. The Gauntlet startup path (test_world/) must remain unchanged.
Blocked by: #576 (tile-type layer on WalkabilityMap).
Blocks: visual tile authoring tickets (Araminta cannot author YAML until format is agreed and documented).', 'done', 'high', NULL, 'server', NULL, '2026-03-03 16:09:18', '2026-03-03 16:14:10', NULL, '400ca059546a7be57aa36d8903ccce74', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZD384RGJMR3PKXEM8', 'task', NULL, 'Storyteller activation pass — implement activation_pass() system', 'Implement the full storyteller module activation logic as specified in ticket #162. The contamination layer (#254) is done and gates this system. This ticket implements steps 1-6 of the activation spec.
Implementation steps (from #162 design):
1. GATE CHECK — Read ContaminationActive resource. If false, early-return. Poll on 10-tick cadence — no activation bus needed.
2. PROXIMITY QUERY — Query MovementHistoryBuffer (#571) for Active-tier NPCs within the player''s last ENGAGEMENT_WINDOW_TICKS (3000 ticks = ~5 real minutes at 10 tps) of movement. Co-presence = NPC was within LOS range during any tick in the window.
3. ENGAGEMENT SCORING — For each co-present NPC, read EngagementRecord (#570):
score = min(conversation_count, 5) * 2.0 + observation_time_ticks * 0.005 + monologue_triggers * 8.0
4. UNENTANGLED-NPC ROUTING — If the highest-scoring NPC is from the mundane 50% (D-029), use D-025 reference links to find a connected Active-tier triangle and route activation through that NPC. Fallback: if no reference links exist, use the highest-scoring Active-tier NPC.
5. MODULE SELECTION — Among active TriangleState forks, activate the fork containing the highest-scoring NPC (or routed NPC from step 4). SelectionStrategy: HighestScore (v0.1 default). Tie-breaking: SimRng (seeded, D-010). Edge cases: no engagement scores -> fall back to proximity-only (nearest Active-tier NPC). No active forks -> log warning, hold, retry on next 10-tick check.
6. ACTIVATION EVENT — Emit TriangleActivated { triangle_id } for downstream consumers. This event is defined and emitted here; consumers (tell system, routine scheduler, monologue, FRIEND arc) are wired up in Sprint 24.
Location: add activation_pass() system to server/src/storyteller/mod.rs. Register in StorytellerPlugin.build().
Prerequisites:
- #570 (EngagementRecord component)
- #571 (MovementHistoryBuffer resource)
- #572 (storyteller lifecycle rules — must be specced before implementation)', 'done', 'high', NULL, 'server', NULL, '2026-03-03 16:09:49', '2026-03-03 16:14:10', NULL, 'd9a9e9a4438497fd06bbd8ade90f959d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXY54ZF4YVKWGWNT7M', 'task', NULL, 'EngagementRecord component — track per-NPC engagement metrics for storyteller', 'The storyteller activation system (#162) needs per-NPC engagement data that does not currently exist in the KnowledgeGraph (D-041). Create an EngagementRecord component (or extend KnowledgeGraph) with three fields: cumulative observation_time_ticks, conversation_count, and monologue_trigger_count — all per observed entity. The perception system, dialogue system, and monologue system must write to this record. Sprint 23 prerequisite for #162.', 'done', 'high', NULL, 'server', NULL, '2026-02-28 11:52:26', '2026-03-03 16:32:40', NULL, '3a2e93b5f720aaca1b1557d60972fe3f', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWF5EQD5VANXST3D90', 'task', NULL, 'Chunk data structure: tile-type layer and ChunkStore resource', 'Extend the existing WalkabilityMap (server/src/simulation/movement.rs) to carry tile type data alongside walkability booleans. For v0.1 the map is hand-authored, not procedurally generated, but the data model must be reusable architecture.
Concrete scope:
1. Define a TileKind enum in movement.rs (or a new server/src/simulation/chunk.rs): Floor | Wall | Void | Restricted. Mirrors the existing TileKind in bridge/types.rs (VisibleTile) but lives server-side as the authoritative source.
2. Extend ChunkData (currently a Vec<bool> walkability grid) to store both walkability and tile kind per tile. Use a single Vec<TileCell { walkable: bool, kind: TileKind }> or parallel vecs — whichever is cleaner internally. The WalkabilityMap public API (set_walkable, is_walkable) must remain backward-compatible.
3. Add set_tile_kind(pos, kind) and tile_kind(pos) -> TileKind API to WalkabilityMap.
4. All existing tests must pass. Add new tests for tile kind round-trip.
CHUNK_SIZE = 32 (D-094). WalkabilityMap lives in server/src/simulation/movement.rs. ChunkCoord / ChunkData are private; the public API is the extension surface.', 'done', 'high', NULL, 'server', NULL, '2026-03-03 16:09:10', '2026-03-03 16:32:40', NULL, '614fe3c9b6716d6f1ab0cc06e12aee1e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXNW7RYXVRW68445WM', 'task', NULL, 'MovementHistoryBuffer resource — player path history for storyteller proximity query', 'The storyteller proximity query (#162 step 2) needs the player character''s recent movement path to identify co-present NPCs. Define and implement a MovementHistoryBuffer resource that records the player''s tile positions over the last N ticks (suggested: ENGAGEMENT_WINDOW_TICKS = 3000). The storyteller queries this buffer to find Active-tier NPCs who were within proximity during the window. Sprint 23 prerequisite for #162.', 'done', 'high', NULL, 'server', NULL, '2026-02-28 11:52:30', '2026-03-03 16:32:41', NULL, '5b2356462dc0b85f81865d7aca02c7e1', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRY54Q75JKB9HD4ZXD8', 'task', NULL, 'Storyteller lifecycle rules — multi-activation, cooldown, resolution for v0.1', 'The storyteller activation spec (#162) defines how a triangle activates but not the lifecycle after activation. Specify for v0.1: (1) Can multiple triangles be active simultaneously? (2) What happens when an activated triangle resolves (player completes investigation or consequences fire)? (3) Is there a cooldown between activations? (4) For the 30-minute vertical slice, probably only one activation per session — state this explicitly so the implementer does not over-engineer. Sprint 23 prerequisite for #162.', 'done', 'medium', NULL, 'server', NULL, '2026-02-28 11:52:32', '2026-03-03 16:32:41', NULL, 'ada0390d9d95d1112b4b746973b85748', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRY043B6BCX4X7JHJTG', 'task', NULL, 'Debug console server: command handler and state manipulation API', 'Implement the server-side debug console command handler. Receives DebugCommand variants via IPC (new PlayerAction variant), executes state manipulation, returns DebugResponse via ObserverSnapshot or a new debug channel.
PROTOCOL CHANGES:
Add to PlayerAction enum in server/src/bridge/types.rs:
DebugCommand(DebugCommandKind)
Add DebugCommandKind enum:
AdvanceTicks(u64) -- fast-forward simulation by N ticks
SkipToContamination -- advance to CONTAMINATION_DELAY_TICKS
TeleportToPosition { x: i32, y: i32, z: i32 } -- move player
TeleportToLocation(String) -- move player to named location (e.g. ''the-terminal'')
ForceContaminationActivate -- bypass timer, fire ContaminationActive immediately
ForceTriangleActivation(String) -- activate named triangle by id
InspectNpc(u64) -- dump NPC state: routine, knowledge graph, relationships
ListTriangles -- return all TriangleState entities
ListPopulation -- return all Active-tier NPCs with tier and position
GetContaminationStatus -- return ContaminationActive status and tick
Add DebugResponse to ObserverSnapshot (or a new optional field): debug_response: Option<DebugResponsePayload>
EXECUTION:
Add system handle_debug_commands in server/src/bridge/ or server/src/storyteller/. Only executes if a DebugEnabled resource is set (server accepts a --debug flag at startup, or the client sends a DebugEnable command). In v0.1 this is on by default.
SECURITY MODEL: Debug commands are accepted only when DebugEnabled resource is true. The resource is set at startup and cannot be toggled mid-session via IPC (prevents accidental debug access in production builds — though v0.1 enables it by default).
Integration points:
- bridge/types.rs: PlayerAction, ObserverSnapshot
- storyteller/mod.rs: ContaminationActive, ContaminationEventQueue
- content/template.rs: TriangleState
- simulation/tier.rs: ActiveSim, ActiveTier
- knowledge/: StableId, EntityRegistry', 'done', 'medium', NULL, 'server', NULL, '2026-03-03 16:10:11', '2026-03-03 16:32:42', NULL, '776920f1ffec5ddc1360cd54705a7fc3', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWD4CEN6QV80HRV330', 'bug', NULL, 'Walls at LOS boundary: server — extend visible_tiles with 1-tile wall margin', 'Wall tiles at the edge of the LOS cone are never included in visible_tiles because the server only sends currently visible tiles. The client has no content to render at the boundary, so the fog overlay composites over empty space, making walls look like fog.
FIX:
In the observer snapshot generation (server/src/perception/observer/mod.rs or query.rs), after computing the standard visible_tiles set, add a secondary pass: for each tile on the LOS boundary, check 1 tile beyond in each direction. If that tile is not walkable (wall or void) in WalkabilityMap, include it in visible_tiles with VisibilitySector::Boundary (or a new sector variant).
This gives the client wall tile data at the boundary so fog composites over real content rather than empty space.
SCOPE:
- Only extend the server-side snapshot to include boundary wall tiles
- Use a new VisibilitySector variant if needed (e.g. BoundaryWall), or reuse LosObstructed (#514 added this field)
- Limit to 1-tile margin — do not include walls 2+ tiles beyond LOS
- Wall margin tiles must not be included in the exploration/memory data (they are ''seen now'' not ''explored'')
Integration: client ticket #575b (client half) consumes this data.
Files:
- server/src/perception/observer/mod.rs (snapshot generation)
- server/src/bridge/types.rs (VisibilitySector enum — may need new variant)', 'done', 'medium', NULL, 'server', NULL, '2026-03-03 16:11:11', '2026-03-03 16:32:42', NULL, '991120c3e4fc789d3973179e79e5dc8d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWY3W3W4M3AVJBTN5C', 'task', NULL, 'Debug console client: tilde console UI, settings toggle, command dispatch', 'Implement the in-game debug console. Permanent feature, enabled by default until v1.0, toggleable via Settings menu.
UI:
- Tilde (~) key toggles the console open/closed (KEY_QUOTELEFT in Godot)
- Console is a semi-transparent panel anchored to bottom of screen (~40% screen height)
- Input line at bottom, output log above (last 50 lines, scrollable)
- Console pauses normal keyboard input routing while open (no movement commands leak through)
SETTINGS INTEGRATION:
- Add ''Debug Console'' toggle to client/ui/settings_dialog.gd (existing settings panel)
- Persisted in user://settings.cfg alongside audio volumes
- Default: enabled. Toggle disables the tilde key entirely.
COMMAND DISPATCH:
Parse input line as: <command> [args...]
Commands map to DebugCommandKind variants sent via Protocol.encode_player_input(''debug_command'', {...}):
ticks <n> -> AdvanceTicks(n)
contaminate -> SkipToContamination
tp <x> <y> [z] -> TeleportToPosition
tp <location_name> -> TeleportToLocation
activate -> ForceContaminationActivate
triangle <id> -> ForceTriangleActivation
npc <entity_id> -> InspectNpc
triangles -> ListTriangles
pop -> ListPopulation
status -> GetContaminationStatus
help -> print command list locally (no server round-trip)
RESPONSE DISPLAY:
Read debug_response from ObserverSnapshot (new field added by server ticket #580). Format and append to output log. JSON-format complex payloads (NPC state, triangle list).
PROTOCOL:
Extend protocol.gd encode_player_input to handle ''debug_command'' action_name -> serialize DebugCommandKind as action_data. Read debug_response from snapshot decode.
Files:
- New: client/ui/debug_console.gd + debug_console.tscn
- Modify: client/ui/settings_dialog.gd (add toggle)
- Modify: client/scripts/protocol/protocol.gd (encode debug_command, decode debug_response)
- Modify: client/scripts/main.gd (wire tilde key, instantiate console)
Blocked by: #580 (server debug command handler — need the protocol contract before client can dispatch)', 'done', 'medium', NULL, 'client', NULL, '2026-03-03 16:10:27', '2026-03-04 19:33:39', NULL, '6b69fc5b4b6ee5efd7fd284ee457518d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZXAG8CQ4B547Z4DFW', 'bug', NULL, 'Walls at LOS boundary: client — render margin tiles correctly through fog', 'Client half of the LOS boundary wall fix. Consumes the server-side boundary wall tiles added by #584.
FIX:
In client/scripts/rendering/tile_renderer.gd, the update_tiles() method processes VisibleTile entries from the snapshot. Currently all visible tiles are rendered to the TileMapLayer with the same logic regardless of VisibilitySector.
Add handling for the new BoundaryWall sector (or equivalent) from #584:
- BoundaryWall tiles are rendered to the tile map (so fog has content to composite over)
- BoundaryWall tiles do NOT update the exploration/memory state (they are not ''explored'')
- BoundaryWall tiles use the same tile rendering as normal wall tiles visually
This ensures the fog shader composites over actual wall content at the LOS boundary rather than over empty viewport.
Files:
- client/scripts/rendering/tile_renderer.gd (update_tiles method)
- client/scripts/autoloads/fog_state.gd (ensure boundary tiles don''t affect fog exploration data)
Blocked by: #584 (server must send boundary wall tiles before client can render them)', 'done', 'medium', NULL, 'client', NULL, '2026-03-03 16:11:20', '2026-03-04 19:33:40', NULL, '11c57b5f43708eadc3a0afd002f76919', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZYNV1QA4GBXPNVTP8', 'task', NULL, 'Author tile maps: The Last Shift, maintenance corridors, and gate corridor', 'Translate three Sprint 12 floor plans into tile coordinate data for the Sova Transit District. One YAML file per location.
SOURCE MATERIAL:
- #312: Spatial layout: Bar (The Last Shift) — 28x22 visual tiles + 6m east extension
- #313: Spatial layout: Smuggling spaces and transition corridors — maintenance corridors, restricted storage, dead-drop locations
- D-093: Gate Cluster spec — 40x32 visual tiles, 7 zones:
aperture chamber 8x4 (restricted)
freight staging 24x8 (private)
passenger arrival 12x8 (semi-public)
freight customs 20x10 (semi-private, 3-5 lanes at 2vt/lane)
pedestrian customs 12x10 (semi-public, 3 lanes at 1vt)
gate concourse 40x8 (public)
observation gallery z=2 32x10 (Commission-only)
- D-093 corridor widths: maintenance 2vt, internal 3-4vt, secondary public 4vt, transition 6vt, gate concourse 8vt
TILE FORMAT:
Same format as #582 (The Terminal). Wait for #577 format confirmation before authoring.
LOCATIONS TO AUTHOR:
1. the-last-shift.yaml — bar layout: long bar counter, corner booth, scattered tables, news ticker, back room (alley exit). Zone palette: #6b4018 / fog #200c04
2. maintenance-corridors.yaml — ring operation spaces: restricted storage + maintenance hatch + transition corridor (40m between hub and bar district). Zone palette: #4e5054 / fog #101214
3. New file: locations/gate-corridor.yaml — gate cluster zones at z=0 (all zones except observation gallery) and z=2 (gallery). Cross-z rail = transparent low wall.
Update district.yaml to add ''gate-corridor'' to the locations list.
Blocked by: #577 (tile format), parallel with #582 (can author in parallel once format is agreed)', 'done', 'high', NULL, 'visual', NULL, '2026-03-03 16:10:57', '2026-03-04 22:24:25', NULL, '1554abf93c6309b9be7448abd570f7d9', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXTYQNRYGW685W2S1W', 'task', NULL, 'Author tile map: The Terminal (logistics hub)', 'Translate the Sprint 12 floor plan (#311) for The Terminal (Sova Logistics Hub) into tile coordinate data in content/campaigns/main/systems/krenn/stations/sova/districts/transit/locations/the-terminal.yaml.
Source material:
- #311: Spatial layout: Logistics Hub (The Terminal) — tile-level floor plan with sightline analysis
- D-093: Sova Transit District — Terminal dimensions: 44x28 visual tiles
- Zones: scanner bays, main corridor (chokepoint), manifest processing, break room, supervisor office (window), restricted storage (smuggling ops)
- Access tiers: public (main corridor), credentialed (work floor), restricted (storage)
- Zone palette surface hex: #7a8490 / fog tint: #0d1520
TILE FORMAT:
Use the format defined by server ticket #577 (Location YAML tile format). Wait for Dudley to confirm the format before authoring. The format will be: tiles[] array of string rows, one character per tile (F=floor, W=wall, V=void, R=restricted). Origin tile (0,0) = top-left corner.
OUTPUT:
Complete the stub at content/campaigns/main/systems/krenn/stations/sova/districts/transit/locations/the-terminal.yaml with:
- canonical_id, display_name, description (already partially present)
- tile_bounds: { x_min, y_min, x_max, y_max, z }
- tiles: [...] (the 44x28 grid)
- sightlines: { open: false, notes: ''supervisor office window faces main corridor'' }
Blocked by: #577 (tile format agreed and documented)', 'done', 'high', NULL, 'visual', NULL, '2026-03-03 16:10:41', '2026-03-04 22:24:25', NULL, '9d343376324e3c00006cc1376f7dde83', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRW9XW5QGV16JQV0WG4', 'bug', NULL, 'Dialogue speaker colors are position-based instead of speaker-bound', 'All speakers on the left side share the same color and all speakers on the right side share the same color. Colors should be bound to speaker identity, so each speaker has a consistent unique color regardless of their position in the conversation.', 'done', 'medium', NULL, 'client', NULL, '2026-03-03 07:50:12', '2026-03-04 22:26:14', NULL, 'd129755f11ca55915511425c0eedf45e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRY5JFPDFPMMWP74RFC', 'bug', NULL, 'Pre-existing test failures in test_entity_renderer (test_rendering.gd)', 'Multiple entity renderer tests fail on clean HEAD (main@5aea6e2). Affected tests: test_entity_renderer_positions_centered, test_entity_renderer_player_color_differs_from_npc, test_entity_renderer_player_uses_d033_color, test_entity_renderer_npc_uses_unknown_teal, test_entity_renderer_object_uses_grey, test_entity_renderer_facing_indicator_rotation_accuracy, test_regression_345_entity_position_set_from_entity_id_entity. Likely entity_renderer.gd changed without updating tests.', 'done', 'low', NULL, 'client', NULL, '2026-03-03 07:50:15', '2026-03-04 22:26:17', NULL, '059f18fe08df1c00cccf0b27c62a1c3d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRW9YPHF7SH0FNMAY6W', 'bug', NULL, 'Walls at LOS boundary absorbed into fog instead of rendering as content', 'Wall tiles at the edge of the LOS cone blend into the fog overlay instead of rendering as distinct wall content. The server only sends currently visible tiles — walls just outside the cone boundary are never included in visible_tiles, so the client has no content to render there. The fog composites over empty space, making walls look like part of the fog.
Bug report: gauntlet-t1093-2026-03-03_14-30-38
Tick: 1093 | Position: (26.5, 20.5) | Facing: North
Tester notes: the walls themselves need to be sent as visible tiles by the server. It looks weird to have them be part of the fog
Context: The client-side tile accumulation fix (removing TileMapLayer.clear per frame) helps for previously-seen walls, but walls that were NEVER in the LOS cone still have no tile data. The server may need to include a margin of wall tiles beyond the LOS boundary so the fog has content to composite over.
Potential approaches:
- Server: include wall tiles 1-2 tiles beyond the LOS cone in visible_tiles
- Server: send a separate nearby_walls array for tiles adjacent to visible positions
- Client: infer wall positions from the gap between visible floor tiles and the explored boundary', 'done', 'medium', NULL, 'server,client', NULL, '2026-03-03 15:33:22', '2026-03-04 22:32:11', NULL, '04fb5709a9822b53bacc4f9ae8566bb1', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZMGXXPB99F23GMH68', 'epic', '06FB0TNSRXXEKGTNRJ7Z6FTDRM', 'Client-Server Integration', 'Connect Rust simulation to Godot client via subprocess/IPC. End-to-end tick loop, input processing, state synchronization.', 'done', 'critical', NULL, NULL, 'D-020', '2026-02-10 23:15:13', '2026-03-04 22:32:11', NULL, '2932557d2c2258a429081c635614cd8e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXCH1ZHWN1V0NVNVT4', 'epic', NULL, 'QA Strategy & Test Infrastructure', 'QA and test infrastructure from the Test Architecture Workshop (2026-02-17). 59 items across Sprint 8 P0/P1, Sprint 9, and backlog. See docs/workshops/test-architecture/workshop-outcomes.md for full spec.', 'done', 'critical', NULL, 'ci', NULL, '2026-02-17 12:58:13', '2026-03-04 22:32:11', NULL, 'ab68a37f61c3c9420b63f6adbd4c3199', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZYTPVEY4DHRKQEKWC', 'epic', NULL, 'v0.1 Content Scoping Workshop Outputs', 'Parent epic for all v0.1 Content Scoping Workshop tickets. Covers: wiki content fixes, style guides/specs, content directory/schema, design specs, workshop-derived tickets, and lead decision tickets. Supersedes original Wiki Review Workshop scope.', 'done', 'high', NULL, 'copy', NULL, '2026-02-12 10:12:50', '2026-03-04 22:32:11', NULL, 'f9e4cade0998bad9ce28460fb947a208', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZV4SN872P892BAZZ4', 'story', NULL, 'Opening monologue — content: already complete (smuggler #299, detective #300)', NULL, 'done', 'high', NULL, 'copy', 'D-039', '2026-03-04 22:32:41', '2026-03-04 22:33:57', NULL, 'c77a3358cf295468c437575171989e01', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYJ028EY4FMDGGR21G', 'task', '06FB0TNSRXS59MQNDC5M7TRVWC', 'Tile data model design — produce D-record for extensible tile properties', NULL, 'done', 'medium', NULL, 'server', NULL, '2026-03-04 22:32:35', '2026-03-05 10:05:55', NULL, '21e264e99de32b502301dc687d2a998b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYWQ7ZKBKR22YQFDTG', 'story', NULL, 'Triangle activation consumer — server: emit behavioral tell escalation on TriangleActivated', NULL, 'done', 'high', NULL, 'server', 'D-024', '2026-03-04 22:32:20', '2026-03-05 10:05:55', NULL, '78d5c4115d55a4693e65403fb3da4685', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRW2R6PFEP9D1Q7HF7M', 'story', NULL, 'v0.1 playthrough proof: full session from main menu through storyteller activation', NULL, 'done', 'critical', NULL, 'server', NULL, '2026-03-04 22:32:33', '2026-03-05 10:05:55', NULL, '7bab9319f8a0735a58a186b0c68d741d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZQ3YJ58RXS1405FGW', 'story', NULL, 'Opening monologue trigger — server: emit opening monologue lines at session start based on archetype', NULL, 'done', 'high', NULL, 'server', 'D-039', '2026-03-04 22:32:39', '2026-03-05 10:05:55', NULL, '8fea21422611e622e2461c32539b7ad8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWWJ39V89N7Y4MQZG4', 'story', NULL, 'News ticker — server: load ticker YAML, emit current headline in snapshot', NULL, 'done', 'medium', NULL, 'server', 'D-039', '2026-03-04 22:32:26', '2026-03-05 10:05:55', NULL, '95b00b4130fc61469d87e5fdf348e77f', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZ1X6QXM7NKFBCCYHC', 'story', NULL, 'Character archetype selection — server: accept archetype in StartSession, spawn correct PC', NULL, 'done', 'high', NULL, 'server', 'D-027', '2026-03-04 22:32:15', '2026-03-05 10:05:55', NULL, 'e75ba63c3637ca381dbc8386e800edf9', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRW5977W5FEAHDV3YSR', 'story', NULL, 'Triangle activation — copy: author 3–5 proximity monologue lines for TriangleActivated (per archetype)', NULL, 'done', 'high', NULL, 'copy', 'D-039', '2026-03-04 22:34:02', '2026-03-05 10:06:02', NULL, 'a3de56a0dea19c0473538000e9c1e4b7', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXG5NRK4E89YX1FEZR', 'epic', NULL, 'Insert document reader — onboarding dossiers', 'Player-facing document reader for the insert UI. First use case: onboarding dossiers on first load — Commission assignment brief (detective) and network warning note (smuggler). Diegetic documents that set up asymmetric information. Three stories: (1) client document reader UI rendering markdown, (2) server+client document data model in YAML, (3) authored content for both archetypes. Documents are persistent/re-readable from insert, markdown format, variable fields injected at runtime. See Q-055 for design details. Refs: D-065, D-090, D-091, D-034, D-036, D-087, D-012.
[CANCELLED 2026-03-05] Superseded by Where''s the Fun? Workshop decisions. Onboarding dossiers for detective/smuggler archetypes are defunct — the game is pivoting to a generator-first life sim with tycoon bookmark. Insert system survives but will be re-scoped for v0.2.', 'cancelled', 'high', NULL, NULL, 'D-065,Q-055', '2026-03-05 10:24:14', '2026-03-05 10:24:14', NULL, '1f35ef548e10f7e9ccad2b148d3278ea', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXVAP1SXZRT96KQCNM', 'story', '06FB0TNSRXG5NRK4E89YX1FEZR', 'Document reader UI component — markdown rendering in insert panel', 'Build an insert UI panel that renders markdown documents. Requirements: (1) Parse markdown subset — headers, bold, italic, horizontal rules, field labels. (2) Full-screen overlay mode for first-load display, dismissed by keypress. (3) Persistent access from insert — player can re-open and re-read documents at any time. (4) Support variable field injection (case ref, date, PC name) via template markers in the markdown source. (5) Scrollable for longer documents. Design should feel like a diegetic data-pad display, not a browser. Refs: D-065 insert UI.
[CANCELLED 2026-03-05] Superseded by Where''s the Fun? Workshop decisions. Onboarding dossiers for detective/smuggler archetypes are defunct — the game is pivoting to a generator-first life sim with tycoon bookmark. Insert system survives but will be re-scoped for v0.2.', 'cancelled', 'high', NULL, 'client', 'D-065,Q-055', '2026-03-05 10:24:20', '2026-03-05 10:24:20', NULL, '107cdc6585e97b6985679a5c61bb3d42', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYVP3KWNSVD0SY44R4', 'story', '06FB0TNSRXG5NRK4E89YX1FEZR', 'Document data model — YAML content, server loading, wire format', 'Design and implement the document data model. Requirements: (1) YAML content files for documents with markdown body, metadata (id, title, archetype, trigger condition, variable field definitions). (2) Server loads document YAML at startup via ContentStore. (3) Documents emitted in ObserverSnapshot — initially the onboarding document for the player''s archetype. (4) Client receives, stores, and surfaces documents to the insert reader UI. (5) Trigger system: onboarding docs fire on first snapshot (tick 0 or session start). Future documents may trigger on events/locations. Wire format: document id + markdown body + injected variables in ObserverSnapshot.
[CANCELLED 2026-03-05] Superseded by Where''s the Fun? Workshop decisions. Onboarding dossiers for detective/smuggler archetypes are defunct — the game is pivoting to a generator-first life sim with tycoon bookmark. Insert system survives but will be re-scoped for v0.2.', 'cancelled', 'high', NULL, 'server', 'Q-055', '2026-03-05 10:24:26', '2026-03-05 10:24:26', NULL, 'd7cd89e876d4347733b702e85bed4ed6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZFM3XYYGEAJC12VJR', 'story', '06FB0TNSRXG5NRK4E89YX1FEZR', 'Onboarding dossier content — detective brief and smuggler warning', 'Author the two onboarding documents per the design consensus:
DETECTIVE DOSSIER (150–200 words): Commission Field Operations assignment brief. Institutional voice — labelled fields, case ref, formal passive. Establishes: cargo irregularities at Sova Transit, standard observation authority, reporting to office not person. Omits: suspect names, ring existence, threat assessment. Format: header block (case ref, date, classification), Background, Scope, Objective, Reporting sections. The Commission is not worried — that''s the tell.
SMUGGLER WARNING (60–80 words): Anonymous dead drop from network coordinator. Terse operational voice, no headers, unsigned or coded sign-off. Establishes: Commission investigator incoming, maintain normal schedule, stay quiet. Omits: detective identity, intel source, reassurance. The brevity IS the voice.
BOTH OMIT all triangle NPC names (Kael, Sera, Torek). Triangle is discovered through observation.
Voice refs: D-090 registers. Detective dossier = Commission voice (not PC). Smuggler note = network contact voice (not PC). Contrast with PC monologue is intentional. See existing monologue pools for PC voice reference. Refs: D-034, D-036, D-087, D-091.
[CANCELLED 2026-03-05] Superseded by Where''s the Fun? Workshop decisions. Onboarding dossiers for detective/smuggler archetypes are defunct — the game is pivoting to a generator-first life sim with tycoon bookmark. Insert system survives but will be re-scoped for v0.2.', 'cancelled', 'high', NULL, 'copy', 'D-090,D-091,D-087', '2026-03-05 10:24:32', '2026-03-05 10:24:32', NULL, '2948b68bc5c43043ee4214152a4b3bfb', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXTV61ZRF9RZ6V1NXR', 'story', NULL, 'Design discussion: insert data feeds — documenting feature systems and plumbing patterns', 'Broad design discussion to map out ALL the data feed systems the insert will eventually surface, identify common patterns, and design foundational plumbing that serves them all. This is bigger than the onboarding dossier — it is the insert as an information platform.
Participants: Tyre (architecture), Dudley (server), Stig (UI), Gestalt (systems design), Miri (worldbuilding — what feeds exist in-setting), Ozzie (player experience — what feels good to check)
The insert (D-065) is the player''s primary information device. It needs to surface a wide variety of data feeds that exist naturally in the setting. Map these out FIRST, then identify patterns, then design plumbing.
## Known and candidate feed types
**Documents/dossiers** — static or semi-static authored content. Assignment briefs, personal notes, found documents, intercepted communications. Markdown body, variable injection, triggered delivery.
**Messaging/comms** — NPC-to-player and overheard messages. Meridian messages, dead drops, intercepted transmissions. Threaded? Timed delivery? Read/unread state?
**News/ticker feeds** — station news, sector bulletins, propaganda. Already have TickerPool (#591) for the bar — how does this generalize to the insert? Weather/atmospheric feeds for planets (adds environmental flavor).
**Time systems** — local station time, central Settled Reach time, planetary day-night cycles (varying rotation periods). How does the insert display multiple time references? Which are always visible vs on-demand?
**Maps** — district layouts, marked locations, fog-of-war on map data. Progressive reveal as player explores.
**Logs/journal** — auto-generated from observations? Player-annotated? Investigation notes the detective accumulates vs cargo manifests the smuggler tracks?
**Faction/relationship status** — trust levels, known affiliations, reputation. How much is visible to the player vs hidden (D-012 information boundary)?
**Inventory/cargo** — physical items, data chips, contraband manifests.
## Questions to resolve
1. What feed types exist in the Settled Reach setting? (Miri: what does a station worker/visitor actually have on their insert?)
2. Which feeds are universal vs archetype-specific? (Detective gets case files; smuggler gets network dead drops. Both get news and time.)
3. What are the common patterns across feeds? (Delivery trigger, persistence, read state, update frequency, rendering format)
4. Server-side: single generic "insert_feeds" collection in ObserverSnapshot vs typed fields? Delta updates vs full state?
5. Client-side: plugin/renderer architecture — how does the insert UI dispatch feed types to renderers without hardcoding each one?
6. Persistence: session-persistent vs save-persistent vs ephemeral. Which feeds accumulate, which rotate, which expire?
7. Information boundary (D-012): feeds are perception-filtered. The insert only shows what the PC has access to. How does this interact with the observer snapshot?
8. Priority/notification: which feeds notify the player (new message!), which are passive (time ticks), which are urgent (warning)?
## Output
D-record (or major amendment to D-065) documenting:
- Catalog of insert feed types with properties
- Common envelope/interface pattern
- Server→client data flow architecture
- Rendering dispatch pattern
- Persistence and lifecycle model
This is a design discussion, not implementation. Produce decisions and patterns that #600 and future insert work implement against.
## Refs
D-065 (insert UI), D-012 (information boundary), D-010 (determinism), Q-055', 'backlog', 'high', NULL, NULL, 'D-065,Q-055', '2026-03-05 10:28:30', '2026-03-05 10:28:30', NULL, 'd83e0c35be5d50b8c328d618a9ad8218', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRX7MSDDBT31NPQKEG8', 'story', NULL, 'Character archetype selection — client: character select screen before session start', NULL, 'done', 'high', NULL, 'client', 'D-027', '2026-03-04 22:32:17', '2026-03-05 15:44:42', NULL, '46623c40fb0026d6245f991b1e83a301', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXWQMB5F3ZGZ4CVXT8', 'story', NULL, 'Triangle activation consumer — client: react to triangle_crisis_events in snapshot (monologue chime + urgent overlay)', NULL, 'done', 'high', NULL, 'client', 'D-039', '2026-03-04 22:32:23', '2026-03-05 15:44:43', NULL, '93a0c8154a42c19dd41ae68947c06191', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYCNC9V8TFQS5QRH50', 'story', NULL, 'News ticker — client: scrolling ticker HUD element in The Last Shift zone', NULL, 'done', 'medium', NULL, 'client', 'D-039', '2026-03-04 22:32:30', '2026-03-05 15:44:43', NULL, 'b771693cca39a15b9893f42ab1a4bfce', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRW7FZPYXXCGWBBRBEC', 'initiative', NULL, 'v0.2 Life-Sim Pivot', 'Generator-first life sim pivot following the Where''s the Fun? workshop (2026-03-05). 24 decisions locked: all NPCs generated, tycoon bookmark, culture-implicit-in-location, broad life verbs, dual-scale consequence model. Detective/smuggler framing retired. Critical path: Sprint 25 generator spike. See docs/workshops/wheres-the-fun/workshop-outcomes.md.', 'backlog', 'critical', NULL, NULL, NULL, '2026-03-05 23:11:31', '2026-03-05 23:11:31', NULL, '6ae9684e83cb0d7c9bef4ba7630906bb', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWJ60W2RFN2FQ2P3EW', 'epic', '06FB0TNSRW7FZPYXXCGWBBRBEC', 'Generator Proof-of-Life', 'Sprint 25 spike: generator must produce usable output before anything else matters. Prove auto-generated locations at scale with legible characters. Template assembly (not procedural geography) for v0.2. See workshop decisions #1, #7, #22, #23. Estimated 7 sprints to proof-of-life playtest (generated location + legible characters + tycoon bookmark from creation to Day 3) — Tyre estimate.', 'backlog', 'critical', NULL, NULL, NULL, '2026-03-05 23:11:39', '2026-03-05 23:11:39', NULL, '089cfcdce855c352a6ef419dc946b533', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXD5W32VCV5YZY3NX0', 'epic', '06FB0TNSRW7FZPYXXCGWBBRBEC', 'Tycoon Bookmark', 'Tycoon is the v0.2 bookmark: small business owner, existing in the Krenn System. Zero investigation content. Tycoon = aspiration not starting state. Workshop decisions #4, #19, #20. Broad economic verb vocabulary serving all careers. Auto-generated apartment reflecting wealth + cultural context. CK3-style bookmark selection flow.', 'backlog', 'high', NULL, NULL, NULL, '2026-03-05 23:11:42', '2026-03-05 23:11:42', NULL, '1ad72ace1dbfcaddf4135c036a128c06', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXE0VWDHVQTXPVPEW0', 'epic', '06FB0TNSRW7FZPYXXCGWBBRBEC', 'Character Creation', 'CK3-style character creation: skills + bookmark selection. Full cosmetic customisation (hair, clothing, colours — workshop decision #11). Culture implicit in starting location (decision #16). No family/culture/religion sliders in creation — those are deferred. Decision #2.', 'backlog', 'high', NULL, NULL, NULL, '2026-03-05 23:11:48', '2026-03-05 23:11:48', NULL, '6dbb86e2884a782e77a5e470ba965b97', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZG75QH8MZ8HKNAB48', 'epic', '06FB0TNSRW7FZPYXXCGWBBRBEC', 'NPC Legibility', 'The proof-of-life gate: generated NPCs must have sufficient personality surface area for emotional attachment. Without legible NPCs nothing else works — all 9 workshop agents converged on this. Decisions #7, #8, #17. Traits + observable behaviour as personality surface. Relationships via Sims interaction + Rimworld adversity. AI-assisted content templating via culture vectors.', 'cancelled', 'critical', NULL, NULL, NULL, '2026-03-05 23:11:52', '2026-03-05 23:11:52', NULL, '796183c18ee4cd9c816764fc8beb50eb', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZ65H50N3XJKJ3F5XM', 'epic', '06FB0TNSRW7FZPYXXCGWBBRBEC', 'World Feel', 'The world is quietly responsive, not indifferent. Gradient of caring by social proximity — world doesn''t care globally but notices locally. Dual-scale consequence model: Rimworld sharp events (crises, reversals) + DF slow accumulation (reputation, debt, relationship erosion). Setting delivery via both visual and insert layers in parallel. Workshop decisions #10, #12, #13, #21.', 'backlog', 'high', NULL, NULL, NULL, '2026-03-05 23:11:59', '2026-03-05 23:11:59', NULL, '4526ff77b16815b4fd9802b4710490ed', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWNGMQS91H3NY20FSC', 'story', '06FB0TNSRXD5W32VCV5YZY3NX0', 'Tycoon small business owner starting state', 'Define the tycoon Day 1 state: small business owner in the Krenn System, NOT a wealthy tycoon. Tycoon = aspiration, not starting state (decision #20). Starting assets, relationships, knowledge, economic position. Auto-generated apartment reflects this. Feeds into character creation and generator.', 'backlog', 'high', NULL, 'planning', NULL, '2026-03-05 23:12:34', '2026-03-05 23:12:34', NULL, '6ef6b0f49b2f2ddb15113539faa03562', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZJTD6C04JX4MZJHKC', 'story', '06FB0TNSRXD5W32VCV5YZY3NX0', 'Broad economic verb vocabulary design', 'Design the tycoon-era verb map. Decision #19: broad life-verb vocabulary, not tycoon-specific. Verbs serve all potential careers. Economic verbs as a superset. Works: hire, negotiate, inspect, invest, source, sell, manage. Social verbs remain. No investigation verbs in v0.2.', 'backlog', 'high', NULL, 'planning', NULL, '2026-03-05 23:12:41', '2026-03-05 23:12:41', NULL, '7dfefdd16ee86fd16d5c374150a8edcb', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXMPF6176KB9CSBH2C', 'story', '06FB0TNSRXD5W32VCV5YZY3NX0', 'Auto-generated apartment', 'SPLIT — see #681 (server: apartment generator logic) and #682 (client: visual rendering + first-moment sequence). Original: Player starting apartment is auto-generated to reflect economic position and cultural context. Decision D-136 (First Settled Reach moment: apartment + insert activation). Decision D-126 (Groundhog Day alarm clock homage).', 'backlog', 'high', NULL, 'server', NULL, '2026-03-05 23:12:44', '2026-03-05 23:12:44', NULL, '6b017ad33eea819c50cee234b8592c6f', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZHHSBPQS9VB8S7D5M', 'story', '06FB0TNSRXE0VWDHVQTXPVPEW0', 'Full character customisation', 'Cosmetic customisation: hair, clothing, colours. Decision #11. Separate from skills/bookmark — aesthetic only. Question open: do creation choices trace into the generated apartment? (Q-WTF-040).', 'backlog', 'high', NULL, 'client', NULL, '2026-03-05 23:12:52', '2026-03-05 23:12:52', NULL, '85045975921a90442ae1a925c0f5b46b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZVMP60W0HNHQQWHEW', 'story', '06FB0TNSRZG75QH8MZ8HKNAB48', 'NPC personality surface area', 'Define and implement the observable personality surface for generated NPCs: traits + visible behaviour patterns. Decision #17. Must be sufficient for emotional attachment without authored backstory. This is the proof-of-life test question: can the player read NPC relationships from generator output? Personality surface = primary gate for the generator spike.', 'cancelled', 'critical', NULL, 'server', NULL, '2026-03-05 23:13:06', '2026-03-05 23:13:06', NULL, '66f616ac54ed1bc9a8736d94cafaaefa', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZBZHXAK3DYTK1C19R', 'story', '06FB0TNSRZG75QH8MZ8HKNAB48', 'Relationship formation system', 'NPC-to-NPC and NPC-to-player relationships form through: Sims-style direct interaction + Rimworld-style shared adversity. Decision #17. Relationships must be readable from the outside. Codify for systems. Decision #18: prove relationships readable before layering narrative depth.', 'backlog', 'high', NULL, 'server', NULL, '2026-03-05 23:13:08', '2026-03-05 23:13:08', NULL, '375b040b8666eabc88766f363a49b938', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRY8VTPE5KJNG2287B8', 'story', '06FB0TNSRZG75QH8MZ8HKNAB48', 'AI content templating pipeline', 'Generative AI pipeline for NPC content templating. Decision #8. Culture vectors (from Krenn culture profile) as prompt constraints. Tone, accents, speech patterns culturally driven — job modifies, culture primary (decision #6). Question open: Claude API, local ollama, or manual for v0.2? (Q-WTF-033). Decision #9: in-game ollama deferred but door open.\n\n[Superseded by #638 LLM Voice Pipeline epic — workshop decisions resolved Q-WTF-033 in favour of local llama-cpp-rs. 2026-03-07]', 'cancelled', 'high', NULL, NULL, NULL, '2026-03-05 23:13:16', '2026-03-05 23:13:16', NULL, '67ea223d333aa117705924569a36faf3', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXK4NDX3Y242QK7BC8', 'story', '06FB0TNSRZ65H50N3XJKJ3F5XM', 'Quietly responsive world', 'The world notices locally but doesn''t care globally. Gradient of caring based on social proximity. Decision #10. Primary social contacts develop responsiveness over time. Indifferent world is wrong — quietly responsive world is right. Implementation: proximity-weighted NPC attention and reaction systems.', 'backlog', 'high', NULL, 'server', NULL, '2026-03-05 23:13:22', '2026-03-05 23:13:22', NULL, 'f83b8e8ed2bf683e45ef1d059feeaba1', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZ3B32PHGY8FSKHGYW', 'story', '06FB0TNSRZ65H50N3XJKJ3F5XM', 'Dual-scale consequence model', 'Two consequence scales operating simultaneously. Decision #21. Rimworld-style: sharp events, crises, dramatic reversals (fast). DF-style: slow accumulation — reputation erosion, debt, relationship drift (slow). Both at different scales, not either/or. Failure mode is clear (decision #21 resolves risk register item).', 'backlog', 'high', NULL, 'server', NULL, '2026-03-05 23:13:25', '2026-03-05 23:13:25', NULL, '48f9ccadb7dea9672fb6492a55502e3f', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWFVC195S2PXX243G4', 'story', '06FB0TNSRZ65H50N3XJKJ3F5XM', 'Setting delivery — both layers', 'Setting delivered via two parallel channels: visual (what you see in the world) + insert (diegetic information device). Decision #12. These are not sequential — both activate simultaneously. Decision #13: First Settled Reach moment = apartment + insert activation. The insert is the primary diegetic information tool. Open question: what should the player feel looking at the span gate from their apartment? (Q-WTF-041).', 'backlog', 'high', NULL, 'planning', NULL, '2026-03-05 23:13:37', '2026-03-05 23:13:37', NULL, '5ea4b9b7829600d52b44a0ed897fa95c', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXS59MQNDC5M7TRVWC', 'epic', NULL, 'Design extensible tile data model to replace single-char encoding', 'The current tile format uses single characters (F/W/V/R) in string arrays. This cannot represent tile-level properties like: door access lists, container contents, damage/hit points, visual properties (paint, material, wear), furniture/interactables, terrain variants, sound properties, trigger zones. This ticket is a DESIGN task. Deliverables: 1) Survey what tile-level data the game needs across all systems. 2) Design an extensible tile data model (consider tile palette/registry, per-tile property bags, layer systems, ECS-style tile components). 3) Write a D-record with the chosen design. 4) Estimate migration effort for existing location YAMLs. The design must handle authoring (human-writable YAML), loading (server parser), runtime (ECS queries), and future procedural generation.', 'done', 'medium', NULL, 'server', NULL, '2026-03-04 21:29:27', '2026-03-06 18:48:31', NULL, '66cb699ef881d49b94936feb553ddccf', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWPFGG0WE5HNJNAWMR', 'story', '06FB0TNSRWJ60W2RFN2FQ2P3EW', 'NPC generation pipeline', 'Wire template generator output into NPC entity creation. Input: DistrictSkeleton with NpcBlueprints from #612. Output: spawned ECS entities using the existing npc/generate.rs pipeline, extended to accept cultural markers from the blueprint.
For the sprint proof this does NOT spawn into a running bevy world — it extends the generator-spike binary to also print per-NPC detail lines. The ECS wiring is the stretch goal; the mandatory deliverable is the text output proving NPC generation from blueprints works.
The pipeline: NpcBlueprint.to_role_definition() feeds the existing 10-axis generator. Cultural markers from the blueprint bias trait selection (a rural Krenn NPC should not generate cosmopolitan traits). Relationship pre-seeds from the blueprint are injected into the relationship component.
Sprint proof output per NPC (printed to stdout for both the rural and industrial runs):
- Name, age, role/occupation
- 2-4 personality traits (from trait enum, human-readable)
- 1-2 observable behaviors (what a bystander notices)
- Cultural speech markers (register, filler words)
- Relationships: "[name] knows [other_name] as [relationship_type] ([valence])"
D-129 gate: can you READ the relationships from the text dump? Not infer — read. The pass condition is a human scanning the output and saying "yes, I can see who knows whom and how they feel about it."
Two-zone comparison test (extends #612 proof):
- Rural NPCs should skew toward agricultural/trade roles, lower density, tighter social networks (more relationships per capita)
- Industrial NPCs should skew toward logistics/manufacturing roles, higher density, looser networks
- Both sets should share Krenn cultural markers (naming, speech patterns)
Acceptance criteria:
- [ ] generator-spike binary prints per-NPC detail for both --zone rural and --zone industrial
- [ ] NPC names follow Krenn phoneme/naming conventions from culture profile
- [ ] Trait sets differ between NPCs (not all NPCs get the same 3 traits)
- [ ] Relationships are explicit in output: "A knows B as colleague (positive)"
- [ ] Rural and industrial NPC populations differ in role distribution and social network density
- [ ] Cultural markers (speech register, filler words) appear in output and match Krenn profile
- [ ] No per-NPC authoring — every NPC is fully derived from zone_type + culture + seed', 'cancelled', 'critical', NULL, 'server', NULL, '2026-03-05 23:12:25', '2026-03-06 19:41:05', NULL, '034f1e5e3d9b42bc9c5283fb9ecfca83', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZX51B0N1WX54KMX50', 'story', '06FB0TNSRWJ60W2RFN2FQ2P3EW', 'NpcBlueprint struct design', 'Define ZoneSpec, CultureProfile, and NpcBlueprint as Rust structs with serde_yaml deserialization. Write example YAML showing the expected format. This IS the schema contract — copy team fills YAML to match these structs. Use a spike-specific SpikeOutput struct, NOT DistrictSkeleton. Phase 0 deliverable: structs + example YAML shared with copy team.', 'done', 'critical', NULL, 'server', NULL, '2026-03-05 23:12:17', '2026-03-07 07:48:00', NULL, '1c2bd9adc32f9a44dd01561f86a186ac', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZB31VT1WJ8WKKCQ2C', 'story', '06FB0TNSRWJ60W2RFN2FQ2P3EW', 'Template assembly generator', 'Generator binary: takes --zone <type> --seed <n>, loads zone spec + culture YAML from disk, produces NPCs via NpcBlueprint, prints text output. Must work with hardcoded test data before real YAML arrives (Phase 1). Sprint proof: run twice (rural + industrial), read side by side, can you tell which is which. Includes the NPC generation pipeline and stdout formatting (absorbs #613).', 'done', 'critical', NULL, 'server', NULL, '2026-03-05 23:12:19', '2026-03-07 07:48:01', NULL, '3fd5fb4be13d1116a0c04581d48668fa', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXR3JWF33ZXF4W84Z0', 'story', '06FB0TNSRWJ60W2RFN2FQ2P3EW', 'Zone identity spec', 'Zone identity taxonomy as YAML the generator deserializes. Schema defined by #611 Rust structs. Minimum two zone types (rural, industrial) with real content. Remaining types can be stubs. The taxonomy must make the generator produce visibly different output per zone type.', 'done', 'critical', 'mellanie', 'copy', NULL, '2026-03-05 23:12:07', '2026-03-07 08:59:42', NULL, '90ab0f91da4ad63524e7b797aface86b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYBTWDB0TGNJAGYN3M', 'story', '06FB0TNSRWJ60W2RFN2FQ2P3EW', 'Krenn culture profile', 'Krenn system-wide culture profile as YAML the generator deserializes. Schema defined by #611 Rust structs. Must provide enough cultural signal that generated NPCs feel Krenn. Sprint scope: name lists (not phoneme generation), speech markers, economic values, social norms. Phoneme-based name generation is out of scope.', 'done', 'critical', 'mellanie', 'copy', NULL, '2026-03-05 23:12:10', '2026-03-07 08:59:43', NULL, '4950fc8677adac4feea1998ff45c1ae7', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYYW12JV7SG241FH2M', 'task', NULL, 'Fix name pool first-pick bias (Dav in all runs)', 'Fisher-Yates shuffle in generator_spike.rs build_name_pool() produces the same first name across different seeds. ''Dav'' appears as NPC 1 in all three test runs (rural/42, industrial/42, rural/99). Investigate seed initialization — the shuffle should produce different orderings for different seeds. This is a bug, not a feature request. Players will metagame recognized names across playthroughs.', 'done', 'high', NULL, 'server', NULL, '2026-03-07 09:35:45', '2026-03-07 10:14:51', NULL, '7aab2f84920686988f9fe6b594cc9bda', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXTVYQK3FGHNVRJGWG', 'task', NULL, 'Behavior dedup within a zone run', 'Generator spike assigns the same behavior string to multiple NPCs in one run. At 11 NPCs (industrial/42): Sera and Vrek both ''presses an ear to a vibrating duct and listens'', Torek and Rask both ''pulls a worker aside for a quiet word near the far wall''. Apply without-replacement selection to behaviors within a single zone generation, same pattern as the name pool Fisher-Yates. If pool exhausts, allow repeats but log a warning.', 'done', 'high', NULL, 'server', NULL, '2026-03-07 09:35:49', '2026-03-07 10:14:54', NULL, '4410c50863b22323a52bad2dfdf4d9e9', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWYZCRYW57HN5MGYG8', 'task', NULL, 'Relationship-to-behavior pipeline', 'Generator spike assigns relationships (rival, friend, subordinate, colleague with sentiment) but they are invisible in behavior output. Five reviewers independently flagged this. Relationships should modify behavior selection: rivals don''t acknowledge each other, friends gravitate, subordinates defer. Minimum viable: when NPC A has a relationship with NPC B and both are in the same social site, A''s behavior line should reflect the relationship. E.g. ''talks past Rask without making eye contact'' for a rival. This is the bridge between ''populated'' and ''inhabited''.', 'done', 'medium', NULL, 'server', NULL, '2026-03-07 09:36:06', '2026-03-07 10:14:54', NULL, '4674355eb2dcba18b02ba76e73b31bb9', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWFKRXPHS17KGP1VBC', 'task', NULL, 'Want/State layer — internal motive biases behavior selection', 'Five reviewers independently identified this as the gap between ''populated'' and ''inhabited''. Every NPC gets a current Want/State (e.g. bored, alert, suspicious, avoiding someone, looking for information) that biases behavior selection and introduces micro-tells. Not a UI element — an internal driver that leaks through observable action. The player sees the behavior, not the Want. The mystery is the gap between them. This is where perception mode gets something to read, and where the asymmetric information mechanic engages. Gestalt''s framing: ''the system generates legible characters but not interesting situations — Want is the lever that converts generated cast into generated situation.'' Ozzie''s framing: ''every NPC is correctly in their lane — what if one isn''t? That crack is the whole game.'' Depends on #629 (behavior dedup) and #630 (expanded behavior pools) being in place so Want has material to express through.', 'done', 'high', NULL, 'server', NULL, '2026-03-07 09:36:51', '2026-03-07 10:14:54', NULL, 'bf3c1e2febb9ff0b4633a9abbbb0676c', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYW89XHEPC7MBZ860R', 'story', '06FB0TNSRZ5GQBCYR7KF7JAZ8M', 'Content: Universal negative injectors (NI-1 through NI-5)', 'Spike 2 finding: universal negative injectors (NI-1 through NI-5) were removed from the RULES const. Worldbuilding constraints belong in per-culture voice_persona blocks, not universal rules. Each culture defines its own technology vocabulary, rank conventions, humor register, Earth references. NI-1/NI-5 are culture-gated not universal. This ticket is superseded by #653 (culture persona authoring).', 'done', 'medium', NULL, 'copy', NULL, '2026-03-07 11:56:00', '2026-03-07 11:56:00', NULL, '5e262a1057fe73f27aee1b51cf858092', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYM15HRRJJW188WREW', 'story', '06FB0TNSRZ5GQBCYR7KF7JAZ8M', 'Spike 1: Build llama-cpp-rs inference CLI', 'Build a standalone Rust CLI tool wrapping llama-cpp-rs. Accepts a prompt via stdin or CLI arg, returns generated text. Loads Gemma 2B (Q4_K_M) and Phi-3 (Q4_K_M) — both models tested against the same prompts in Spike 1. No game integration, no queue, no cache. Goal: prove the plumbing works and enable the manual prompt quality session. Reference: docs/workshops/llm-voice-pipeline/round-3-inputs.md (Spike 1 strategy).', 'done', 'high', NULL, 'server', NULL, '2026-03-07 11:55:31', '2026-03-07 18:18:01', NULL, '95e7c65a94bb1d67259d216d4314248b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZ7R0HVNA2K40PH2GM', 'story', '06FB0TNSRZ5GQBCYR7KF7JAZ8M', 'Spike 1: Manual prompt quality session', 'Jeroen + Mellanie + Paula manually craft and test prompts using the Spike 1 CLI tool (ticket #639). Test culture injectors, behavior seeds, dialogue seeds. Run Gemma 2B and Phi-3 head-to-head on the same prompts. Answer: does this even play? Outputs: winning model recommendation, refined injector templates, go/no-go for Spike 2. No game integration — purely inference tool + manual experimentation. Reference: docs/workshops/llm-voice-pipeline/round-3-inputs.md.', 'done', 'high', NULL, 'copy', NULL, '2026-03-07 11:55:37', '2026-03-07 18:18:04', NULL, 'a9d8d3a069ff6bc7d0ea09fc5585e8f4', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRX9T3CRS5TAEXDAYX4', 'story', '06FB0TNSRZ5GQBCYR7KF7JAZ8M', 'Spike 2: Pre-voicing pipeline integration', 'Wire the validated llama-cpp-rs runner (Spike 1) into the full pre-voicing pipeline. Scope: background queue, cache-as-determinism (generate once per seed, cache result), thread pool isolation (inference vs world generation), baked content generation (pre-voice at build time for hub zones), fallback to base text. Uses whichever model won Spike 1 (Gemma 2B or Phi-3). Full architecture as designed in workshop — both observable behaviors AND dialogue re-voiced. Tells excluded from LLM output but present as read-only context inputs. Reference: docs/workshops/llm-voice-pipeline/round-3-inputs.md.', 'done', 'high', NULL, 'server', NULL, '2026-03-07 11:55:43', '2026-03-07 18:18:05', NULL, '091d08d2ce7e2d8116d1b4ab5eef81b1', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRW0T2X7QXDNCMKFNG8', 'task', '06FB0TNSRZ5GQBCYR7KF7JAZ8M', 'Data model: Add tell_behaviors field to NpcBlueprint', 'Add a separate tell_behaviors: Vec<String> field to NpcBlueprint alongside observable_behaviors. Tells are passthrough — the pipeline checks the field tag and routes all tell_behaviors content as base text, never sending it to the LLM. Additionally, tell presence is used as a read-only input to the re-voicing prompt for surrounding behaviors and dialogue (e.g. an NPC with avoidance_tell gets dialogue phrased in an avoiding way). The tell itself is never LLM output. See D-record from LLM Voice Pipeline Workshop.', 'done', 'high', NULL, 'server', NULL, '2026-03-07 11:55:50', '2026-03-07 18:18:05', NULL, 'ff71c49919ed3bd17c3ef4327bccff6a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRW6MM0B4C5Y9E04DM0', 'story', '06FB0TNSRZ5GQBCYR7KF7JAZ8M', 'Content: Krenn culture injector clauses', 'Author Krenn culture injector clauses for use in LLM re-voicing prompts. 10-20 clauses per culture tier. Sourced from Krenn culture RON speech fields (ticket #610 base). Covers tone, speech patterns, idioms, cultural philosophy. Must include explicit NOT-lists (negative injectors) specific to Krenn to prevent lore contamination. Output: structured clause set ready for pipeline consumption. Required input for Spike 1 manual prompt session (ticket #640).', 'done', 'high', NULL, 'copy', NULL, '2026-03-07 11:55:56', '2026-03-07 18:18:06', NULL, '6ac424a74e96dfcae08fea3bc589b6a3', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXJDMPQNP05JFD2P6G', 'story', '06FB0TNSRZ5GQBCYR7KF7JAZ8M', 'Voice pipeline: GPU acceleration (ROCm/Vulkan)', 'sr-voice has cargo features for rocm/vulkan/cuda/metal but builds CPU-only (~13 t/s). Enable after code stable. GPU would reach 50+ t/s. LIBCLANG_PATH already in Makefile.', 'backlog', 'low', NULL, 'server', NULL, '2026-03-07 18:19:29', '2026-03-07 18:19:29', NULL, 'd2bd7a06f863d8753db86ce7d39dcd67', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZ5GQBCYR7KF7JAZ8M', 'epic', NULL, 'LLM Voice Pipeline', 'Runtime LLM re-voicing of NPC observable behaviors and dialogue using llama-cpp-rs (Gemma 2B / Phi-3 bundled). Decided in LLM Voice Pipeline Workshop (2026-03-07). Two-spike strategy: Spike 1 proves plumbing and quality; Spike 2 wires into pre-voicing pipeline. Tells pass through untouched (base text only) but inform LLM context as read-only inputs. Model bundled with install (~1.5GB). Amends D-123 and supersedes D-124. See docs/workshops/llm-voice-pipeline/.', 'done', 'high', NULL, 'server', NULL, '2026-03-07 11:55:26', '2026-03-07 18:19:40', NULL, '81df2cf5fe47cc5210d8c62541cf9f55', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXVQE5XHJ5HABRWCXR', 'task', NULL, 'Expand behavior pools — trader, foreman, off-shift register', 'Review feedback: trader has 6 behaviors (3 are functional, not observable), foreman has 5 (4 are surveillance). At 11 NPCs per zone, collisions are math. Add: (1) more trader behaviors that are stage directions not internal notes, (2) foreman behaviors showing the person beneath the role (covering for someone, absorbing a problem), (3) off-shift behaviors for dock_worker and technician roles so break room social site has material to draw from. Paula flagged this as medium priority.', 'done', 'medium', NULL, 'copy', NULL, '2026-03-07 09:36:02', '2026-03-07 18:22:02', NULL, '942d0e1196fdb9bac46451b9be5127a0', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZX5H34RPMG3MNKKV8', 'task', NULL, 'Update all agent briefings to reflect v0.2 pivot', 'The Where''s the Fun workshop (2026-03-05) pivoted: generator-first, tycoon bookmark, no detective/smuggler. Multiple agents still reference old design. Update docs/briefings/*.md and .claude/agents/*.md. Nigel specifically flagged during Spike 1 voice testing.', 'cancelled', 'medium', NULL, 'main', NULL, '2026-03-07 14:43:16', '2026-03-07 18:37:45', NULL, 'd6bf65f8df679a4750bb22eccce1a372', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXT3DHSAX139VNTFVC', 'story', '06FB0TNSRZ5GQBCYR7KF7JAZ8M', 'Voice pipeline: observer integration', 'Wire voice cache into perception/observer. Look up voiced text before emitting behavior/dialogue to client. Tell behaviors always passthrough. Fall back to base text on cache miss. Final integration step for voiced content.', 'done', 'high', NULL, 'server', NULL, '2026-03-07 18:19:23', '2026-03-13 08:38:20', NULL, '1085b1ae21e3526d239fccc2b4350b48', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXTADJM056MJ3RCRWR', 'story', '06FB0TNSRZ5GQBCYR7KF7JAZ8M', 'ContentType::Factual — LLM bypass for fact-bearing lines', 'Lines with numbers, causal chains, denials bypass LLM. New ContentType alongside Dialogue/Behavior. Spike 2 showed 2B corrupts numbers and inverts meaning. Separate truth layer (simulation) from register layer (LLM).', 'done', 'high', NULL, 'server', NULL, '2026-03-07 18:19:12', '2026-03-13 08:38:20', NULL, '5e60b67ed4a9f65f3cfd2c572293758d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZXFHE6XS7V027WKQ0', 'story', NULL, 'SQLite settings storage', 'Use SQLite for persistent settings storage. SQLite is compiled into the server binary via rusqlite with bundled feature — zero runtime dependency, no installer, no DLL.
Architecture: settings live on the SERVER side in a SQLite database with per-player settings tables. The client is a dumb terminal — it sends ChangeSettings commands to the server over the existing IPC protocol, same as any other player action. The server reads/writes the SQLite file. The client never touches the database directly.
This keeps the client thin (Godot rendering + input only) and settings consistent with the multiplayer-ready baseline (D-010). Per-player tables mean multiple players on the same server get isolated settings.
Scope: keybindings, audio volume, display preferences, accessibility options. Replaces Godot ConfigFile approach. SQLite gives structured queries for free if settings grow beyond flat key-value (per-game overrides, profile-specific keybindings, accessibility presets).', 'done', 'high', NULL, 'server', NULL, '2026-03-07 08:06:58', '2026-03-13 08:38:21', NULL, '7900c9df95b093396731f53a9504bae7', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRX8GBD260FPD269K4C', 'story', '06FB0TNSRZ5GQBCYR7KF7JAZ8M', 'Friendly and RoutineDeviation tells — iterate post-ship', 'Spike 2 showed these two tells produce output indistinguishable from neutral on Gemma 2B. 3/5 tells work (Nervous, Guarded, Angry). May be 2B capacity ceiling. Options: stronger examples, non-speech channels only, or revisit at 7B.', 'done', 'low', NULL, 'server', NULL, '2026-03-07 18:19:20', '2026-03-13 08:38:21', NULL, 'c2e56eec51454c24877d21d5d5306715', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZQRN59HBG6ZTA0SCW', 'task', NULL, 'Composable behavior engine — assemble NPC behaviors from culture + role + context primitives', 'Current behavior pools are hand-authored per culture×zone×role combination. This doesn''t scale. The generator should compose observable behaviors from smaller pieces: role actions (generic physical stage directions), culture modifiers (Krenn-specific flavoring, speech patterns), and context tags (on-shift, off-duty, social site type). Server implements the composition engine that assembles these at generation time. See Q-057. Dependency: copy team delivers the decomposed content format (separate ticket).', 'done', 'high', NULL, 'server', NULL, '2026-03-07 09:54:51', '2026-03-13 08:38:22', NULL, '6938e199ee115c4d690a9303df8cc97a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRY4PM820VK4AMK8XPC', 'task', NULL, 'Remove v0.1 content loading system', 'Delete the v0.1 hand-authored content pipeline, superseded by the v0.2 generator-first approach (D-122, D-128). Scope: server/src/content/ module (loader.rs, types.rs, line_pool.rs, hot_reload.rs, spawn.rs, template.rs, instantiation.rs, mod.rs), tooling/content-converter/, tooling/validate-content, content-ron/ compiled output, content/_meta/ style guides, related design docs (content-directory-structure.md, monologue-content-architecture.md, opening-hook-content.md, v0.1-wow-moments-checklist.md), and server/tests/content_*.rs test files. Preserve content/global/ (enums, knowledge, culture profiles) — those are still live. Preserve any types from content/types.rs that are re-exported to other modules.', 'done', 'medium', NULL, 'server', NULL, '2026-03-07 18:31:00', '2026-03-13 08:38:22', NULL, 'b25abfd77afd93487064862c71992601', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRW36DF5F95406ETRGR', 'story', NULL, 'Server: extract platform abstraction module when second consumer appears', 'Trigger ticket: when any server module beyond voice/hardware.rs needs OS-dependent info (CPU, memory, battery, GPU), extract shared platform queries into server/src/platform.rs. Currently all OS detection lives in server/src/voice/hardware.rs (sysinfo crate, on_battery(), VRAM probes, available_parallelism). Single consumer, no duplication — no action needed until a second consumer appears. Trigger conditions: (1) a second system needs CPU/memory info independently of voice pipeline, (2) battery detection needed outside voice scaling (e.g. tick rate throttling), (3) server exposes system info over IPC (multiplayer diagnostics). Extraction is ~30 minutes: pull on_battery(), probe_current_free(), sysinfo calls into platform.rs, leave voice-specific scaling logic in hardware.rs.', 'backlog', 'low', NULL, 'server', NULL, '2026-03-13 09:49:07', '2026-03-13 09:49:07', NULL, 'f663fdc943a2f78182985150af1210af', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYERWZ0J9T76FJ6GTR', 'task', NULL, 'Restructure: move behavior primitives to zone-type template files --sprint 26', 'Move existing behavior_primitives from per-location zone specs (krenn-rural-zone.ron, krenn-industrial-zone.ron) into global zone-type template files (content/global/zone-types/rural_agricultural.ron, industrial_freight.ron). Strip primitives and typical_behaviors from location specs. Add zone_type field to location specs. Scrub Krenn-specific language from primitives (they must be culture-neutral). Per D-141 zone-type template architecture.', 'done', 'high', 'done', 'copy', NULL, '2026-03-13 10:30:29', '2026-03-13 10:43:42', NULL, 'ed34153d0b950462010bf5200522c80a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWDBHBZN895XZ244QC', 'story', '06FB0TNSRZ5GQBCYR7KF7JAZ8M', 'UX: AI-Enhanced Dialogue toggle + hardware detection', 'Implement layered hardware detection and player-facing controls for the LLM Voice Pipeline. Three detection layers: (1) CPU/RAM check — can the model even load? (2) time-per-token benchmark on first enable — is inference fast enough to be useful? (3) recommendation threshold — suggest disabling if below bar, but player can always override. Always-available ''AI-Enhanced Dialogue'' toggle in settings. No hard minimum spec floor — if they pass the RAM check, let them run it. Bundled model (no download step). Blocked on Spike 2 pipeline existing.', 'done', 'medium', NULL, 'client', NULL, '2026-03-07 11:56:15', '2026-03-13 10:45:13', NULL, '3ebf0843ce489127226557c802f203a0', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRW29VJJRQE5CQDYW0M', 'story', NULL, 'PlatformInfo: client-side OS abstraction autoload', 'Create a PlatformInfo autoload (client/scripts/autoloads/platform_info.gd) that centralizes all OS-dependent queries. Individual systems consume PlatformInfo — they never call OS.* directly.
Scope:
- PowerProfile enum (FULL, BATTERY, POWER_SAVER) with power_profile_changed signal
- 30-second power state poll timer (skipped on desktops without battery)
- Memory queries (refresh on demand)
- Platform identity (OS name, has_battery)
- Platform-dependent file paths (user_data_dir, config_dir, install_dir, executable_path, benchmark_cache_path, cache_dir, model_dir)
- Full interface scope per Q-059 (Tyre researching best practices — resolution, GPU, CPU, DPI, locale, etc.)
Migration:
- Refactor HardwareDetector to consume PlatformInfo instead of calling OS directly
- Delete AiDialogueDetector (duplicate of HardwareDetector)
- Wire power_profile_changed into HardwareDetector for battery auto-suspend (D-138 Layer 3)
Architecture: D-141. Client-side only — never relies on server for hardware info (multiplayer readiness).', 'done', 'high', NULL, 'client', 'D-141', '2026-03-13 09:40:49', '2026-03-13 10:45:13', NULL, 'e2dd5b2892d093da98488f376dc1ba54', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWCWTR33WS2TGEK474', 'story', '06FB0TNSRZ5GQBCYR7KF7JAZ8M', 'Content: Base text elevation pass', 'Review and elevate existing NPC base text across zone RON content (~50 lines/role). Base text serves as both the LLM seed and the fallback when pre-voicing is unavailable or disabled — it must be strong on its own. Pass should improve clarity, cultural neutrality (so culture injectors have clean material to work from), and completeness. Priority: Sova Transit District hub NPCs (baked content tier). Reference: resolved input C-5 and C-9 from workshop round 2.', 'done', 'medium', NULL, 'copy', NULL, '2026-03-07 11:56:06', '2026-03-13 11:24:58', NULL, '8309a662478dd555f1aaa030cb58c5de', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZP37GQR92GB0G6J48', 'task', NULL, 'Author decomposed behavior content — role action templates, culture modifiers, context tags', 'Define and author the content format for composable behaviors (Q-057). Deliver: (1) role action templates — generic observable stage directions per role, no culture specificity, (2) culture modifier sets — Krenn-specific flavoring that overlays role actions (speech style, mannerisms, social norms), (3) context tags — on-shift, off-duty, break-room, social that the generator uses to filter/weight behaviors. Output format TBD with server team (RON or similar). Blocked by server ticket #633 for composition engine schema.', 'done', 'high', NULL, 'copy', NULL, '2026-03-07 09:55:04', '2026-03-13 11:24:58', NULL, 'ef3ecda5be69b0ae1e7c502dfa6307ed', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWG4R8G8SSGCQ2AMPR', 'story', '06FB0TNSRZ5GQBCYR7KF7JAZ8M', 'Voice pipeline: culture persona authoring for non-Krenn cultures', 'Each culture needs voice_persona, voice_examples, occasional_injections. Worldbuilding constraints (tech terms, rank conventions) belong in culture persona not universal rules. Krenn done and test-validated. Remaining cultures per content roadmap.', 'done', 'medium', NULL, 'copy', NULL, '2026-03-07 18:19:26', '2026-03-13 11:24:59', NULL, '4e35276234130f8f0b14cd2c1ea598f3', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZSPPMN08CB5Y2VZK8', 'task', NULL, 'Docs: Amend D-123, supersede D-124, file workshop D-records', 'File D-records from the LLM Voice Pipeline Workshop (2026-03-07). (1) Amend D-123 to cover both baked (build-time, human-reviewed) and pre-voiced (runtime background) modes — Paula''s distinction must be explicit. AI pipeline is both an authoring tool and a runtime enhancement. (2) Supersede D-124 (in-game live AI deferred) — the workshop resolved this with the two-spike strategy. (3) File new D-records covering: model selection (Gemma 2B primary, Phi-3 fallback; no Chinese-origin models), bundled distribution (~1.5GB), hardware detection policy (no hard floor), tell treatment (passthrough + context influence), spike strategy. Source: docs/workshops/llm-voice-pipeline/round-3-inputs.md and workshop-outcomes.md when available.', 'done', 'medium', NULL, 'copy', NULL, '2026-03-07 11:56:25', '2026-03-13 11:24:59', NULL, 'fef0dae7b82f45a52ddb5675821e8fa5', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZ7A5FRRD042G4TK1M', 'task', NULL, 'Remove detective mission system', 'Delete v0.1 detective/investigation system, dropped in v0.2 pivot (D-122). Scope: content/global/knowledge/investigation.yaml (case facts), content/global/factions/lattice-commission.yaml (detective employer faction), docs/design/ detective specs (character-build-detective.md, detective-chain-of-command.md, archetype-evidence-presentation.md, divergent-starting-knowledge.md, insert-hud-wireframe-v01.md detective sections), docs/workshops/v01-content-scoping/ and content-gap-analysis_v0_1/ workshop outputs, pc-detective.yaml and pc-smuggler.yaml NPC files. Review client/tests/test_insert_off_behavior.gd and server test_world invariants for detective references.', 'done', 'medium', NULL, 'copy', NULL, '2026-03-07 18:31:10', '2026-03-13 11:25:00', NULL, '518e03aaf4fa04c693234c126c0204c9', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRY8ASP39EGQZ99WB38', 'task', NULL, 'Remove handwritten Krenn dialogue and monologue content', 'Delete v0.1 hand-authored Krenn dialogue trees, NPC profiles, and monologue files from content/campaigns/. Superseded by generated NPCs (D-122) and voice pipeline (D-138). Scope: content/campaigns/main/systems/krenn/ (23 NPC YAML profiles, dialogue/ trees, monologue/ files for detective and smuggler, insert/detective.yaml), content-ron/campaigns/ compiled versions. Note: content/global/culture-krenn.ron and culture-krenn.example.ron are LIVE — do not delete.', 'done', 'medium', NULL, 'copy', NULL, '2026-03-07 18:31:07', '2026-03-13 11:25:00', NULL, 'a2432b0099e70684b62920bb692d4bb2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRW44VBDKXPV5SPHJ2C', 'task', NULL, 'Update agent profiles and briefings for v0.2', 'Audit and update .claude/agents/ profiles (20 files) and docs/briefings/ (18 files) to reflect v0.2 pivot. Many briefings still reference v0.1 detective/smuggler gameplay, hand-authored content pipeline, and pre-workshop design assumptions. Update to reflect: generator-first approach (D-122), all NPCs generated (no named NPCs), voice pipeline (D-138), tycoon bookmark deferred, no detective/smuggler roles. Remove stale context that could mislead agents.', 'done', 'medium', '', 'planning', NULL, '2026-03-07 18:31:14', '2026-03-13 11:27:57', NULL, '4592ada8c8cc20496815e4e68e72d357', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRY4CMF4W57DCQASQNW', 'task', NULL, 'Mid-sprint ticket creation protocol for team agents', 'When team agents discover new work during a sprint (like #661 zone-type templates in Sprint 26), they should message the lead immediately for retroactive ticket creation rather than just doing the work uncaptured. Document this in the sprint-start skill agent spawn prompt and/or team-patterns.md.', 'backlog', 'low', NULL, 'meta', NULL, '2026-03-13 11:37:34', '2026-03-13 11:37:34', NULL, 'a12f1ea5b537e527f18f4b74e7c7dd38', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWX3RXXCTHX0TH0DE0', 'task', NULL, 'Remove v0.1 YAML schema and meta directories', 'JSON Schema validation of YAML content is replaced by Rust struct deserialization (validate_ron). These directories served the v0.1 YAML pipeline which has been deleted (server/src/content/ removed). Clean up any references to these schemas.', 'done', 'medium', NULL, 'maintenance', NULL, '2026-03-13 12:33:58', '2026-03-13 14:01:21', NULL, '5c1fbb6645b64d8bb04e6326de4b3f13', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYS4EPWWHBJ5WQVZKG', 'task', NULL, 'Remove dead content-ron build artifact and converter tool', 'content-ron/ is a gitignored generated directory that nothing consumes at runtime. tooling/content-converter/ generates it. Both are v0.1 artifacts superseded by the v0.2 RON-first generator pipeline. Remove the converter crate, clean up any Makefile targets that reference content-ron or content-converter.', 'done', 'medium', NULL, 'maintenance', NULL, '2026-03-13 12:33:46', '2026-03-13 14:01:21', NULL, '63c2aea6b1bf09a20472b5e87e99015a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWW5EH5KJKGBT2SCEM', 'task', NULL, 'Move authoring guides from docs/design/ to wiki/authoring/', 'Authoring guides (base-text, dual-lens, cultural generation, monologue voice guides) are about the game world''s voice and content standards. They belong in wiki/authoring/ alongside existing authoring docs. Move from docs/design/ and update any cross-references.', 'done', 'medium', NULL, 'maintenance', NULL, '2026-03-13 12:48:38', '2026-03-13 14:01:21', NULL, 'ab4278155656710c7b1faed48c0c8318', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWMBH2CHHYNW5YRY9M', 'task', NULL, 'Move NPC pattern docs from docs/design/ to wiki/', 'docs/design/npc-patterns/ contains lore/authoring reference material (mirror moments, contradiction arcs, threshold patterns). These belong in wiki/ alongside other canonical worldbuilding content, not in project documentation. Move to wiki/npc-patterns/ or wiki/authoring/npc-patterns/.', 'done', 'medium', NULL, 'maintenance', NULL, '2026-03-13 12:34:27', '2026-03-13 14:01:21', NULL, 'ae400779655b17477fce03afca1ce42a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWS7M9P29KDJZYNBBC', 'task', NULL, 'Add standardized frontmatter to docs/ and wiki/ files', 'Adopt a consistent frontmatter format across all documentation, similar to what skills use. Minimum fields: name, description (one-line, used for relevance filtering), type (architecture/design/lore/authoring/briefing/sprint/workshop), status (active/archived/draft). This enables context-aware document loading -- agents can filter by metadata instead of loading everything. Design the schema first, then apply incrementally (wiki/ first as it''s smaller, then docs/).', 'done', 'medium', NULL, 'maintenance', NULL, '2026-03-13 12:49:15', '2026-03-13 23:02:07', NULL, '127d616e8b4539e1cacb3b3160a29636', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRX1PW1RXYRBNRHVNYW', 'story', '06FB0TNSRXZK7C4X2DHQ6A0CKR', 'Gate network travel planner — interactive route-finding overlay on the star map insert, showing hop paths between systems with transit time estimates and corridor information', NULL, 'backlog', 'medium', NULL, 'client', NULL, '2026-03-15 10:20:07', '2026-03-15 10:20:07', NULL, 'df94c2282d6cc2cbe7748eef82f8ca1a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZDYCH1YRQNVN0B2FC', 'story', '06FB0TNSRXD5W32VCV5YZY3NX0', 'Apartment generator logic (server)', 'Server implementation: apartment generator takes economic position (from #615 tycoon starting state) + cultural context (from #679 location-to-culture resolution) + aesthetic taste trait. Produces apartment tile layout, furniture placement, and item set. Decision D-136 (First Settled Reach moment: apartment + insert activation). Decision D-137 (generator: structural and cosmetic variety). Blocked by: #615 (tycoon starting state), #679 (culture resolution).', 'backlog', 'high', NULL, 'server', NULL, '2026-03-17 07:06:30', '2026-03-17 07:06:30', NULL, '12bd8760286c0756641e19fb19c6bd6d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWN2D34BAWH41DJHXW', 'story', '06FB0TNSRXD5W32VCV5YZY3NX0', 'Apartment visual rendering + first-moment sequence (client)', 'Client implementation: renders apartment generator output. Implements alarm clock homage on Day 1 wake (D-126 Groundhog Day homage). Implements insert activation beat — first moment the player activates their insert in the apartment (D-136). Blocked by: #681 (apartment generator must produce layout/items before client can render).', 'backlog', 'high', NULL, 'client', NULL, '2026-03-17 07:06:36', '2026-03-17 07:06:36', NULL, '8a728e7fc73654989b60189b8dc95c21', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZDA414TMQBXY3BHJ0', 'epic', NULL, 'Character Visuals System', 'Epic covering all character visual systems for Sprint 28: body types, face types, hair, expressions, scars/tattoos, injuries, clothing/apparel/accessories, 8-direction rotation, color mesh recolorability, sprite layer compositor, and character creation UI. Architectural foundation: D-134 (full character customization at tile scale), D-146 (tile-scale heavy zoom preview).\n\nScope will be refined during the Sprint 28 planning workshop. Workload is fluid until the workshop concludes.', 'done', 'high', NULL, 'visual', NULL, '2026-03-17 08:19:55', '2026-03-17 08:19:55', NULL, '3a111ac865115f954696fcfae5df17d1', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZZ7YYET2DRFS14V68', 'task', NULL, 'Assign team and sprint for v0.2 story tickets (#614-#626)', 'The v0.2 story tickets from the Where''s the Fun? workshop (#614-#626, plus #620-#622) are teamless in the backlog. Before they can enter a sprint they need: (1) team assignment — each ticket must be assigned to exactly one team (server, client, copy, or planning). Split cross-team work into separate tickets with explicit blockers. (2) Review open questions that block implementation: Q-WTF-039 (character creation portrait vs tile preview), Q-WTF-040 (do creation choices trace into the generated apartment). (3) Confirm whether any of these tickets should be split further before implementation begins. Tickets in scope: #614 (bookmark definition system), #615 (tycoon small business owner starting state), #616 (broad economic verb vocabulary design), #617 (auto-generated apartment), #618 (CK3-style character creation screen), #619 (full character customisation), #620 (culture-implicit-in-location system), #621 (NPC personality surface area), #622 (relationship formation system), #624 (quietly responsive world), #625 (dual-scale consequence model), #626 (setting delivery — both layers). Output: each ticket has a team and any required child tickets are created with blockers.', 'done', 'high', NULL, 'planning', NULL, '2026-03-16 21:43:12', '2026-03-17 08:30:31', NULL, '0d30175940065aa560b2187cac7782ef', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZGKH24QYWQD4NWFPW', 'task', NULL, 'Move content/ directory under server/', 'Game content (RON files, campaign data) is consumed exclusively by the server. Move content/ to server/content/ so it lives with its consumer. Update all path references in server Rust code (generator_spike, validate_ron, tests), Makefile targets, and any tooling scripts. The client''s own data files (client/data/) are separate and stay put.', 'done', 'medium', NULL, 'server', NULL, '2026-03-13 12:34:12', '2026-03-17 11:18:02', NULL, '321d1e10a17bde387f99ba3dac14ce9a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWAHRBQ9T5ZR9KJYMC', 'task', NULL, 'Server: add ZoneTypeTemplate and LocationSpec structs for D-142 zone-type architecture', 'Content team has shipped zone-type template files (content/global/zone-types/) and restructured location specs per D-142. Server needs: (1) ZoneTypeTemplate struct in blueprint.rs — roles without weight, behavior_primitives only, no typical_behaviors. (2) LocationSpec struct (or update ZoneSpec) — role_weights instead of full roles, zone_type string reference, abandoned flag, poi_overlay field. (3) Register zone_type schema in validate-ron so tooling/validate-ron accepts the new format. (4) Loader change: resolve (zone_type, role) → primitive pool from global template file, not from location spec.', 'done', 'high', NULL, 'server', NULL, '2026-03-13 10:47:54', '2026-03-17 11:18:02', NULL, '9f5b841023e038976b95d82c3fc79953', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWFFM0F94Q7AXG8PGC', 'task', NULL, 'Clippy cleanup and CI enforcement', 'Gemini project review found 66 clippy warnings with --deny warnings. Two actions: (1) one-time cleanup — run cargo clippy --fix for automatable fixes, manual refactor for complex type signatures and too_many_arguments. (2) Add cargo clippy -- --deny warnings as a blocking check in the PR review workflow or pre-commit hook. This is the single biggest process gap given our review standards elsewhere.', 'done', 'high', NULL, 'server', NULL, '2026-03-07 10:58:10', '2026-03-17 11:18:03', NULL, '8047133eb07e8857350f2a0bf77ba913', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXRA5BRW6CJB05DN9M', 'task', NULL, 'Audit and flag v0.1 YAML content on server/copy branches', 'The server and copy branches contain ~67 YAML files (NPC profiles, dialogue, monologue pools) that haven''t merged to main. Per D-122 (all NPCs generated) and D-138, this content is superseded by the generator approach. Audit these files: flag what''s still useful as generator input (convert to RON), and mark the rest as deprecated. Don''t delete without team review.', 'done', 'medium', NULL, 'server', NULL, '2026-03-13 12:49:07', '2026-03-17 11:18:04', NULL, '2e0bbd06b50a72699351057f10af5cb6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRX9PHETW3SM83K9TC4', 'task', NULL, 'Add cargo audit to CI/review pipeline', 'Gemini''s project review used cargo audit to surface the bincode RUSTSEC finding. We should run this regularly. Options: (1) add to pre-PR review checks (make pre-pr), (2) run on Cargo.toml/Cargo.lock changes in CI, (3) scheduled periodic scan. Recommend option 2 — trigger on dependency changes so new vulnerabilities are caught at introduction, not months later. Also add cargo-audit to the Makefile targets so it can be run manually.', 'done', 'medium', NULL, 'server', NULL, '2026-03-07 10:58:17', '2026-03-17 11:18:04', NULL, 'daec4a89afff3c871f62b5ad88cbbd22', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWCK5MQKRN7V4N3SHC', 'task', NULL, 'Formal D-record for overheard conversation RON format', 'The overheard conversation pool format (role-pair-parameterized, knowledge_payload, relationship_context) was introduced in #664 with a content note in decisions/content.md but no formal D-record. Claim and write a D-NNN entry before the format proliferates to all 29 zone types.', 'cancelled', 'low', NULL, 'copy', NULL, '2026-03-17 11:19:09', '2026-03-17 11:19:09', NULL, '6f577c85c55ba33d8549a099f0a90282', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWA1974HZCDDB9RJ08', 'task', NULL, 'Design transit_corridor zone type for between-zone passage spaces', 'No zone type exists for transit corridors, walkways, and passage spaces between zones where eavesdropping gameplay naturally occurs. These are high-value spaces for overheard conversation mechanics. Needs zone character definition, roles, and behavior primitives.', 'cancelled', 'low', NULL, 'copy', NULL, '2026-03-17 11:19:12', '2026-03-17 11:19:12', NULL, 'f2213b420d6acbcb0be4a67efbda4ca7', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZ7Y8K99HK3C6SF4Q0', 'epic', NULL, 'Author zone-type behavior templates (29 remaining types)', 'Author behavior primitive pools for the 29 zone types not yet covered (rural_agricultural and industrial_freight exist). Each zone type needs ~4 roles x ~15 primitives. Primitives must be culture-neutral, standalone-readable, tagged with context and modifier_hint. Per D-141 zone-type template architecture. 31 total zone types for v1.0.', 'done', 'high', NULL, 'copy', NULL, '2026-03-13 10:30:36', '2026-03-17 11:28:30', NULL, '95f936a24b175e6fb9345c3c5ae7495d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRW7Q5ZEZ9Y1CY9GK6W', 'task', NULL, 'Extend validate-ron to cross-culture name pool collision checking', 'Currently name collisions across culture profiles (Vael, Osse, Krenn) are caught only in PR review. Extend tooling/validate-ron to check for given_name and family_name overlaps across all culture-*.ron files. Sprint 26 review caught 6 overlaps manually.', 'done', 'low', NULL, 'copy', NULL, '2026-03-13 11:37:33', '2026-03-17 11:28:31', NULL, 'a89b0e8a9e7f1a18edd53cf1645fdd17', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYXR6TA5A8J9AJGC4M', 'task', NULL, 'Replace overheard.yaml with generator-compatible conversation system', 'content/npc-conversations/overheard.yaml contains 48 hand-authored conversation entries referencing deleted named NPCs (Kael, Sera, Torek, etc.). All entries are v0.1 detective-arc content. Needs disposition: either delete entirely (generator replaces overheard conversations) or redesign as role-generic templates compatible with the zone-type template architecture (D-142). Cross-references removed but content still references named characters that no longer exist.', 'done', 'medium', NULL, 'copy', NULL, '2026-03-13 10:57:46', '2026-03-17 11:28:31', NULL, 'ef2f251a360c61e8de561c2b7b9cf10d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZY4009E9TC215X6EC', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Clothing, apparel and accessory layer sprites', '2 clothing sets and 2 accessories with full layer ordering, occlusion rules, 8-direction rotation, and color mesh regions. Clothing is the outermost visible layer and must occlude appropriately.\n\nDeliverables:\n- 2 clothing set sprite sheets (8 directions each)\n- 2 accessory sprite sheets (8 directions each)\n- Layer order spec (clothing above body/injuries, accessories above clothing)\n- Occlusion rules (which body parts are hidden per garment)\n- Color mesh regions per clothing item and accessory\n\nBlocked by: #684 (Character visuals planning workshop) — clothing system spec, occlusion rules, and layer ordering confirmed by workshop.\n\nScope will be refined during the Sprint 28 planning workshop. Workload is fluid until the workshop concludes.', 'cancelled', 'high', NULL, 'visual', NULL, '2026-03-17 08:20:57', '2026-03-17 17:48:01', NULL, '6fcbfec49483ab077512a68e6fdef5bf', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZ6A71PWM1SY11W7D8', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Face type sprites', '2 face types, each with 8-direction rotation sprites and color mesh regions (skin tone recolorability at minimum).\n\nDeliverables:\n- 2 distinct face type sprite sheets (8 directions each)\n- Color mesh region maps per face type\n- Alignment spec for compositing over body sprites\n\nBlocked by: #684 (Character visuals planning workshop) — face spec, color mesh regions, and alignment approach confirmed by workshop.\n\nScope will be refined during the Sprint 28 planning workshop. Workload is fluid until the workshop concludes.', 'cancelled', 'high', NULL, 'visual', NULL, '2026-03-17 08:20:44', '2026-03-17 17:48:01', NULL, '97aa92d740d2a3e4ef1719e0c34e3a42', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXW2EA2JPY188FD3KC', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Visible injuries overlay sprites', 'Damage state sprites and healing progression visuals as overlay sprites. Represents the visible state of combat injury on the character.\n\nDeliverables:\n- Damage state overlay sprites (states defined by workshop)\n- Healing progression visuals showing recovery stages\n- Layering rules relative to clothing and scar overlays\n\nBlocked by: #684 (Character visuals planning workshop) — damage state count, healing progression steps, and layering rules confirmed by workshop.\n\nScope will be refined during the Sprint 28 planning workshop. Workload is fluid until the workshop concludes.', 'cancelled', 'high', NULL, 'visual', NULL, '2026-03-17 08:20:54', '2026-03-17 17:48:01', NULL, '9ba0e7567f6c6d53e2ad8e9719ca6511', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRY7P0G9NW718KB3H10', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Facial expression sprites', 'Expression state sprites for all expression types defined by the planning workshop. 8-direction rotation per expression.\n\nDeliverables:\n- Sprite sheets for each expression state (count TBD by workshop) across 8 directions\n- Expression state naming conventions\n- Compositing approach (replaces or modifies face layer)\n\nBlocked by: #684 (Character visuals planning workshop) — expression state list and count confirmed by workshop.\n\nScope will be refined during the Sprint 28 planning workshop. Workload is fluid until the workshop concludes.', 'cancelled', 'high', NULL, 'visual', NULL, '2026-03-17 08:20:49', '2026-03-17 17:48:01', NULL, 'a2ee24a169b024159e444944c1e8bffe', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRY056A5VV8YXQKXFS8', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Scar and tattoo overlay sprites', '2 scar options and 2 tattoo options as overlay sprites with placement system. These are additive overlays composited above the skin layer.\n\nDeliverables:\n- 2 scar overlay sprites with placement anchors\n- 2 tattoo overlay sprites with placement anchors\n- Placement system spec (body region mapping, z-order within overlay stack)\n- Interaction with color mesh (tattoos may have their own recolorable region)\n\nBlocked by: #684 (Character visuals planning workshop) — placement system approach and overlay rules confirmed by workshop.\n\nScope will be refined during the Sprint 28 planning workshop. Workload is fluid until the workshop concludes.', 'cancelled', 'high', NULL, 'visual', NULL, '2026-03-17 08:20:52', '2026-03-17 17:48:01', NULL, 'a5f00d14329704abd2971066a34571ac', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRX0Z9WT9F3V33ERWSR', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Hair type sprites', '2 hair styles, each with 8-direction rotation sprites and color mesh regions for hair color recolorability.\n\nDeliverables:\n- 2 distinct hair style sprite sheets (8 directions each)\n- Color mesh region maps per hair style\n- Alignment and layering rules (sits above face, may occlude ears/forehead)\n\nBlocked by: #684 (Character visuals planning workshop) — hair spec, occlusion rules, and color mesh approach confirmed by workshop.\n\nScope will be refined during the Sprint 28 planning workshop. Workload is fluid until the workshop concludes.', 'cancelled', 'high', NULL, 'visual', NULL, '2026-03-17 08:20:47', '2026-03-17 17:48:01', NULL, 'b50dc507c119105c2bd88be33fed3618', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRY0B9BMA8A8GATFM20', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Body type sprites', '2 body types, each with 8-direction rotation sprites and color mesh regions marked for recolorability. These are the base layer all other character sprites composite on top of.\n\nDeliverables:\n- 2 distinct body type sprite sheets (8 directions each)\n- Color mesh region maps per body type\n- Naming and file conventions per workshop spec\n\nBlocked by: #684 (Character visuals planning workshop) — body proportions, color mesh spec, and target size confirmed by workshop.\n\nScope will be refined during the Sprint 28 planning workshop. Workload is fluid until the workshop concludes.', 'cancelled', 'high', NULL, 'visual', NULL, '2026-03-17 08:20:41', '2026-03-17 17:48:01', NULL, 'd86f33c80881cdaee69cb84667570441', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWQEZ7D8AX2PRS2FWM', 'task', '06FB0TNSRZDA414TMQBXY3BHJ0', 'LOD crowd rendering research — performance-driven strategy for 400+ characters', 'Research and prototype LOD strategy for 400+ simultaneous characters on screen (crowd scenes, protests, battles).\n\nCurrent budget: 80 NPCs at full detail. This ticket defines the path to 400+.\n\nKey constraints from workshop:\n- LOD degradation is performance-driven, NOT distance/count threshold\n- Degradation is proactive (triggers on PROJECTED viewport character count, not measured frame drop — reactive causes hitching)\n- Progressive from player outward: furthest degrade first (full mesh -> simplified mesh -> billboard)\n- When paused: restore full detail on all characters\n- At billboard tier: body size tier and dominant clothing color must remain visually distinct\n- Outline baked into impostor sprite at billboard tier\n\nDeliverable: technical spec + prototype result. Does not produce shippable code.\n\nNot in Sprint 28 — backlog for when crowd-scale systems begin.', 'backlog', 'medium', NULL, 'client', NULL, '2026-03-17 17:48:12', '2026-03-17 17:48:12', NULL, '806d4d38941c2642bb60218be8a69158', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWFXW9NXVCYK5JV65G', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Standalone 3D character editor', 'Standalone Godot tool for authoring and previewing 3D character appearances. Primary Sprint 28 deliverable.\n\nSpec: docs/design/character-visuals-spec.md (written by Qatux this sprint)\n\nRequirements:\n- Real-time 3D viewport at 30 degree camera tilt / 45 degree map rotation\n- 4 cardinal direction buttons (N/E/S/W), South-facing default\n- Body type selector (2-3 types, switching must validate all combinations)\n- Clothing/hair/face customization panels with per-item color pickers\n- Inverted hull outline at #1e1e24\n- Dark/neutral backdrop (no apartment preview)\n- No free rotation — cardinal buttons only\n- Player and NPC use identical models (no protagonist distinction)\n\nBlocked by: wireframe (#685), body type meshes (#699)', 'cancelled', 'high', NULL, 'client', NULL, '2026-03-17 17:48:05', '2026-03-17 17:48:48', NULL, '1a741cf3bbe2bc54fac0f8a941ec5b04', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWJG3SZT1RSC8XQNG4', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', '3D body type placeholder meshes (2-3 types)', '2-3 distinct 3D body types as Godot-compatible meshes. Placeholder quality acceptable for Sprint 28 — visual fidelity is not the goal, switching validation and proportion read at 30 degree isometric is.\n\nRequirements:\n- 2-3 body type meshes (e.g. lean / average / broad)\n- Readable as distinct body shapes at ~40px screen height\n- Compatible with clothing overlay / compositor system (#700)\n- Rig must support 4 visual facing directions (N/E/S/W)\n- Proportions validated in the standalone editor at 30 degree camera\n\nBlocks: #698 (editor), #700 (compositor)', 'cancelled', 'high', NULL, 'visual', NULL, '2026-03-17 17:48:05', '2026-03-17 17:48:48', NULL, '544ee108294345428cd4cffceb9acd28', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZ2CZB5EBEAZ0JM1B0', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', '3D character appearance compositor', 'Godot shader/material system for real-time character appearance assembly. Runtime compositor for 3D characters.\n\nSpec: docs/design/compositor-api-spec.md (written by Qatux this sprint)\n\nRequirements:\n- Per-item clothing color (not uniform team color — each item has its own color parameter)\n- Inverted hull outline at #1e1e24, uniform for all characters\n- Billboard impostor tier: outline baked in, body size tier and dominant clothing color preserved\n- LOD integration: compositor outputs baked impostors at billboard tier\n- Wear level as continuous shader parameter (flavor, not primary sprint focus)\n- No player/NPC distinction in compositor logic\n\nBlocked by: #699 (body type meshes)\nBlocks: #698 (editor depends on compositor for preview)', 'cancelled', 'high', NULL, 'client', NULL, '2026-03-17 17:48:05', '2026-03-17 17:48:48', NULL, 'f6df96a8539ac87d7cb3b8404f8afe94', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXK06MPH5XE64D51CC', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Character visuals planning workshop', 'Planning workshop for Sprint 28 character visuals. Scope revised during workshop: approach is real-time 3D models in Godot, not 2D sprites.\n\nDeliverables (workshop outputs):\n- docs/design/character-visuals-spec.md\n- docs/design/compositor-api-spec.md\n- docs/workshops/character-visuals/workshop-outcomes.md\n- D-records filed for: camera angle (supersedes D-019), 3D rendering approach, outline spec, direction count, LOD strategy, uniform player/NPC visuals\n\nKey decisions locked:\n- Camera: 30 degree tilt / 45 degree map rotation (diamond grid)\n- Rendering: real-time 3D models, not sprites\n- Directions: 8 server-side (perception), 4 visual facings (N/E/S/W) with smooth rotation interpolation\n- Outlines: inverted hull, #1e1e24, uniform for all characters\n- LOD: performance-driven, proactive (projected viewport count), not reactive\n- Editor: standalone Godot tool, 4 cardinal direction buttons, South-facing default, dark/neutral backdrop\n- Player/NPC: no visual distinction\n\nBlocks downstream implementation tickets.', 'done', 'high', NULL, 'planning', NULL, '2026-03-17 08:20:05', '2026-03-17 18:28:03', NULL, '2ff96008f1aa30c8cf849b2e59e6f845', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWZRN2JBEB6V5HEC6R', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Define CharacterVisualDescriptor (GDScript + Rust)', 'Define the server-to-client data contract for character visual state. This struct must be agreed and stable before compositor code is written.\n\nSpec (from docs/architecture/character-asset-organization.md Section 6):\n\nCharacterVisualDescriptor:\n body_type: BodyType # enum: ThinM, ThinF, AverageM, AverageF, MuscularM, MuscularF, TeenM, TeenF, HeavyM, HeavyF, Child\n head_id: String # template ID ("head_042") -- resolves to heads/templates/\n hair_id: String # hair style key ("bob", "ponytail", "bald")\n hair_tint: Color\n facial_hair_id: String # empty string = none\n facial_hair_tint: Color\n eyebrow_id: String\n eyebrow_tint: Color\n skin_tone: int # index 0-8 into 9-tone palette\n clothing_slots: Dictionary # slot -> item_id\n clothing_tints: Dictionary # item_id -> Array[Color]\n accessory_slots: Dictionary # slot -> item_id\n accessory_tints: Dictionary # item_id -> Color\n\nDeliverables:\n- GDScript class at client/scripts/rendering/character_visual_descriptor.gd\n- Rust struct in server at server/src/bridge/types.rs (or equivalent bridge layer)\n- IPC serialization: MessagePack field mapping between Rust struct and GDScript class\n- BodyType enum defined in both GDScript and Rust with matching variant names\n\nDecisions: D-159 (11 body types), D-160 (21 segments), D-161 (head separate)\n\nAcceptance: Both sides compile. A hardcoded test descriptor can be constructed in GDScript and matched to a Rust struct without panics.', 'done', 'high', NULL, 'client', NULL, '2026-03-19 21:20:28', '2026-03-20 08:05:06', NULL, '15b9a16bda5f3d08cd6410ec2d90a4ab', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRY9P48AAKA6T02Q5WR', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Port spike shaders and import settings to production client', 'Copy the proven shader and import configuration from the spikes into the production client tree. No functional changes — this is a verified copy.\n\nSource files:\n- spikes/quaternius-aesthetic/shaders/toon.gdshader\n- spikes/quaternius-aesthetic/shaders/toon_masked.gdshader\n- spikes/quaternius-aesthetic/shaders/outline.gdshader\n\nDestination: client/assets/characters/shaders/\n\nAlso copy skin tone textures: spikes/quaternius-aesthetic/assets/T_Skin_*.png -> client/assets/characters/skin_tones/ (renamed to match convention: pale_cool.png, pale_warm.png, etc.)\n\nproject.godot update: ensure gltf/embedded_image_handling=3 is set under [gltf]. This was the critical finding from the 3dpipeline spike — values 0-2 fail or degrade textures.\n\nAcceptance: shaders present at client/assets/characters/shaders/, skin tones at client/assets/characters/skin_tones/, project.godot setting verified.', 'done', 'high', NULL, 'client', NULL, '2026-03-19 21:20:16', '2026-03-20 08:05:06', NULL, 'e7ed2cff44554e05688845ef8a747679', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXAM8VSN05N556A1X4', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Build character visual compositor', 'Implement the runtime character assembler (CharacterVisual.gd) that takes a CharacterVisualDescriptor and produces a visible 3D character in the scene tree.\n\nThe compositor is a runtime assembler. It selects pre-authored meshes and attaches them. It does NOT scale, deform, or generate anything at runtime.\n\nLocation: client/scripts/rendering/character_visual.gd\n\nSlot architecture:\n- Body: loads seg_*.glb files from bodies/{body_type}/ by convention path\n- Head: loads heads/templates/{head_id}.glb via BoneAttachment3D to Head bone\n- Hair: loads hair/{hair_id}.glb via BoneAttachment3D to Head bone\n- Facial hair: loads facial_hair/{facial_hair_id}.glb via BoneAttachment3D\n- Eyebrows: loads eyebrows/{eyebrow_id}.glb via BoneAttachment3D\n- Clothing: reads clothing/{item_id}/coverage.json, hides covered segments, loads clothing/{item_id}/{body_type}.glb\n- Accessories: loads accessories/{accessory_id}.glb via BoneAttachment3D to slot bone\n\nTorso variant handling: coverage.json torso_variant field selects seg_torso.glb (full) or seg_torso_upper.glb (upper only). Full-coverage garments hide seg_torso entirely.\n\nSkin tone: applies tint_color to body segment materials using skin_tones/{skin_tone_name}.png\n\nTest against: Quaternius starter assets (4 OnlyHead templates + Source tier hairstyles) and placeholder segment meshes from visual team.\n\nPath convention: all paths derived programmatically from descriptor — no lookup tables. The directory structure IS the lookup table.\n\nDecisions: D-159, D-160, D-161, D-162, D-163, D-164\nRef: docs/architecture/character-asset-organization.md Section 6', 'done', 'high', NULL, 'client', NULL, '2026-03-19 21:20:39', '2026-03-20 08:05:07', NULL, '6515a8841a2d555d5d40be0ea1e3121e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZ1E5EE8R0SGS00XHW', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Skeleton and animation library import', 'Extract the shared 65-bone armature and import the Universal Animation Libraries (UAL1 + UAL2) into the production client.\n\nSkeleton:\n- Source: extract armature from a Quaternius Source .blend (Regular_Male_FullBody.blend or equivalent)\n- Output: client/assets/characters/skeleton/armature.glb\n- Requirements: 65-bone hierarchy, no mesh geometry, no animation data, Y-up -Z forward, 1 unit = 1 meter\n\nAnimation libraries:\n- UAL1 (core): client/assets/characters/animations/ual_standard.glb — idle, walk, run, and core animations\n- UAL2 (extended): client/assets/characters/animations/ual_extended.glb — extended animations + female mannequin\n- Requirements: same skeleton hierarchy as armature.glb, bone names match post-January 2026 naming update\n\nValidation: instantiate armature.glb in a test scene, apply AnimationPlayer from ual_standard.glb, confirm idle and walk animations play without joint collapse.\n\nNote: the January 2026 bone naming update in the Quaternius Universal Animation Library changed several bone names. Verify compatibility before committing.\n\nDecision: D-164 (fork Quaternius skeleton, replace body meshes)', 'done', 'high', NULL, 'client', NULL, '2026-03-19 21:21:00', '2026-03-20 08:05:07', NULL, 'a171b89de4578d1278e14e1a5d499f5a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYXPA202KSKKJ15KZ8', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Character creation screen', 'Full character creation UI using the game UI kit and widget system — not a debug/test wrapper. This is the production character creation screen that players use at game start.
Built on the 3D CharacterVisual compositor (#693). Uses the same UI kit, widgets, and interaction patterns as the rest of the game UI.
Features:
- Body type selector (11 types)
- Head template browser (curated library)
- Hair, facial hair, eyebrow pickers with preview
- Skin tone selector (9-tone palette)
- Clothing and accessory slots
- Color customization per recolorable region
- Live 3D preview with camera orbit (frontal default per D-158, full rotation per D-155)
- Keyboard navigation (tab/arrow/enter/esc)
- Randomize button
- Standalone scene entry point (can be launched independently for artist review)
- Feeds CharacterVisualDescriptor into new game flow on confirm
Decisions: D-134 (full customization), D-146 (tile-scale heavy zoom preview), D-155 (editor rotation), D-158 (frontal camera default)
Blocked by: #693 (compositor)', 'backlog', 'high', NULL, 'client', NULL, '2026-03-17 08:21:47', '2026-03-22 14:11:12', NULL, '23b43d43eaab8d1856d4dac033a96106', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRX9WD7DERYC948ZCWG', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Standalone character viewer / creation screen', 'Standalone Godot scene wrapping the 3D CharacterVisual compositor with UI controls for previewing and creating characters. Features: body type selector, head template picker, hair/facial hair/eyebrow pickers, skin tone selector, camera orbit (frontal default per D-158, with rotation per D-155), zoom. Runs as its own scene (F6 or main menu entry) for quick artist review and gameplay character creation. Replaces cancelled #694 (2D character creation screen).', 'cancelled', 'high', NULL, 'client', NULL, '2026-03-22 14:09:57', '2026-03-22 14:11:13', NULL, '239d40709572a74e62a2ae5a5e3f2bf4', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRX7RDX56FZFNK6S94W', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', '3D character compositor (CharacterVisual.gd)', 'Port the spike CharacterVisual.gd to production. Loads body segments onto shared armature, attaches head/hair/facial_hair/eyebrows via BoneAttachment3D, applies skin tone tinting, handles clothing segment coverage. Replaces cancelled #693 (2D sprite compositor). Input: CharacterVisualDescriptor. Output: assembled 3D character node. Ref: spikes/quaternius-aesthetic/scripts/spike/character_visual.gd, docs/architecture/character-asset-organization.md Section 6.', 'cancelled', 'high', NULL, 'client', NULL, '2026-03-22 14:09:54', '2026-03-22 14:11:13', NULL, '6bd9b1161fdc0d1cbbb217e1fe359a82', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYSZ90GPGE5NSRMM88', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Body type meshes from Source .blends (all 11 types, 21 segments each)', 'Author all 11 body type meshes from Quaternius Source .blend files, segmented into 18 GLB files per body type.\n\nBody type breakdown (from docs/architecture/character-asset-organization.md):\n- average_m: Regular_Male_FullBody.blend — fork + segment\n- average_f: Regular_Female_FullBody.blend — fork + segment\n- muscular_m: Superhero_Male_FullBody.blend — segment as-is\n- muscular_f: Superhero_Female_FullBody.blend — segment as-is\n- teen_m: Teen_Male_FullBody.blend — segment as-is or minor tweaks\n- teen_f: Teen_Female_FullBody.blend — segment as-is or minor tweaks\n- thin_m: Regular_Male as base — fork + slim down + segment\n- thin_f: Regular_Female as base — fork + slim down + segment\n- heavy_m: Regular_Male as base — fork + widen + segment\n- heavy_f: Regular_Female as base — fork + widen + segment\n- child: Teen as base — fork to gender-neutral + scale down + segment\n\n21 segments per body type (17 base + 2 swappable torso + 2 face detail):\nseg_head, seg_neck, seg_torso, seg_torso_upper, seg_arm_upper_l, seg_arm_upper_r,\nseg_arm_lower_l, seg_arm_lower_r, seg_hand_l, seg_hand_r, seg_leg_upper_l,\nseg_leg_upper_r, seg_leg_lower_l, seg_leg_lower_r, seg_foot_l, seg_foot_r,\nseg_eyes, seg_eyebrows\n\nOutput: client/assets/characters/bodies/{body_type}/seg_{segment}.glb\n\nRequirements:\n- 1-ring vertex overlap at all segment boundaries (eliminates seams during animation)\n- Foot segments extend past ankle bone (1-ring overlap with lower leg segment)\n- No animation data embedded in segment GLBs\n- Shared skeleton hierarchy (same bone names as armature.glb)\n- All meshes start fresh from Source .blends — no spike mesh output carries over\n\nDecisions: D-159 (11 body types), D-160 (21 segments per body type), D-164 (Source .blends as starting point)', 'done', 'high', NULL, 'visual', NULL, '2026-03-19 21:21:13', '2026-03-22 14:26:53', NULL, '2277be42450f95b70951dffb2b7a58a8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXM5A3E0ZCX19T0HP4', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Hairstyle import from Source tier', 'Process the Source tier ''Rigged to Head Bone'' hairstyle .blends for production use. ~18 hairstyles are nearly production-ready.\n\nSource files: docs/assets/downloads/Universal Base Characters[Source]/ (hair .blends)\n\nHairstyle list from Source tier:\nFemale: Bob, Buns, BuzzedFemale, Long, LongDreads, Ponytail_2 (+ Teen variants)\nMale: Balding, Beard, Buzzed, Dreads, Mohawk, Moustache, MuttonChops, Ponytail, SimpleParted, SlickBack (+ Teen variants)\nEyebrows: Female, Regular, Teen, Thick\n\nNaming convention (Quaternius name -> file key):\n- Bob -> bob, Buns -> buns, Long -> long, Ponytail -> ponytail\n- SimpleParted -> simple_parted, SlickBack -> slick_back\n- BuzzedFemale -> buzzed_female, LongDreads -> long_dreads\n- Beard -> beard, Moustache -> moustache, MuttonChops -> mutton_chops\n\nOutput per hairstyle: {key}.glb + {key}_mask.png\n- Hair: client/assets/characters/hair/{key}.glb + {key}_mask.png\n- Facial hair (beard, moustache, mutton_chops): client/assets/characters/facial_hair/{key}.glb + {key}_mask.png\n- Eyebrows: client/assets/characters/eyebrows/eyebrows_{key}.glb\n\nRequirements:\n- BoneAttachment3D-compatible: attached to Head bone, single-bone follow\n- Mask PNG: greyscale, white = tintable region (for hair_tint shader uniform)\n- No body-type-specific variants needed — hair attaches to Head bone, body type irrelevant\n\nInclude bald.glb in hair/ as explicit ''no hair'' slot placeholder.\n\nDecision: D-164 (Source tier as foundation)', 'done', 'high', NULL, 'visual', NULL, '2026-03-19 22:03:36', '2026-03-22 14:26:54', NULL, '01fcac6b3de558a94dfee5a4b505e2cc', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXPPG8PHS0X0M025KG', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Head template library — process OnlyHead .blends', 'Process the 4 Quaternius OnlyHead .blends as the starter set for the curated head template library.\n\nSource files (Quaternius Source tier):\n- Regular_Female_OnlyHead.blend -> head_001.glb\n- Regular_Male_OnlyHead.blend -> head_002.glb\n- Teen_Female_OnlyHead.blend -> head_003.glb\n- Teen_Male_OnlyHead.blend -> head_004.glb\n\nOutput: client/assets/characters/heads/templates/\n head_001.glb + head_001_mask.png\n head_002.glb + head_002_mask.png\n head_003.glb + head_003_mask.png\n head_004.glb + head_004_mask.png\n\nNumeric ID format: head_NNN (zero-padded 3 digits). These are catalogue entries, not descriptive names — heads are unique per character.\n\nRequirements:\n- BoneAttachment3D-compatible: attaches to Head bone, no skinning required\n- Correct scale relative to body at gameplay zoom (verified against armature.glb)\n- Mask PNG: greyscale, white = tintable skin tone region\n- Skin tone matching: head skin tone must be matchable to body skin tone via toon shader tinting\n\nNote: Trellis-generated heads expand this library over time via the offline pipeline (see D-163). This ticket is only the 4 Quaternius starter heads.\n\nDecisions: D-161 (head always separate), D-163 (Trellis offline pipeline for expansion)', 'done', 'high', NULL, 'visual', NULL, '2026-03-19 22:03:48', '2026-03-22 14:26:54', NULL, 'fb2189419c671c356ab2428a2cb2c231', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRY6TPACQSYVFGDEMPR', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Scar overlay system — per-segment scar meshes or textures', 'Visual overlay system for scars on character bodies. Scars are per-segment (leveraging the 18-segment body architecture for placement). Each scar is a visual modification to a specific body segment — either a decal/texture overlay or a small mesh overlay attached to the segment. Requirements: scars placed per body segment, multiple scars per character, persist across clothing changes (visible on exposed segments), hidden under clothing via coverage system, recolorable via toon shader for skin tone matching. Depends on compositor (#704) and body segments (#707) — both done. Architecture uses per-segment addressability from D-160.', 'backlog', 'medium', NULL, 'visual', NULL, '2026-03-22 14:30:36', '2026-03-22 14:30:36', NULL, 'bfa01e8fa64832206ee067fc8fa05b5d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWS9B34T2HPJCYMGK0', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Tattoo overlay system — per-segment tattoo textures', 'Visual overlay system for tattoos on character bodies. Tattoos are per-segment texture overlays with their own recolor masks for ink color variation. Requirements: tattoos placed per body segment, multiple tattoos per character, persist across clothing changes (visible on exposed segments), hidden under clothing via coverage system, recolorable via multi-region mask for multi-color tattoos. May share implementation approach with scar system (#715). Architecture uses per-segment addressability from D-160.', 'backlog', 'medium', NULL, 'visual', NULL, '2026-03-22 14:30:42', '2026-03-22 14:30:42', NULL, '7b84afe1a6b478561d9f987e9a38cb2d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWEDR71D90K594P7DG', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Visible injury overlay system — per-segment injury visualization', 'Visual overlay system for injuries on character bodies. Injuries are per-segment visual states showing damage (bruises, cuts, burns) and healing progression. Requirements: injuries placed per body segment, severity/type affects visual appearance, healing progression over time changes the overlay, hidden under clothing via coverage system, interacts with amputation system (injury on a removed limb is moot). Depends on compositor (#704) and body segments (#707) — both done. Architecture uses per-segment addressability from D-160 and limb removal capability.', 'backlog', 'medium', NULL, 'visual', NULL, '2026-03-22 14:30:49', '2026-03-22 14:30:49', NULL, '2da255f05c09700ef99228359a7a3a41', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWRWX6W805ESYZ2DC8', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Character creation screen wireframe', 'UI wireframe for the character creation screen, updated for slot-based architecture per the Sprint 28 planning workshop outcome and asset organization proposal.\n\nContext: character creation uses a slot system (body type, head, hair, facial hair, eyebrows, clothing slots, accessory slots). The wireframe must reflect this architecture and the 11 body type selector.\n\nDecisions: D-159 (11 body types), D-160 (21 segments per body type), D-162 (clothing pre-baked per body type), D-163 (Trellis head template library), D-164 (Source tier as starting point)\n\nDeliverables:\n- Wireframe for character creation screen covering all slot panels\n- Body type selector (11 options: thin_m/f, average_m/f, muscular_m/f, teen_m/f, heavy_m/f, child)\n- Head template picker (scrollable grid, starts with 4 Quaternius OnlyHead starters)\n- Hair slot, facial hair slot, eyebrow slot panels\n- Clothing slot panels (torso, legs, feet, hands) — category filter (coveralls, jacket, shirt, pants, boots, gloves)\n- Accessory slot panels (hat, goggles, mask, backpack, belt, held_l, held_r)\n- Skin tone selector (9 tones)\n- Camera angle toggle (editor frontal -5deg, gameplay dramatic -30deg per D-158)\n\nTool: Frame0 or equivalent wireframing tool\nOutput: wireframe PNG(s) committed to docs/design/character-creation-wireframe/', 'done', 'high', NULL, 'planning', NULL, '2026-03-17 08:20:27', '2026-03-22 15:27:02', NULL, '7822baea7307abf593b4b25cc720b74b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRY5J5AWB356FT9WK0M', 'story', NULL, 'Accessory slot filtering — _get_accessory_ids_for_slot() ignores slot param', '_get_accessory_ids_for_slot(slot) accepts a slot parameter but returns all accessories unfiltered. Every accessory slot shows the full catalogue. Should filter by slot-appropriate prefix (hat_*, goggles_*, etc.) matching the clothing tab''s approach. Currently invisible with placeholder assets but will be wrong when real accessories are added.', 'backlog', 'low', 'client', NULL, NULL, '2026-03-22 16:33:07', '2026-03-22 16:33:09', NULL, 'aae4c228e51f9ddc2ea7dfa01ade27a4', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYX8B3TR0VJPN5XFB0', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Surface Deform batch pipeline (Blender tooling)', 'Build the Blender batch pipeline that takes one reference clothing mesh (authored on average_m) and produces 11 fitted variants via Surface Deform.\n\nOutput location: tooling/blender/surface_deform_batch.py (not in client tree)\n\nScript interface:\n- Input: path to reference GLB (authored on average_m)\n- Input: path to the 11 body type meshes (Surface Deform targets)\n- Output: directory to write fitted variants\n\nOutput per clothing item:\n <item_id>/\n thin_m.glb, thin_f.glb\n average_m.glb (copy of reference)\n average_f.glb, muscular_m.glb, muscular_f.glb\n teen_m.glb, teen_f.glb\n heavy_m.glb, heavy_f.glb\n child.glb\n\nRequirements:\n- Headless-capable (Blender --background mode)\n- average_m.glb variant is a direct copy of reference, not a new fit\n- Surface Deform bind must work in headless context (MEDIUM reliability per spike — document any workarounds needed)\n- UV layout preserved through deform (mask sidecar applies identically to all variants)\n\nValidation: run against one test clothing item across all 11 body types. Verify visual quality at gameplay zoom especially at extreme types (heavy_m, heavy_f, child). This answers the open question from the quaternius-aesthetic spike VERDICT.\n\nRef: spikes/quaternius-aesthetic/VERDICT.md (Surface Deform PARTIALLY VALIDATED)\nRef: docs/architecture/character-asset-organization.md Section 5', 'done', 'high', NULL, 'visual', NULL, '2026-03-19 22:04:00', '2026-03-22 16:39:46', NULL, '2247982d3654842a230184a6d7898557', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRW7C74XT5GVS80BHX8', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Initial clothing set — reference meshes and batch fit', 'Author the initial v0.2 clothing set as reference meshes on average_m, then run the Surface Deform batch pipeline to produce all 11 body-type variants.\n\nMinimum v0.2 clothing catalogue (to be confirmed with project lead — scope decision per open question in VERDICT.md):\n- coveralls_basic (full-body work suit)\n- jacket_utility (upper body outerwear)\n- pants_cargo (lower body)\n- shirt_henley (upper body inner)\n- boots_work (foot slot)\n\nPer item, author on average_m:\n- reference.glb — the authored mesh\n- reference_mask.png — recolor mask (greyscale, white = tintable, black = preserve)\n- coverage.json — segment hides array + torso_variant\n\nThen run tooling/blender/surface_deform_batch.py to produce 11 variants.\n\nFinal output per item in client/assets/characters/clothing/{item_id}/:\n reference.glb, reference_mask.png, coverage.json\n thin_m.glb, thin_f.glb, average_m.glb, average_f.glb\n muscular_m.glb, muscular_f.glb, teen_m.glb, teen_f.glb\n heavy_m.glb, heavy_f.glb, child.glb\n\ncoverage.json format:\n { "hides": ["torso", "arm_upper_l", ...], "torso_variant": "full" }\n\nAcceptance: compositor can load all variants and apply coverage correctly on all 11 body types.\n\nDecision: D-162 (clothing pre-baked per body type via Surface Deform)', 'done', 'high', NULL, 'visual', NULL, '2026-03-19 22:04:11', '2026-03-22 16:39:47', NULL, 'ca1f8d68d6ebc02a54d0ecf24f8dd7c8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZAR2KCWAGWNMP36E8', 'story', '06FB0TNSRZDA414TMQBXY3BHJ0', 'Character editor Godot tool implementation', 'Standalone Godot tool (EditorPlugin or standalone scene) that uses the compositor to preview and edit character appearances. Built from the wireframe (#685).\n\nFeatures:\n- Body type selector: 11 options (ThinM, ThinF, AverageM, AverageF, MuscularM, MuscularF, TeenM, TeenF, HeavyM, HeavyF, Child)\n- Head template picker: scrollable grid of available head GLBs in heads/templates/\n- Slot panels: hair, facial hair, eyebrows (each showing available options)\n- Clothing slot panels by category (coveralls, jacket, shirt, pants, boots, gloves, vest, dress, uniform)\n- Accessory slot panels (hat, goggles, mask, backpack, belt, held_l, held_r)\n- Skin tone selector: 9 swatches\n- Tint color pickers per slot (hair_tint, facial_hair_tint, clothing tints, accessory tints)\n- Camera angle toggle: three presets per D-148/D-158 — frontal (-5°, editor default), dramatic/low-angle (-30°, gameplay default), top-down (0°, overhead)\n- Live preview via compositor — all selections drive a live CharacterVisualDescriptor\n\nAcceptance: can select all slots and see changes reflected live in the 3D viewport.\n\nDecisions: D-158 (camera angles), D-159 (11 body types)', 'done', 'high', NULL, 'client', NULL, '2026-03-19 21:20:50', '2026-03-22 16:56:41', NULL, 'f8f9b2535c1c59d22e1e3e1e3d107ba5', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYS8J5T5CV3EKNPJMR', 'task', NULL, 'Adopt auto-sizing label pattern for dynamic text', 'Evaluate GodotFontAutoSizeLabels (https://github.com/LuisEscorza/GodotFontAutoSizeLabels) for handling text that must fit a fixed container. We will hit this with: NPC name labels, item names in grids, clothing/hair button labels, dialogue boxes, any server-generated text rendered in fixed UI elements. Adopt the pattern early (either this plugin or our own implementation) before overflow bugs accumulate.', 'backlog', 'low', NULL, 'client', NULL, '2026-03-23 17:42:32', '2026-03-23 17:42:32', NULL, '49f7b5c5f6dd51d05d7893d019eaea80', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXJ3WZ2AQMWBNB4D2W', 'task', NULL, 'Install Godot Icons Fonts addon for UI development', 'Godot Icons Fonts (https://github.com/rakugoteam/Godot-Icons-Fonts) — searchable icon font library (FontAwesome, Material Icons, etc.) directly in the Godot editor. MIT. Saves time hunting for individual icon assets during UI development. Install from asset library. Use for: HUD icons, status indicators, navigation symbols, button icons, inventory categories.', 'backlog', 'low', NULL, 'client', NULL, '2026-03-23 18:19:22', '2026-03-23 18:19:22', NULL, 'f9e8f5995f427a32d3b2b4ba130b0c9a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYKBBN824V9KB9NEN0', 'task', NULL, 'Add resolution parameter to screenshot test config', 'Extend the character creator screenshot test config JSON with a resolution field. The system sets viewport size before capturing, enabling automated visual regression across common resolutions (1080p, 1440p, 4K, ultrawide, Steam Deck 800p). Batch script iterates the manifest body types × resolutions for full coverage. No plugin needed — just OS.window_size or viewport size override. Inspired by Project Resolution plugin (https://github.com/Danim3D/Godot-4-Plugin-Project-Resolution) but built into our existing test infrastructure.', 'backlog', 'medium', NULL, 'client', NULL, '2026-03-23 18:27:51', '2026-03-23 18:27:51', NULL, '67eea197bc010d67fc3ccd9ecfc937aa', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZGD6VYZM7BT6PC5WM', 'task', NULL, 'First-launch install wizard for distribution', 'Evaluate Godot4xInstallWizard (https://github.com/untamedlabs/Godot4xInstallWizard) for first-launch experience: EULA acceptance, initial settings (resolution, audio, controls), install path selection. Needed when packaging for Steam or standalone distribution. Low priority until release preparation begins.', 'backlog', 'low', NULL, 'client', NULL, '2026-03-23 18:31:35', '2026-03-23 18:31:35', NULL, '3d3a6e12b43f377621678ca05caf1fd6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXBTTPTBFJN0DGX2YC', 'task', NULL, 'License and attribution screen in settings menu', 'Add a licenses/credits screen accessible from the main menu settings. Must display all third-party licenses: Quaternius (CC0), Godot engine, any adopted plugins (MIT), font licenses, audio assets. Pattern reference: godot-licensing (https://github.com/nathanfranke/godot-licensing) — auto-collects license info. We don''t need the plugin but need the pattern: scan for LICENSE files, Godot''s built-in Engine.get_license_info(), and our own attribution list. Legal requirement for distribution.', 'backlog', 'medium', NULL, 'client', NULL, '2026-03-23 18:33:04', '2026-03-23 18:33:04', NULL, '2c9dc4a31389829d2c21c19cb2f363cd', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRY35YGQ1PGBXE4B7KM', 'task', NULL, 'Konami code easter egg', 'Up up down down left right left right B A. Because reasons. https://github.com/aravikusu/konamicode — reference implementation. Trigger: unlock debug panel in release builds, or something fun.', 'backlog', 'low', NULL, 'client', NULL, '2026-03-23 18:38:37', '2026-03-23 18:38:37', NULL, 'caa7fadefe4ac8273733a8bfc34ceb6d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWJAKQQ33BNCFZGV3W', 'task', NULL, 'Minimap rendering from server exploration data', 'The minimap needs to render from server-sent exploration and visibility data. No existing Godot minimap plugin fits because our information boundary is server-authoritative. Architecture: server sends explored tile states (unexplored/explored/visible) as part of ObserverSnapshot. Client renders a top-down 2D minimap from this data — fog for unexplored, dim for explored-not-visible, lit for currently visible. Player position centered. Needs a dedicated data stream or section in the snapshot for minimap tile states. Reference: the existing 2D fog_state.gd has the rendering patterns (texture from tile data), but the 3D minimap may use a separate overhead Camera3D in a SubViewport instead.', 'backlog', 'medium', NULL, 'client', NULL, '2026-03-23 18:48:16', '2026-03-23 18:48:16', NULL, 'b533600c777e8b402b7fdd2a7d3c9cf0', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXGNK7WHD9DEGPY4BC', 'task', NULL, 'Nested tooltip system for information layering', 'Implement nested/recursive tooltips for the information-dense UI. Reference: Nested-Tooltips (https://github.com/duzychri/Nested-Tooltips). Pattern: hover over text → tooltip appears → hover over a keyword in the tooltip → nested tooltip explains that keyword. Use cases: NPC names (→ disposition, role), items (→ stats → stat modifier explanations), zone names (→ zone type, danger level), factions (→ relationship, territory). This is core to the asymmetric information design — tooltips reveal what your character KNOWS, with depth proportional to familiarity. Study the plugin for the recursive hover detection pattern, implement with our diegetic UI aesthetic.', 'backlog', 'medium', NULL, 'client', NULL, '2026-03-23 18:49:04', '2026-03-23 18:49:04', NULL, '1ff6d346334c84f1e15ccf130a4a1ef0', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRW10TE82JMV5KJWP98', 'task', NULL, '3D speech bubbles for NPC dialogue in world', '3D speech bubbles floating above NPC heads for overheard dialogue, conversation snippets, and NPC chatter. Reference: speechbubble3d (https://github.com/tamort/speechbubble3d). The server sends dialogue events in ObserverSnapshot — the client renders them as 3D world-space bubbles above the speaking NPC. Information boundary applies: only NPCs within hearing range get bubbles, content reflects what the player character can hear. Different bubble styles: direct conversation (solid), overheard (faded/partial), internal monologue (thought bubble, player character only). Must face camera (billboard) and scale appropriately at gameplay zoom.', 'cancelled', 'medium', NULL, 'client', NULL, '2026-03-23 18:51:14', '2026-03-23 18:51:14', NULL, 'fe60d82d3caee73683d71e448b3ea051', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXY24QTYKWPERE52X4', 'epic', NULL, 'Settings and input system — keybinds, save browser, cursors, controller', 'Unified design and implementation pass for player-facing settings and input: (a) Key binding remapper with save/load. (b) Save browser with thumbnails and metadata. (c) Context-sensitive cursors (examine, talk, use, attack, walk). (d) Controller/gamepad support with radial menus. Design all four together in Frame0 for visual consistency, then implement as a cohesive system. These share UI patterns (settings panels, modal dialogs, input feedback) and should not be built independently.', 'backlog', 'medium', NULL, 'client', NULL, '2026-03-23 18:53:39', '2026-03-23 18:53:39', NULL, 'ae389adecea95af14d80991b97b5709b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZ3GAN6QKTNJWVKPDG', 'task', NULL, 'Install Poly Haven downloader addon', 'Godot Poly Haven Downloader (https://github.com/P3ntest/godot-polyhaven-downloader) — browse and download CC0 models, textures, and HDRIs from Poly Haven directly in the Godot editor. Removes friction from asset sourcing. Install when environment art work begins.', 'backlog', 'low', NULL, 'client', NULL, '2026-03-23 19:08:40', '2026-03-23 19:08:40', NULL, 'bf0c0cac580957ac16fb94478babea4e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRY9W1MWNKMS803195C', 'task', NULL, 'Selection glow effect for interactable objects', 'Soft glow border on hovered/selected objects and characters. Reference: GlowBorderEffect (https://github.com/Magodra/GlowBorderEffect). Distinct from our inverted hull outline (permanent character silhouette) — this is a context-sensitive highlight: hover over an NPC to see a soft glow indicating they''re interactable, selected items glow in inventory, quest-relevant objects pulse. Could be a simple shader (stencil + blur) or adopt the plugin. Evaluate when interaction system is built.', 'backlog', 'low', NULL, 'client', NULL, '2026-03-23 19:28:45', '2026-03-23 19:28:45', NULL, '9119713bf272cd6e1aa5497219b55a3d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXERQG6GFS85FRJ5HW', 'epic', '06FB0TNSRXBP82ANSVAFMACCHW', 'Body catalog completion — 258 remaining systems', 'Author body catalogs for all remaining star systems using the /atlas author-system pipeline. 43 systems done (hops 0-3 + Schuilhoek), 258 remaining across 5 corridor tracks. Each system: wiki analysis, astrophysical skeleton, Miri naming, Paula+Ozzie review, integrity check, commit. Parallelizable by corridor.', 'done', 'high', NULL, 'copy', NULL, '2026-03-25 13:22:02', '2026-03-25 13:22:02', NULL, '089252c3d85e148594e8501746aa49d6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZE4CSS8YR0FCEXMK8', 'initiative', NULL, 'Development Cascade — 6-phase first-things-first build order', 'The Settled Reach builds core system to core system in strict cascade order. Each phase completes before the next begins. No scoping negotiations, no v0.2 target. Established 2026-03-24 during world generation workshop. See CLAUDE.md for the full cascade table.', 'backlog', 'critical', NULL, NULL, NULL, '2026-03-25 13:28:54', '2026-03-25 13:28:54', NULL, '4724eb031a18eb64d35e153d078f716a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXX29H5X0EB0F0GBW4', 'epic', '06FB0TNSRZE4CSS8YR0FCEXMK8', 'Phase 5: Player control & in-world rendering — character, walls/stairs/doors, lighting on generated tiles', 'Deliverable: Player viewport with final-version assets (boring repeating but correct). 2-floor test map streamed from server to validate dynamic mapping protocols. Character model rendering + basic walls, stairs, doors (fully materialized models). Lighting and visual polish pass. Depends on Phases 1-3 (world data exists to stream).', 'backlog', 'high', NULL, NULL, NULL, '2026-03-25 13:29:48', '2026-03-25 13:29:48', NULL, '05d2ccd1f420f543ed72bf01e8a46f25', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZGGNZMAP8GDMXSCMW', 'epic', '06FB0TNSRZE4CSS8YR0FCEXMK8', 'Phase 6: Detail coloring — room-level content, cultural architecture', 'ONLY starts when the world is walkable and the question is ''what is here''. Room-level detail, furniture, signage, cultural architectural expression, environmental storytelling props. NO NPC layer — life comes after the cascade. Depends on Phase 5 (walkable world exists).', 'backlog', 'medium', NULL, NULL, NULL, '2026-03-25 13:30:11', '2026-03-25 13:30:11', NULL, 'd4471361bad4fdb658ba5663742348bb', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRZRZH8DF975QG5HMG4', 'story', '06FB0TNSRXERQG6GFS85FRJ5HW', 'Track A: North Reach body catalogs (43 systems)', 'Author body catalogs for 43 north_reach systems (hops 4-12). Commonwealth naming register. Process in hop order using /atlas author-system pipeline.', 'done', 'high', NULL, 'copy', NULL, '2026-03-25 13:22:18', '2026-03-25 20:28:55', NULL, '360b654850079819839b0902cd1a4d1c', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRW2WFGWJ9HBG9D532M', 'story', '06FB0TNSRXERQG6GFS85FRJ5HW', 'Track B: South Reach body catalogs (47 systems)', 'Author body catalogs for 47 south_reach systems (hops 4-10). Iberian/Latin American naming register. Process in hop order using /atlas author-system pipeline.', 'done', 'high', NULL, 'copy', NULL, '2026-03-25 13:22:29', '2026-03-26 09:27:36', NULL, '2668b0d365c661c59e05fb03a2a3f844', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYW41VB4NCY55HQ750', 'story', '06FB0TNSRXERQG6GFS85FRJ5HW', 'Track C: East Reach body catalogs (47 systems)', 'Author body catalogs for 47 east_reach systems (hops 4-10). East Asian naming register (Chinese/Japanese/Korean). Process in hop order using /atlas author-system pipeline.', 'done', 'high', NULL, 'copy', NULL, '2026-03-25 13:22:39', '2026-03-29 15:42:00', NULL, '97b87c7236bd090198498e74cb4eef3b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);