From 3b3ef392c5749bfc2a880f10a50b2a667b91df67 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sat, 25 Jul 2026 20:11:55 +0200 Subject: [PATCH 1/9] =?UTF-8?q?chore(meta):=20pql=20changelog=20=E2=80=94?= =?UTF-8?q?=20visual=20cluster=20re-scoped=20(T-1050/T-1052=20refined+acti?= =?UTF-8?q?ve,=20T-1051=20deferred=20blocked-by=20T-1049)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .pql/changelog/ticket_deps/2026-07.sql | 1 + .pql/changelog/ticket_history/2026-07.sql | 207 +++++++++ .pql/changelog/tickets/2026-07.sql | 497 ++++++++++++++++++++++ 3 files changed, 705 insertions(+) diff --git a/.pql/changelog/ticket_deps/2026-07.sql b/.pql/changelog/ticket_deps/2026-07.sql index b27050445..4e752fd98 100644 --- a/.pql/changelog/ticket_deps/2026-07.sql +++ b/.pql/changelog/ticket_deps/2026-07.sql @@ -26,3 +26,4 @@ INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updat INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FS5XQW73B1P8WKR2BCZBFXZW', '06FS5XW0ZMPVYA2CPBBTP88TQC', '2026-07-24 07:12:41.953', '2026-07-24 07:12:41.953', NULL, '900579ff4a0f777db3fee1b48be50ad2', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FS5XXY5GNCQEXYAYQEQD0CHM', '06FS5XZF4A978WS1J66Q2JBK8C', '2026-07-24 07:12:42.876', '2026-07-24 07:12:42.876', NULL, '6215bc0bc9966f904af9212895fb2a93', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FS5XT949C8D2JZRWDX2CRZN8', '06FRXKFEJMDEPQ0PZ5Q862DHKG', '2026-07-24 07:12:43.096', '2026-07-24 07:12:43.096', NULL, '1813bd9c252aa447c9d7ad88c604d6cd', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPE8FR7TGEM7X6QVPV4M', '06FBPTXPPHC9FZ4T0GNKR615PR', '2026-07-25 18:11:40.311', '2026-07-25 18:11:40.311', NULL, '10922d908dd3b4fb42c989bb75391935', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); diff --git a/.pql/changelog/ticket_history/2026-07.sql b/.pql/changelog/ticket_history/2026-07.sql index 9ded777e5..ef90fa3e7 100644 --- a/.pql/changelog/ticket_history/2026-07.sql +++ b/.pql/changelog/ticket_history/2026-07.sql @@ -2115,3 +2115,210 @@ Batch-5 Si pass correction (2026-07-25): the 2026-06-16 refinement block above i INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FRJD6EKPBQE08NA8VHP692W8', 'status', 'backlog', 'in_progress', NULL, '2026-07-25 18:04:06', '2026-07-25 18:04:06.736', '2026-07-25 18:04:06.736', NULL, '581321f1ce2faaef8636c8b2f5182716', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSM3MQ5GFSPKKFJF6GGZN8CM', 'status', 'backlog', 'ready', NULL, '2026-07-25 18:04:07', '2026-07-25 18:04:07.273', '2026-07-25 18:04:07.273', NULL, '56f337047d088a7547c30ce3f4186497', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FRJD6EKPBQE08NA8VHP692W8', 'assigned_to', NULL, 'hoshe', NULL, '2026-07-25 18:04:09', '2026-07-25 18:04:09.374', '2026-07-25 18:04:09.374', NULL, '91edbb31a5c8128989e1fb19f2eedc70', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPJ910NNR5T2R9F9NWQR', 'description', '(description follows in first append) + +--- +Refinement (2026-06-16, /whats-next Si pass) — FULL scope (user decision). Two deliverables + conventions. + +(1) CATALOG: instantiate the 5 docs/assets/visual/*.md files (sprites, furniture, tilesets, icons, effects) with per-category status tables. Template: docs/assets/_templates/sprite.md (SPR/FUR/TILE/ICO/FX prefixes); status vocab planned/in-progress/placeholder/final (docs/assets/README.md). Audio side (docs/assets/audio/README.md) is the working model. + +(2) glb-gen PRODUCTION PATH: SKILL.md:115 names client/assets/models/ which does NOT exist. Create it + furniture/ + props/ subdirs (mirror the spike) + .gitkeep. Define client/assets/models/manifest.json schema MIRRORING the character manifest (docs/architecture/character-asset-organization.md) — per-asset entries: name, category, source-model path, subdirectory. Promotion convention per D-241 (promotion = repo-canonical). Update SKILL.md to point at the new path + reference the manifest. + +(3) CONVENTIONS (author NOW, full scope): door state-frame suffixes (e.g. _open / _closed), Godot TileSet resource organization (/.tres), multi-tile footprint metadata, and renderer/textures/ naming keyed to D-235 tokens (current renderer/README.md:80-95 covers sprites only: __.png). Author these in docs/assets/visual/conventions.md (single home) and cross-link from the SKILL. + +OVERLAP: T-1053 (rewrite araminta briefing) softly depends on this catalog — note the edge. T-1049''s per-class stories will reference these conventions — do T-1050 first / concurrently so T-1049 can cite them. + +--- +HELD 2026-06-16 (separated from the cascade batch; needs re-scoping before resuming). +The asset pipeline this ticket assumes is sprite-centric (docs/assets/visual/sprites.md +"Entities", _templates/sprite.md, SPR/FUR/TILE/ICO/FX prefixes, the /sprite-gen +render-3D->2D-sprites step). Direction correction: we are NOT producing sprites — Trellis +produces 3D objects (.glb) displayed in-world; the only flattened 2D content is TEXTURES. +Re-scope this ticket around a 3D-model + texture catalog (not a sprite catalog) before +activating. See the sprite-vs-3D root-cause note (this session). + +Batch-5 Si pass correction (2026-07-25): the 2026-06-16 refinement block above is INVALIDATED by the HELD note that follows it — the sprite-centric scope (SPR/FUR prefixes, sprite catalog, /sprite-gen) contradicts the recorded direction (Trellis produces 3D .glb models; only flattened 2D content is textures). Do not implement as written. A re-scope pass (3D-model + texture catalog, manifest mirroring character-asset-organization, D-241 promotion, D-235-keyed texture naming) is running as part of batch 5; T-1052 (style bible) and T-1051 (title-only) get refinement passes in the same sweep. Sequencing per T-1049''s own note stands: this ticket''s conventions land first, T-1049/T-1053 consume them.', '(description follows in first append) + +--- +Refinement (2026-06-16, /whats-next Si pass) — FULL scope (user decision). Two deliverables + conventions. + +(1) CATALOG: instantiate the 5 docs/assets/visual/*.md files (sprites, furniture, tilesets, icons, effects) with per-category status tables. Template: docs/assets/_templates/sprite.md (SPR/FUR/TILE/ICO/FX prefixes); status vocab planned/in-progress/placeholder/final (docs/assets/README.md). Audio side (docs/assets/audio/README.md) is the working model. + +(2) glb-gen PRODUCTION PATH: SKILL.md:115 names client/assets/models/ which does NOT exist. Create it + furniture/ + props/ subdirs (mirror the spike) + .gitkeep. Define client/assets/models/manifest.json schema MIRRORING the character manifest (docs/architecture/character-asset-organization.md) — per-asset entries: name, category, source-model path, subdirectory. Promotion convention per D-241 (promotion = repo-canonical). Update SKILL.md to point at the new path + reference the manifest. + +(3) CONVENTIONS (author NOW, full scope): door state-frame suffixes (e.g. _open / _closed), Godot TileSet resource organization (/.tres), multi-tile footprint metadata, and renderer/textures/ naming keyed to D-235 tokens (current renderer/README.md:80-95 covers sprites only: __.png). Author these in docs/assets/visual/conventions.md (single home) and cross-link from the SKILL. + +OVERLAP: T-1053 (rewrite araminta briefing) softly depends on this catalog — note the edge. T-1049''s per-class stories will reference these conventions — do T-1050 first / concurrently so T-1049 can cite them. + +--- +HELD 2026-06-16 (separated from the cascade batch; needs re-scoping before resuming). +The asset pipeline this ticket assumes is sprite-centric (docs/assets/visual/sprites.md +"Entities", _templates/sprite.md, SPR/FUR/TILE/ICO/FX prefixes, the /sprite-gen +render-3D->2D-sprites step). Direction correction: we are NOT producing sprites — Trellis +produces 3D objects (.glb) displayed in-world; the only flattened 2D content is TEXTURES. +Re-scope this ticket around a 3D-model + texture catalog (not a sprite catalog) before +activating. See the sprite-vs-3D root-cause note (this session). + +Batch-5 Si pass correction (2026-07-25): the 2026-06-16 refinement block above is INVALIDATED by the HELD note that follows it — the sprite-centric scope (SPR/FUR prefixes, sprite catalog, /sprite-gen) contradicts the recorded direction (Trellis produces 3D .glb models; only flattened 2D content is textures). Do not implement as written. A re-scope pass (3D-model + texture catalog, manifest mirroring character-asset-organization, D-241 promotion, D-235-keyed texture naming) is running as part of batch 5; T-1052 (style bible) and T-1051 (title-only) get refinement passes in the same sweep. Sequencing per T-1049''s own note stands: this ticket''s conventions land first, T-1049/T-1053 consume them. + +Refinement (batch-5 Si pass, 2026-07-25) — SUPERSEDES the 2026-06-16 block above (sprite-centric, invalidated by D-244). READY. + +CONTEXT: D-244 (confirmed, 2026-06-16) already re-scoped the catalog structure and repointed +renderer/README.md, sprite-gen SKILL.md, docs/assets/README.md, docs/assets/visual/{README,palette}.md, +and docs/assets/_templates/sprite.md (marked legacy) at the 3D-model reality. This ticket is now +narrower than its original draft: instantiate what D-244 named but did not fill in, plus author +conventions.md (net-new — no D-244 amendment touched it). + +DELIVERABLE (a) — Category catalog files, per docs/assets/README.md''s already-updated structure: + - docs/assets/visual/models.md — 3D objects/props (.glb via /glb-gen). Row template TBD (see + conventions.md below) since _templates/sprite.md is explicitly legacy/retired for this use. + - docs/assets/visual/textures.md — PBR surface maps (BaseColor/Normal/Roughness) for 3D models. + - docs/assets/visual/artwork.md — flat 2D art (paintings/flags/billboards/signage) via /sprite-gen''s + repurposed 2D-artwork role. + - docs/assets/visual/icons.md — UI icons (13 SVGs already exist in client/assets/icons/ — this file + should reflect that as-built inventory, not start at zero). + - docs/assets/visual/effects.md — shaders/particles/overlays. + Each file follows docs/assets/audio/README.md''s model: per-category status table (planned/in-progress/ + placeholder/final per docs/assets/README.md''s vocab), current counts. Update docs/assets/visual/README.md''s + category table counts once files exist (currently all show 0, which will stay accurate at initial creation + — do not invent placeholder rows). + ACCEPTANCE: 5 files exist, cross-linked from visual/README.md (already has the links — verify they resolve), + each with a real (possibly empty) status table matching the audio/README.md pattern. + +DELIVERABLE (b) — glb-gen production path: + - Create client/assets/models/ with furniture/ and props/ subdirectories (mirroring the proven + spike layout at spikes/3dpipeline/models/{furniture,props}/) + .gitkeep in each. + - Define client/assets/models/manifest.json, schema mirroring docs/architecture/character-asset-organization.md''s + per-asset-entry pattern (that doc''s clothing/accessory entries are the closest precedent — a flat + per-item record, not per-body-type variants since environment props aren''t body-fitted). Minimum + fields: name, category (furniture|props), source_model (path under client/assets/models/), subdirectory, + status (planned/in-progress/placeholder/final — reuse the docs/assets/README.md vocab so catalog and + manifest never disagree). Promotion = repo-canonical per D-241 — an asset entering manifest.json AND + client/assets/models/ IS the promotion act; nothing stays "promoted" in spikes/. + - Update .claude/skills/glb-gen/SKILL.md — replace the "client/assets/models/ does not exist... that''s + a Phase-5 concern" paragraph (lines ~96-102) with the new path + a pointer to the manifest. NOTE: + this contradicts nothing already in D-244/T-750 — T-750''s own epic description lists furniture/props + production as Phase-4 parallel work, so populating the production path (not populating the world with + them — that''s Phase 5 per T-961) is in-scope now. + ACCEPTANCE: client/assets/models/{furniture,props}/.gitkeep exist; manifest.json schema documented + (either inline in SKILL.md or as a comment/example in the file itself, following the character + manifest''s convention); SKILL.md''s stale "does not exist" paragraph is corrected. + +DELIVERABLE (c) — docs/assets/visual/conventions.md (NEW FILE — no prior art to amend): + - Model naming: _.glb (e.g. table_baroque.glb, chair_modernist.glb) — + mirrors the spike''s existing baroque_table.glb/modernist_chair.glb pattern loosely; pick one + order and state it (category-first matches the character-asset clothing convention + _; the spike used descriptor-first — this is a naming decision to make, + not silently pick — see lead note below on who rules it). + - Mask sidecar: _mask.png, single-channel per the character-asset convention (Section 3 + of character-asset-organization.md) UNLESS the item needs multi-region tinting, in which case follow + the RGBA channel-routed convention from that doc''s Section 11 (T-1089) rather than reinventing one. + - Texture naming keyed to D-235 tokens: PBR maps for building-exterior surfaces should name by + the ratified ObjectTag vocabulary (D-235 amendment, wiki/economics/object_tag_vocabulary.toml) — + e.g. concrete_wall_basecolor.png, pitched_roof_normal.png — NOT the old renderer/README.md + __.png scheme, which is direction-indexed for the retired 4-direction + sprite render and does not apply to PBR texture maps at all (there is no "direction" for a texture, + only map type: basecolor/normal/roughness). + - Door state handling — DO NOT port a state-frame suffix convention. The 2026-06-16 draft proposed + _open/_closed GLB variants; this is sprite-era thinking (baked state = baked frame). + For a 3D object the door''s open/closed/locked/sealed state (T-979/T-998''s DoorSpec: Open/Closed/ + TemporalWindow/Locked/Sealed) is a RUNTIME transform/animation on one door .glb, not separate baked + models — but the actual mechanism (AnimationPlayer swing vs. procedural hinge rotation vs. a + door-model convention with a named "open" bone) is a Phase-5 rendering decision (player control + + in-world rendering, per the cascade), not a Phase-4 asset-catalog concern. This ticket + should record ONLY the negative constraint (no state-frame suffix files) and defer the positive + mechanism to whichever Phase-5 ticket handles door rendering. Do not invent a runtime convention here. + - Godot TileSet resource organization — OUT OF SCOPE, removed from ticket. client/scripts/rendering/ + tile_renderer.gd''s TileSet is an explicitly-labeled placeholder ("programmatic TileSet with placeholder + colored rectangles," D-014) and the cascade rule states current in-world rendering code is + "left as-is until Phase 5 — do not build on it or rip it out before then." A /.tres + convention would be building on Phase-5-owned code from Phase 4. Do not author this section. + - Multi-tile footprint metadata: still relevant (a table/couch spans multiple sim tiles regardless + of sprite vs 3D) — keep this item, express as a manifest.json field (e.g. footprint_tiles: [w, h]) + rather than a filename convention. + ACCEPTANCE: conventions.md exists, covers model naming / mask sidecars / D-235-keyed texture naming / + multi-tile footprint metadata; explicitly states the door-state and TileSet items are deferred (with + reasons) rather than silently omitted, so a future refinement pass doesn''t have to re-derive why. + +SEQUENCING (unchanged from 2026-06-16 note): T-1053 (araminta briefing rewrite) softly depends on this +catalog. T-1049''s per-class stories reference these conventions — this ticket lands first / concurrently. + +Lead note (batch-5 activation): the model-naming-order call (category-first vs descriptor-first) is +delegated to araminta (art seat) this batch — her ruling lands in conventions.md with a one-line +rationale; surfaced to Jeroen in the batch report for cheap veto. VERDICT: READY.', NULL, '2026-07-25 18:11:31', '2026-07-25 18:11:31.588', '2026-07-25 18:11:31.588', NULL, '538f0887a4125773b49a88dcdd0b99ff', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPV39JX54HYP83RZJFDM', 'description', '(description follows in first append)', '(description follows in first append) + +Refinement (batch-5 Si pass, 2026-07-25) — READY. + +CURRENT STATE: docs/assets/visual/palette.md exists as a STUB (correctly re-scoped by D-244 — no +sprite-era content survives in it) — 4 bullets: camera angle (D-019, now superseded by D-148 for +gameplay — palette.md''s bullet is stale and must be corrected), entity colors (D-033), environmental +neutrality (D-045), "functional warmth." It explicitly defers to "art direction workshop synthesis and +first visual sprint" — this ticket IS that synthesis pass for Phase 4. + +WHAT D-235 ALREADY FIXES (do not re-litigate): the building-exterior material/roof/facade/street +vocabulary (WallMaterial/RoofForm/FacadeRhythm/StreetSurface, ratified as the 28-template +architecture_trait_catalog.toml / wiki/economics/object_tag_vocabulary.toml) and the fallback-to-generic +mechanism. D-235 is the LOGICAL token layer ("what material is this wall") — Phase 4+ scope per its own +Implementation note ("textured render + the bulk of the theme library are Phase 5+"). T-1052 is the +RENDERED-LOOK layer on top: given a WallMaterial token, what does concrete_wall actually look like +(hue range, texture grain, weathering baseline) when art is eventually produced against it. + +WHAT T-1052 MUST STILL AUTHOR (net-new, no existing decision covers this): + - Correct the camera-angle bullet: D-148 (30 deg low-angle Camera3D, supersedes D-019) is the GAMEPLAY + camera; D-019''s -72.5 deg survives only for the offline renderer (2D-artwork / retired sprite mode, + per renderer/README.md). palette.md''s current bullet conflates these — fix it to name both and + which applies where. + - A concrete color/material register for the D-235 token vocabulary: not new tokens, but the RANGE + each token maps to (e.g. "stone_wall reads as warm grey-tan, not cool grey" / "corrugated_metal + weathers to rust-orange, not staying silver") — the thing D-235 explicitly parked for Phase 5+ art + production but which a style bible can pre-specify so future asset generation has a target. + - Reconcile with docs/design/visual-grammar-v01.md and docs/workshops/art-direction-mood-board/ + workshop-outcomes.md (both pre-cascade, v0.1-era, sprite-framed) — extract what survives (color + relationships, "functional warmth" identity, D-033 palette) vs what''s superseded by D-235/D-244 + (era-based material ladder — explicitly retired by D-235''s own text: "Era is NOT a material filter + here"). Do not leave two contradictory style documents live without a note in one pointing at the + other''s supersession. + - Texture "grain"/rendering treatment for the D-244 3D-object reality: since objects are lit 3D meshes + now (not flat sprite fills), the style bible should say something about the toon/PBR balance — + precedent exists in client/assets/characters/shaders/ (toon.gdshader, toon_masked.gdshader, + outline.gdshader) and the glb-gen postprocess convention (roughness=1.0/specular=0.0 "for toon + compatibility" per .claude/skills/glb-gen/SKILL.md''s setup_materials() note). Confirm whether + environment props share this toon treatment with characters, or diverge — this is a real open + question the style bible should answer, not assume. + +ACCEPTANCE: docs/assets/visual/palette.md is no longer a stub; camera-angle bullet corrected; +color/material register for D-235 tokens documented; toon-vs-PBR treatment for props stated; +supersession relationship to visual-grammar-v01.md / workshop-outcomes.md is explicit (either by +archiving/marking those superseded, or by palette.md stating exactly what it inherits from each). + +Lead note (batch-5 activation): the toon-vs-PBR treatment call for environment props is delegated to +araminta (art seat) this batch — her ruling lands in palette.md with rationale; surfaced to Jeroen in +the batch report for cheap veto. VERDICT: READY.', NULL, '2026-07-25 18:11:34', '2026-07-25 18:11:34.859', '2026-07-25 18:11:34.859', NULL, '3a08af26c8f04bdd4ecef0b4a2fcd115', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPPHC9FZ4T0GNKR615PR', 'description', '(description follows in first append)', '(description follows in first append) + +Refinement (batch-5 Si pass, 2026-07-25) — GAPS. Formally deferred rather than refined into an +actionable brief-writing task now. + +WHY: T-1051 was filed as a child of T-1049''s per-class breakdown (the fable-ous.md audit T-1049 cites, +2026-06-12). T-1049 itself is HELD pending the same sprite-to-3D re-scope as T-1050 (its "Refinement +2026-06-16 — READY" block still frames the per-class stories, briefs, and catalog files in sprite-era +language, and its own text says "sequence T-1049 to consume T-1050''s conventions where they overlap" — +so T-1049 cannot be un-held before T-1050 lands). T-1051 is one layer further down that same +dependency: it cannot be meaningfully scoped (what does a "one-page brief" even template against?) +until (1) T-1050 lands conventions.md + the catalog structure a brief would reference, AND (2) T-1049 +is re-scoped and un-held (it owns the per-class story breakdown T-1051 is part of). + +Zero repo hits exist for lamp posts, barns, TVs, or billboards beyond the T-750 epic description text +(confirmed by T-1049''s own audit matrix) — there is no existing partial work this ticket would be +resuming, so there is no cost to deferring it cleanly. + +DISPOSITION: blocker edge wired (T-1051 blocked-by T-1049). When T-1049 is re-scoped (a future Si +pass, after T-1050 lands), that re-scope explicitly decides whether lamp posts/barns/TVs/billboards +get real briefs or a formal deferral note (T-1049''s existing block already leaned toward "explicitly +record which classes are deferred" for cars via Q-067 — the same treatment likely applies to these +four, but that is T-1049''s call when re-scoped, not this ticket''s in isolation).', NULL, '2026-07-25 18:11:38', '2026-07-25 18:11:38.173', '2026-07-25 18:11:38.173', NULL, '8567ca57e67996983e28229cb470dbd4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPJ910NNR5T2R9F9NWQR', 'status', 'backlog', 'in_progress', NULL, '2026-07-25 18:11:42', '2026-07-25 18:11:42.942', '2026-07-25 18:11:42.942', NULL, '97221c4a7a5aa2ff6e522b450335292d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPV39JX54HYP83RZJFDM', 'status', 'backlog', 'in_progress', NULL, '2026-07-25 18:11:42', '2026-07-25 18:11:42.948', '2026-07-25 18:11:42.948', NULL, '613156e91577043373205250f21a72cd', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPJ910NNR5T2R9F9NWQR', 'assigned_to', NULL, 'justine', NULL, '2026-07-25 18:11:45', '2026-07-25 18:11:45.571', '2026-07-25 18:11:45.571', NULL, '7fe3851d234f1cafb2b4fc70c1f5eed8', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPV39JX54HYP83RZJFDM', 'assigned_to', NULL, 'araminta', NULL, '2026-07-25 18:11:46', '2026-07-25 18:11:46.095', '2026-07-25 18:11:46.095', NULL, '0d1336d72b0807fe85c054696e914350', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/tickets/2026-07.sql b/.pql/changelog/tickets/2026-07.sql index b3ccd56eb..927168e82 100644 --- a/.pql/changelog/tickets/2026-07.sql +++ b/.pql/changelog/tickets/2026-07.sql @@ -3071,3 +3071,500 @@ Harness techniques inventory addition (from the PR #204 / T-1183 restart-persist 5. Fixed-center revisit mode: record a prior run''s actual derived centers and re-request them literally on the comparison run, bypassing cursor-anchored re-derivation — required when run 2 must land on run 1''s exact cache keys, not merely repeat the gesture. Harness-discipline gap found 2026-07-25 (Jeroen spotted a live orphan): agent-side kill-on-completion does not cover an agent CRASHING mid-capture (API error killed a session between server spawn and its own cleanup step — the server ran on unnoticed for ~25 min on the shared machine). Inventory addition: (6) the LEAD runs an explicit orphan sweep (ps for godot4 + settled-reach-server) at every cycle checkpoint — commit, merge, and after any agent dies abnormally — not only inside agent protocols; pr-process step 1a''s >5-min gdUnit grep does not match live-capture servers, so the sweep needs the server binary name too.', 'in_progress', 'medium', 'hoshe', 'client', NULL, '2026-07-22 09:42:47.197', '2026-07-25 18:04:09.369', NULL, 'bfec1c0e51d38b2b61d89e92c62540a2', 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 ('06FBPTXPJ910NNR5T2R9F9NWQR', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Create the visual production-status catalog; fix glb-gen production path + naming conventions', '(description follows in first append) + +--- +Refinement (2026-06-16, /whats-next Si pass) — FULL scope (user decision). Two deliverables + conventions. + +(1) CATALOG: instantiate the 5 docs/assets/visual/*.md files (sprites, furniture, tilesets, icons, effects) with per-category status tables. Template: docs/assets/_templates/sprite.md (SPR/FUR/TILE/ICO/FX prefixes); status vocab planned/in-progress/placeholder/final (docs/assets/README.md). Audio side (docs/assets/audio/README.md) is the working model. + +(2) glb-gen PRODUCTION PATH: SKILL.md:115 names client/assets/models/ which does NOT exist. Create it + furniture/ + props/ subdirs (mirror the spike) + .gitkeep. Define client/assets/models/manifest.json schema MIRRORING the character manifest (docs/architecture/character-asset-organization.md) — per-asset entries: name, category, source-model path, subdirectory. Promotion convention per D-241 (promotion = repo-canonical). Update SKILL.md to point at the new path + reference the manifest. + +(3) CONVENTIONS (author NOW, full scope): door state-frame suffixes (e.g. _open / _closed), Godot TileSet resource organization (/.tres), multi-tile footprint metadata, and renderer/textures/ naming keyed to D-235 tokens (current renderer/README.md:80-95 covers sprites only: __.png). Author these in docs/assets/visual/conventions.md (single home) and cross-link from the SKILL. + +OVERLAP: T-1053 (rewrite araminta briefing) softly depends on this catalog — note the edge. T-1049''s per-class stories will reference these conventions — do T-1050 first / concurrently so T-1049 can cite them. + +--- +HELD 2026-06-16 (separated from the cascade batch; needs re-scoping before resuming). +The asset pipeline this ticket assumes is sprite-centric (docs/assets/visual/sprites.md +"Entities", _templates/sprite.md, SPR/FUR/TILE/ICO/FX prefixes, the /sprite-gen +render-3D->2D-sprites step). Direction correction: we are NOT producing sprites — Trellis +produces 3D objects (.glb) displayed in-world; the only flattened 2D content is TEXTURES. +Re-scope this ticket around a 3D-model + texture catalog (not a sprite catalog) before +activating. See the sprite-vs-3D root-cause note (this session). + +Batch-5 Si pass correction (2026-07-25): the 2026-06-16 refinement block above is INVALIDATED by the HELD note that follows it — the sprite-centric scope (SPR/FUR prefixes, sprite catalog, /sprite-gen) contradicts the recorded direction (Trellis produces 3D .glb models; only flattened 2D content is textures). Do not implement as written. A re-scope pass (3D-model + texture catalog, manifest mirroring character-asset-organization, D-241 promotion, D-235-keyed texture naming) is running as part of batch 5; T-1052 (style bible) and T-1051 (title-only) get refinement passes in the same sweep. Sequencing per T-1049''s own note stands: this ticket''s conventions land first, T-1049/T-1053 consume them. + +Refinement (batch-5 Si pass, 2026-07-25) — SUPERSEDES the 2026-06-16 block above (sprite-centric, invalidated by D-244). READY. + +CONTEXT: D-244 (confirmed, 2026-06-16) already re-scoped the catalog structure and repointed +renderer/README.md, sprite-gen SKILL.md, docs/assets/README.md, docs/assets/visual/{README,palette}.md, +and docs/assets/_templates/sprite.md (marked legacy) at the 3D-model reality. This ticket is now +narrower than its original draft: instantiate what D-244 named but did not fill in, plus author +conventions.md (net-new — no D-244 amendment touched it). + +DELIVERABLE (a) — Category catalog files, per docs/assets/README.md''s already-updated structure: + - docs/assets/visual/models.md — 3D objects/props (.glb via /glb-gen). Row template TBD (see + conventions.md below) since _templates/sprite.md is explicitly legacy/retired for this use. + - docs/assets/visual/textures.md — PBR surface maps (BaseColor/Normal/Roughness) for 3D models. + - docs/assets/visual/artwork.md — flat 2D art (paintings/flags/billboards/signage) via /sprite-gen''s + repurposed 2D-artwork role. + - docs/assets/visual/icons.md — UI icons (13 SVGs already exist in client/assets/icons/ — this file + should reflect that as-built inventory, not start at zero). + - docs/assets/visual/effects.md — shaders/particles/overlays. + Each file follows docs/assets/audio/README.md''s model: per-category status table (planned/in-progress/ + placeholder/final per docs/assets/README.md''s vocab), current counts. Update docs/assets/visual/README.md''s + category table counts once files exist (currently all show 0, which will stay accurate at initial creation + — do not invent placeholder rows). + ACCEPTANCE: 5 files exist, cross-linked from visual/README.md (already has the links — verify they resolve), + each with a real (possibly empty) status table matching the audio/README.md pattern. + +DELIVERABLE (b) — glb-gen production path: + - Create client/assets/models/ with furniture/ and props/ subdirectories (mirroring the proven + spike layout at spikes/3dpipeline/models/{furniture,props}/) + .gitkeep in each. + - Define client/assets/models/manifest.json, schema mirroring docs/architecture/character-asset-organization.md''s + per-asset-entry pattern (that doc''s clothing/accessory entries are the closest precedent — a flat + per-item record, not per-body-type variants since environment props aren''t body-fitted). Minimum + fields: name, category (furniture|props), source_model (path under client/assets/models/), subdirectory, + status (planned/in-progress/placeholder/final — reuse the docs/assets/README.md vocab so catalog and + manifest never disagree). Promotion = repo-canonical per D-241 — an asset entering manifest.json AND + client/assets/models/ IS the promotion act; nothing stays "promoted" in spikes/. + - Update .claude/skills/glb-gen/SKILL.md — replace the "client/assets/models/ does not exist... that''s + a Phase-5 concern" paragraph (lines ~96-102) with the new path + a pointer to the manifest. NOTE: + this contradicts nothing already in D-244/T-750 — T-750''s own epic description lists furniture/props + production as Phase-4 parallel work, so populating the production path (not populating the world with + them — that''s Phase 5 per T-961) is in-scope now. + ACCEPTANCE: client/assets/models/{furniture,props}/.gitkeep exist; manifest.json schema documented + (either inline in SKILL.md or as a comment/example in the file itself, following the character + manifest''s convention); SKILL.md''s stale "does not exist" paragraph is corrected. + +DELIVERABLE (c) — docs/assets/visual/conventions.md (NEW FILE — no prior art to amend): + - Model naming: _.glb (e.g. table_baroque.glb, chair_modernist.glb) — + mirrors the spike''s existing baroque_table.glb/modernist_chair.glb pattern loosely; pick one + order and state it (category-first matches the character-asset clothing convention + _; the spike used descriptor-first — this is a naming decision to make, + not silently pick — see lead note below on who rules it). + - Mask sidecar: _mask.png, single-channel per the character-asset convention (Section 3 + of character-asset-organization.md) UNLESS the item needs multi-region tinting, in which case follow + the RGBA channel-routed convention from that doc''s Section 11 (T-1089) rather than reinventing one. + - Texture naming keyed to D-235 tokens: PBR maps for building-exterior surfaces should name by + the ratified ObjectTag vocabulary (D-235 amendment, wiki/economics/object_tag_vocabulary.toml) — + e.g. concrete_wall_basecolor.png, pitched_roof_normal.png — NOT the old renderer/README.md + __.png scheme, which is direction-indexed for the retired 4-direction + sprite render and does not apply to PBR texture maps at all (there is no "direction" for a texture, + only map type: basecolor/normal/roughness). + - Door state handling — DO NOT port a state-frame suffix convention. The 2026-06-16 draft proposed + _open/_closed GLB variants; this is sprite-era thinking (baked state = baked frame). + For a 3D object the door''s open/closed/locked/sealed state (T-979/T-998''s DoorSpec: Open/Closed/ + TemporalWindow/Locked/Sealed) is a RUNTIME transform/animation on one door .glb, not separate baked + models — but the actual mechanism (AnimationPlayer swing vs. procedural hinge rotation vs. a + door-model convention with a named "open" bone) is a Phase-5 rendering decision (player control + + in-world rendering, per the cascade), not a Phase-4 asset-catalog concern. This ticket + should record ONLY the negative constraint (no state-frame suffix files) and defer the positive + mechanism to whichever Phase-5 ticket handles door rendering. Do not invent a runtime convention here. + - Godot TileSet resource organization — OUT OF SCOPE, removed from ticket. client/scripts/rendering/ + tile_renderer.gd''s TileSet is an explicitly-labeled placeholder ("programmatic TileSet with placeholder + colored rectangles," D-014) and the cascade rule states current in-world rendering code is + "left as-is until Phase 5 — do not build on it or rip it out before then." A /.tres + convention would be building on Phase-5-owned code from Phase 4. Do not author this section. + - Multi-tile footprint metadata: still relevant (a table/couch spans multiple sim tiles regardless + of sprite vs 3D) — keep this item, express as a manifest.json field (e.g. footprint_tiles: [w, h]) + rather than a filename convention. + ACCEPTANCE: conventions.md exists, covers model naming / mask sidecars / D-235-keyed texture naming / + multi-tile footprint metadata; explicitly states the door-state and TileSet items are deferred (with + reasons) rather than silently omitted, so a future refinement pass doesn''t have to re-derive why. + +SEQUENCING (unchanged from 2026-06-16 note): T-1053 (araminta briefing rewrite) softly depends on this +catalog. T-1049''s per-class stories reference these conventions — this ticket lands first / concurrently. + +Lead note (batch-5 activation): the model-naming-order call (category-first vs descriptor-first) is +delegated to araminta (art seat) this batch — her ruling lands in conventions.md with a one-line +rationale; surfaced to Jeroen in the batch report for cheap veto. VERDICT: READY.', 'backlog', 'high', NULL, 'visual', NULL, '2026-06-12 10:40:59', '2026-07-25 18:11:31.583', NULL, '9308e8dc46545e41dc298a34500e7621', 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 ('06FBPTXPV39JX54HYP83RZJFDM', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Write the Phase-4 visual style bible into docs/assets/visual/palette.md', '(description follows in first append) + +Refinement (batch-5 Si pass, 2026-07-25) — READY. + +CURRENT STATE: docs/assets/visual/palette.md exists as a STUB (correctly re-scoped by D-244 — no +sprite-era content survives in it) — 4 bullets: camera angle (D-019, now superseded by D-148 for +gameplay — palette.md''s bullet is stale and must be corrected), entity colors (D-033), environmental +neutrality (D-045), "functional warmth." It explicitly defers to "art direction workshop synthesis and +first visual sprint" — this ticket IS that synthesis pass for Phase 4. + +WHAT D-235 ALREADY FIXES (do not re-litigate): the building-exterior material/roof/facade/street +vocabulary (WallMaterial/RoofForm/FacadeRhythm/StreetSurface, ratified as the 28-template +architecture_trait_catalog.toml / wiki/economics/object_tag_vocabulary.toml) and the fallback-to-generic +mechanism. D-235 is the LOGICAL token layer ("what material is this wall") — Phase 4+ scope per its own +Implementation note ("textured render + the bulk of the theme library are Phase 5+"). T-1052 is the +RENDERED-LOOK layer on top: given a WallMaterial token, what does concrete_wall actually look like +(hue range, texture grain, weathering baseline) when art is eventually produced against it. + +WHAT T-1052 MUST STILL AUTHOR (net-new, no existing decision covers this): + - Correct the camera-angle bullet: D-148 (30 deg low-angle Camera3D, supersedes D-019) is the GAMEPLAY + camera; D-019''s -72.5 deg survives only for the offline renderer (2D-artwork / retired sprite mode, + per renderer/README.md). palette.md''s current bullet conflates these — fix it to name both and + which applies where. + - A concrete color/material register for the D-235 token vocabulary: not new tokens, but the RANGE + each token maps to (e.g. "stone_wall reads as warm grey-tan, not cool grey" / "corrugated_metal + weathers to rust-orange, not staying silver") — the thing D-235 explicitly parked for Phase 5+ art + production but which a style bible can pre-specify so future asset generation has a target. + - Reconcile with docs/design/visual-grammar-v01.md and docs/workshops/art-direction-mood-board/ + workshop-outcomes.md (both pre-cascade, v0.1-era, sprite-framed) — extract what survives (color + relationships, "functional warmth" identity, D-033 palette) vs what''s superseded by D-235/D-244 + (era-based material ladder — explicitly retired by D-235''s own text: "Era is NOT a material filter + here"). Do not leave two contradictory style documents live without a note in one pointing at the + other''s supersession. + - Texture "grain"/rendering treatment for the D-244 3D-object reality: since objects are lit 3D meshes + now (not flat sprite fills), the style bible should say something about the toon/PBR balance — + precedent exists in client/assets/characters/shaders/ (toon.gdshader, toon_masked.gdshader, + outline.gdshader) and the glb-gen postprocess convention (roughness=1.0/specular=0.0 "for toon + compatibility" per .claude/skills/glb-gen/SKILL.md''s setup_materials() note). Confirm whether + environment props share this toon treatment with characters, or diverge — this is a real open + question the style bible should answer, not assume. + +ACCEPTANCE: docs/assets/visual/palette.md is no longer a stub; camera-angle bullet corrected; +color/material register for D-235 tokens documented; toon-vs-PBR treatment for props stated; +supersession relationship to visual-grammar-v01.md / workshop-outcomes.md is explicit (either by +archiving/marking those superseded, or by palette.md stating exactly what it inherits from each). + +Lead note (batch-5 activation): the toon-vs-PBR treatment call for environment props is delegated to +araminta (art seat) this batch — her ruling lands in palette.md with rationale; surfaced to Jeroen in +the batch report for cheap veto. VERDICT: READY.', 'backlog', 'medium', NULL, 'visual', 'D-235', '2026-06-12 10:40:59', '2026-07-25 18:11:34.859', NULL, '65d7ab732c3d9e6c912962245db39d28', 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 ('06FBPTXPPHC9FZ4T0GNKR615PR', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Author one-page briefs (or record formal deferral) for lamp posts, barns, TVs, billboards', '(description follows in first append) + +Refinement (batch-5 Si pass, 2026-07-25) — GAPS. Formally deferred rather than refined into an +actionable brief-writing task now. + +WHY: T-1051 was filed as a child of T-1049''s per-class breakdown (the fable-ous.md audit T-1049 cites, +2026-06-12). T-1049 itself is HELD pending the same sprite-to-3D re-scope as T-1050 (its "Refinement +2026-06-16 — READY" block still frames the per-class stories, briefs, and catalog files in sprite-era +language, and its own text says "sequence T-1049 to consume T-1050''s conventions where they overlap" — +so T-1049 cannot be un-held before T-1050 lands). T-1051 is one layer further down that same +dependency: it cannot be meaningfully scoped (what does a "one-page brief" even template against?) +until (1) T-1050 lands conventions.md + the catalog structure a brief would reference, AND (2) T-1049 +is re-scoped and un-held (it owns the per-class story breakdown T-1051 is part of). + +Zero repo hits exist for lamp posts, barns, TVs, or billboards beyond the T-750 epic description text +(confirmed by T-1049''s own audit matrix) — there is no existing partial work this ticket would be +resuming, so there is no cost to deferring it cleanly. + +DISPOSITION: blocker edge wired (T-1051 blocked-by T-1049). When T-1049 is re-scoped (a future Si +pass, after T-1050 lands), that re-scope explicitly decides whether lamp posts/barns/TVs/billboards +get real briefs or a formal deferral note (T-1049''s existing block already leaned toward "explicitly +record which classes are deferred" for cars via Q-067 — the same treatment likely applies to these +four, but that is T-1049''s call when re-scoped, not this ticket''s in isolation).', 'backlog', 'medium', NULL, 'visual', NULL, '2026-06-12 10:40:59', '2026-07-25 18:11:38.173', NULL, '6b2a870d348502d5ea42f134e1e12e85', 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 ('06FBPTXPJ910NNR5T2R9F9NWQR', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Create the visual production-status catalog; fix glb-gen production path + naming conventions', '(description follows in first append) + +--- +Refinement (2026-06-16, /whats-next Si pass) — FULL scope (user decision). Two deliverables + conventions. + +(1) CATALOG: instantiate the 5 docs/assets/visual/*.md files (sprites, furniture, tilesets, icons, effects) with per-category status tables. Template: docs/assets/_templates/sprite.md (SPR/FUR/TILE/ICO/FX prefixes); status vocab planned/in-progress/placeholder/final (docs/assets/README.md). Audio side (docs/assets/audio/README.md) is the working model. + +(2) glb-gen PRODUCTION PATH: SKILL.md:115 names client/assets/models/ which does NOT exist. Create it + furniture/ + props/ subdirs (mirror the spike) + .gitkeep. Define client/assets/models/manifest.json schema MIRRORING the character manifest (docs/architecture/character-asset-organization.md) — per-asset entries: name, category, source-model path, subdirectory. Promotion convention per D-241 (promotion = repo-canonical). Update SKILL.md to point at the new path + reference the manifest. + +(3) CONVENTIONS (author NOW, full scope): door state-frame suffixes (e.g. _open / _closed), Godot TileSet resource organization (/.tres), multi-tile footprint metadata, and renderer/textures/ naming keyed to D-235 tokens (current renderer/README.md:80-95 covers sprites only: __.png). Author these in docs/assets/visual/conventions.md (single home) and cross-link from the SKILL. + +OVERLAP: T-1053 (rewrite araminta briefing) softly depends on this catalog — note the edge. T-1049''s per-class stories will reference these conventions — do T-1050 first / concurrently so T-1049 can cite them. + +--- +HELD 2026-06-16 (separated from the cascade batch; needs re-scoping before resuming). +The asset pipeline this ticket assumes is sprite-centric (docs/assets/visual/sprites.md +"Entities", _templates/sprite.md, SPR/FUR/TILE/ICO/FX prefixes, the /sprite-gen +render-3D->2D-sprites step). Direction correction: we are NOT producing sprites — Trellis +produces 3D objects (.glb) displayed in-world; the only flattened 2D content is TEXTURES. +Re-scope this ticket around a 3D-model + texture catalog (not a sprite catalog) before +activating. See the sprite-vs-3D root-cause note (this session). + +Batch-5 Si pass correction (2026-07-25): the 2026-06-16 refinement block above is INVALIDATED by the HELD note that follows it — the sprite-centric scope (SPR/FUR prefixes, sprite catalog, /sprite-gen) contradicts the recorded direction (Trellis produces 3D .glb models; only flattened 2D content is textures). Do not implement as written. A re-scope pass (3D-model + texture catalog, manifest mirroring character-asset-organization, D-241 promotion, D-235-keyed texture naming) is running as part of batch 5; T-1052 (style bible) and T-1051 (title-only) get refinement passes in the same sweep. Sequencing per T-1049''s own note stands: this ticket''s conventions land first, T-1049/T-1053 consume them. + +Refinement (batch-5 Si pass, 2026-07-25) — SUPERSEDES the 2026-06-16 block above (sprite-centric, invalidated by D-244). READY. + +CONTEXT: D-244 (confirmed, 2026-06-16) already re-scoped the catalog structure and repointed +renderer/README.md, sprite-gen SKILL.md, docs/assets/README.md, docs/assets/visual/{README,palette}.md, +and docs/assets/_templates/sprite.md (marked legacy) at the 3D-model reality. This ticket is now +narrower than its original draft: instantiate what D-244 named but did not fill in, plus author +conventions.md (net-new — no D-244 amendment touched it). + +DELIVERABLE (a) — Category catalog files, per docs/assets/README.md''s already-updated structure: + - docs/assets/visual/models.md — 3D objects/props (.glb via /glb-gen). Row template TBD (see + conventions.md below) since _templates/sprite.md is explicitly legacy/retired for this use. + - docs/assets/visual/textures.md — PBR surface maps (BaseColor/Normal/Roughness) for 3D models. + - docs/assets/visual/artwork.md — flat 2D art (paintings/flags/billboards/signage) via /sprite-gen''s + repurposed 2D-artwork role. + - docs/assets/visual/icons.md — UI icons (13 SVGs already exist in client/assets/icons/ — this file + should reflect that as-built inventory, not start at zero). + - docs/assets/visual/effects.md — shaders/particles/overlays. + Each file follows docs/assets/audio/README.md''s model: per-category status table (planned/in-progress/ + placeholder/final per docs/assets/README.md''s vocab), current counts. Update docs/assets/visual/README.md''s + category table counts once files exist (currently all show 0, which will stay accurate at initial creation + — do not invent placeholder rows). + ACCEPTANCE: 5 files exist, cross-linked from visual/README.md (already has the links — verify they resolve), + each with a real (possibly empty) status table matching the audio/README.md pattern. + +DELIVERABLE (b) — glb-gen production path: + - Create client/assets/models/ with furniture/ and props/ subdirectories (mirroring the proven + spike layout at spikes/3dpipeline/models/{furniture,props}/) + .gitkeep in each. + - Define client/assets/models/manifest.json, schema mirroring docs/architecture/character-asset-organization.md''s + per-asset-entry pattern (that doc''s clothing/accessory entries are the closest precedent — a flat + per-item record, not per-body-type variants since environment props aren''t body-fitted). Minimum + fields: name, category (furniture|props), source_model (path under client/assets/models/), subdirectory, + status (planned/in-progress/placeholder/final — reuse the docs/assets/README.md vocab so catalog and + manifest never disagree). Promotion = repo-canonical per D-241 — an asset entering manifest.json AND + client/assets/models/ IS the promotion act; nothing stays "promoted" in spikes/. + - Update .claude/skills/glb-gen/SKILL.md — replace the "client/assets/models/ does not exist... that''s + a Phase-5 concern" paragraph (lines ~96-102) with the new path + a pointer to the manifest. NOTE: + this contradicts nothing already in D-244/T-750 — T-750''s own epic description lists furniture/props + production as Phase-4 parallel work, so populating the production path (not populating the world with + them — that''s Phase 5 per T-961) is in-scope now. + ACCEPTANCE: client/assets/models/{furniture,props}/.gitkeep exist; manifest.json schema documented + (either inline in SKILL.md or as a comment/example in the file itself, following the character + manifest''s convention); SKILL.md''s stale "does not exist" paragraph is corrected. + +DELIVERABLE (c) — docs/assets/visual/conventions.md (NEW FILE — no prior art to amend): + - Model naming: _.glb (e.g. table_baroque.glb, chair_modernist.glb) — + mirrors the spike''s existing baroque_table.glb/modernist_chair.glb pattern loosely; pick one + order and state it (category-first matches the character-asset clothing convention + _; the spike used descriptor-first — this is a naming decision to make, + not silently pick — see lead note below on who rules it). + - Mask sidecar: _mask.png, single-channel per the character-asset convention (Section 3 + of character-asset-organization.md) UNLESS the item needs multi-region tinting, in which case follow + the RGBA channel-routed convention from that doc''s Section 11 (T-1089) rather than reinventing one. + - Texture naming keyed to D-235 tokens: PBR maps for building-exterior surfaces should name by + the ratified ObjectTag vocabulary (D-235 amendment, wiki/economics/object_tag_vocabulary.toml) — + e.g. concrete_wall_basecolor.png, pitched_roof_normal.png — NOT the old renderer/README.md + __.png scheme, which is direction-indexed for the retired 4-direction + sprite render and does not apply to PBR texture maps at all (there is no "direction" for a texture, + only map type: basecolor/normal/roughness). + - Door state handling — DO NOT port a state-frame suffix convention. The 2026-06-16 draft proposed + _open/_closed GLB variants; this is sprite-era thinking (baked state = baked frame). + For a 3D object the door''s open/closed/locked/sealed state (T-979/T-998''s DoorSpec: Open/Closed/ + TemporalWindow/Locked/Sealed) is a RUNTIME transform/animation on one door .glb, not separate baked + models — but the actual mechanism (AnimationPlayer swing vs. procedural hinge rotation vs. a + door-model convention with a named "open" bone) is a Phase-5 rendering decision (player control + + in-world rendering, per the cascade), not a Phase-4 asset-catalog concern. This ticket + should record ONLY the negative constraint (no state-frame suffix files) and defer the positive + mechanism to whichever Phase-5 ticket handles door rendering. Do not invent a runtime convention here. + - Godot TileSet resource organization — OUT OF SCOPE, removed from ticket. client/scripts/rendering/ + tile_renderer.gd''s TileSet is an explicitly-labeled placeholder ("programmatic TileSet with placeholder + colored rectangles," D-014) and the cascade rule states current in-world rendering code is + "left as-is until Phase 5 — do not build on it or rip it out before then." A /.tres + convention would be building on Phase-5-owned code from Phase 4. Do not author this section. + - Multi-tile footprint metadata: still relevant (a table/couch spans multiple sim tiles regardless + of sprite vs 3D) — keep this item, express as a manifest.json field (e.g. footprint_tiles: [w, h]) + rather than a filename convention. + ACCEPTANCE: conventions.md exists, covers model naming / mask sidecars / D-235-keyed texture naming / + multi-tile footprint metadata; explicitly states the door-state and TileSet items are deferred (with + reasons) rather than silently omitted, so a future refinement pass doesn''t have to re-derive why. + +SEQUENCING (unchanged from 2026-06-16 note): T-1053 (araminta briefing rewrite) softly depends on this +catalog. T-1049''s per-class stories reference these conventions — this ticket lands first / concurrently. + +Lead note (batch-5 activation): the model-naming-order call (category-first vs descriptor-first) is +delegated to araminta (art seat) this batch — her ruling lands in conventions.md with a one-line +rationale; surfaced to Jeroen in the batch report for cheap veto. VERDICT: READY.', 'in_progress', 'high', NULL, 'visual', NULL, '2026-06-12 10:40:59', '2026-07-25 18:11:42.941', NULL, '59842f2c6f871ac65a35da2af2d26258', 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 ('06FBPTXPV39JX54HYP83RZJFDM', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Write the Phase-4 visual style bible into docs/assets/visual/palette.md', '(description follows in first append) + +Refinement (batch-5 Si pass, 2026-07-25) — READY. + +CURRENT STATE: docs/assets/visual/palette.md exists as a STUB (correctly re-scoped by D-244 — no +sprite-era content survives in it) — 4 bullets: camera angle (D-019, now superseded by D-148 for +gameplay — palette.md''s bullet is stale and must be corrected), entity colors (D-033), environmental +neutrality (D-045), "functional warmth." It explicitly defers to "art direction workshop synthesis and +first visual sprint" — this ticket IS that synthesis pass for Phase 4. + +WHAT D-235 ALREADY FIXES (do not re-litigate): the building-exterior material/roof/facade/street +vocabulary (WallMaterial/RoofForm/FacadeRhythm/StreetSurface, ratified as the 28-template +architecture_trait_catalog.toml / wiki/economics/object_tag_vocabulary.toml) and the fallback-to-generic +mechanism. D-235 is the LOGICAL token layer ("what material is this wall") — Phase 4+ scope per its own +Implementation note ("textured render + the bulk of the theme library are Phase 5+"). T-1052 is the +RENDERED-LOOK layer on top: given a WallMaterial token, what does concrete_wall actually look like +(hue range, texture grain, weathering baseline) when art is eventually produced against it. + +WHAT T-1052 MUST STILL AUTHOR (net-new, no existing decision covers this): + - Correct the camera-angle bullet: D-148 (30 deg low-angle Camera3D, supersedes D-019) is the GAMEPLAY + camera; D-019''s -72.5 deg survives only for the offline renderer (2D-artwork / retired sprite mode, + per renderer/README.md). palette.md''s current bullet conflates these — fix it to name both and + which applies where. + - A concrete color/material register for the D-235 token vocabulary: not new tokens, but the RANGE + each token maps to (e.g. "stone_wall reads as warm grey-tan, not cool grey" / "corrugated_metal + weathers to rust-orange, not staying silver") — the thing D-235 explicitly parked for Phase 5+ art + production but which a style bible can pre-specify so future asset generation has a target. + - Reconcile with docs/design/visual-grammar-v01.md and docs/workshops/art-direction-mood-board/ + workshop-outcomes.md (both pre-cascade, v0.1-era, sprite-framed) — extract what survives (color + relationships, "functional warmth" identity, D-033 palette) vs what''s superseded by D-235/D-244 + (era-based material ladder — explicitly retired by D-235''s own text: "Era is NOT a material filter + here"). Do not leave two contradictory style documents live without a note in one pointing at the + other''s supersession. + - Texture "grain"/rendering treatment for the D-244 3D-object reality: since objects are lit 3D meshes + now (not flat sprite fills), the style bible should say something about the toon/PBR balance — + precedent exists in client/assets/characters/shaders/ (toon.gdshader, toon_masked.gdshader, + outline.gdshader) and the glb-gen postprocess convention (roughness=1.0/specular=0.0 "for toon + compatibility" per .claude/skills/glb-gen/SKILL.md''s setup_materials() note). Confirm whether + environment props share this toon treatment with characters, or diverge — this is a real open + question the style bible should answer, not assume. + +ACCEPTANCE: docs/assets/visual/palette.md is no longer a stub; camera-angle bullet corrected; +color/material register for D-235 tokens documented; toon-vs-PBR treatment for props stated; +supersession relationship to visual-grammar-v01.md / workshop-outcomes.md is explicit (either by +archiving/marking those superseded, or by palette.md stating exactly what it inherits from each). + +Lead note (batch-5 activation): the toon-vs-PBR treatment call for environment props is delegated to +araminta (art seat) this batch — her ruling lands in palette.md with rationale; surfaced to Jeroen in +the batch report for cheap veto. VERDICT: READY.', 'in_progress', 'medium', NULL, 'visual', 'D-235', '2026-06-12 10:40:59', '2026-07-25 18:11:42.948', NULL, 'ec166c5d4c83097a1d51136ebf37380d', 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 ('06FBPTXPJ910NNR5T2R9F9NWQR', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Create the visual production-status catalog; fix glb-gen production path + naming conventions', '(description follows in first append) + +--- +Refinement (2026-06-16, /whats-next Si pass) — FULL scope (user decision). Two deliverables + conventions. + +(1) CATALOG: instantiate the 5 docs/assets/visual/*.md files (sprites, furniture, tilesets, icons, effects) with per-category status tables. Template: docs/assets/_templates/sprite.md (SPR/FUR/TILE/ICO/FX prefixes); status vocab planned/in-progress/placeholder/final (docs/assets/README.md). Audio side (docs/assets/audio/README.md) is the working model. + +(2) glb-gen PRODUCTION PATH: SKILL.md:115 names client/assets/models/ which does NOT exist. Create it + furniture/ + props/ subdirs (mirror the spike) + .gitkeep. Define client/assets/models/manifest.json schema MIRRORING the character manifest (docs/architecture/character-asset-organization.md) — per-asset entries: name, category, source-model path, subdirectory. Promotion convention per D-241 (promotion = repo-canonical). Update SKILL.md to point at the new path + reference the manifest. + +(3) CONVENTIONS (author NOW, full scope): door state-frame suffixes (e.g. _open / _closed), Godot TileSet resource organization (/.tres), multi-tile footprint metadata, and renderer/textures/ naming keyed to D-235 tokens (current renderer/README.md:80-95 covers sprites only: __.png). Author these in docs/assets/visual/conventions.md (single home) and cross-link from the SKILL. + +OVERLAP: T-1053 (rewrite araminta briefing) softly depends on this catalog — note the edge. T-1049''s per-class stories will reference these conventions — do T-1050 first / concurrently so T-1049 can cite them. + +--- +HELD 2026-06-16 (separated from the cascade batch; needs re-scoping before resuming). +The asset pipeline this ticket assumes is sprite-centric (docs/assets/visual/sprites.md +"Entities", _templates/sprite.md, SPR/FUR/TILE/ICO/FX prefixes, the /sprite-gen +render-3D->2D-sprites step). Direction correction: we are NOT producing sprites — Trellis +produces 3D objects (.glb) displayed in-world; the only flattened 2D content is TEXTURES. +Re-scope this ticket around a 3D-model + texture catalog (not a sprite catalog) before +activating. See the sprite-vs-3D root-cause note (this session). + +Batch-5 Si pass correction (2026-07-25): the 2026-06-16 refinement block above is INVALIDATED by the HELD note that follows it — the sprite-centric scope (SPR/FUR prefixes, sprite catalog, /sprite-gen) contradicts the recorded direction (Trellis produces 3D .glb models; only flattened 2D content is textures). Do not implement as written. A re-scope pass (3D-model + texture catalog, manifest mirroring character-asset-organization, D-241 promotion, D-235-keyed texture naming) is running as part of batch 5; T-1052 (style bible) and T-1051 (title-only) get refinement passes in the same sweep. Sequencing per T-1049''s own note stands: this ticket''s conventions land first, T-1049/T-1053 consume them. + +Refinement (batch-5 Si pass, 2026-07-25) — SUPERSEDES the 2026-06-16 block above (sprite-centric, invalidated by D-244). READY. + +CONTEXT: D-244 (confirmed, 2026-06-16) already re-scoped the catalog structure and repointed +renderer/README.md, sprite-gen SKILL.md, docs/assets/README.md, docs/assets/visual/{README,palette}.md, +and docs/assets/_templates/sprite.md (marked legacy) at the 3D-model reality. This ticket is now +narrower than its original draft: instantiate what D-244 named but did not fill in, plus author +conventions.md (net-new — no D-244 amendment touched it). + +DELIVERABLE (a) — Category catalog files, per docs/assets/README.md''s already-updated structure: + - docs/assets/visual/models.md — 3D objects/props (.glb via /glb-gen). Row template TBD (see + conventions.md below) since _templates/sprite.md is explicitly legacy/retired for this use. + - docs/assets/visual/textures.md — PBR surface maps (BaseColor/Normal/Roughness) for 3D models. + - docs/assets/visual/artwork.md — flat 2D art (paintings/flags/billboards/signage) via /sprite-gen''s + repurposed 2D-artwork role. + - docs/assets/visual/icons.md — UI icons (13 SVGs already exist in client/assets/icons/ — this file + should reflect that as-built inventory, not start at zero). + - docs/assets/visual/effects.md — shaders/particles/overlays. + Each file follows docs/assets/audio/README.md''s model: per-category status table (planned/in-progress/ + placeholder/final per docs/assets/README.md''s vocab), current counts. Update docs/assets/visual/README.md''s + category table counts once files exist (currently all show 0, which will stay accurate at initial creation + — do not invent placeholder rows). + ACCEPTANCE: 5 files exist, cross-linked from visual/README.md (already has the links — verify they resolve), + each with a real (possibly empty) status table matching the audio/README.md pattern. + +DELIVERABLE (b) — glb-gen production path: + - Create client/assets/models/ with furniture/ and props/ subdirectories (mirroring the proven + spike layout at spikes/3dpipeline/models/{furniture,props}/) + .gitkeep in each. + - Define client/assets/models/manifest.json, schema mirroring docs/architecture/character-asset-organization.md''s + per-asset-entry pattern (that doc''s clothing/accessory entries are the closest precedent — a flat + per-item record, not per-body-type variants since environment props aren''t body-fitted). Minimum + fields: name, category (furniture|props), source_model (path under client/assets/models/), subdirectory, + status (planned/in-progress/placeholder/final — reuse the docs/assets/README.md vocab so catalog and + manifest never disagree). Promotion = repo-canonical per D-241 — an asset entering manifest.json AND + client/assets/models/ IS the promotion act; nothing stays "promoted" in spikes/. + - Update .claude/skills/glb-gen/SKILL.md — replace the "client/assets/models/ does not exist... that''s + a Phase-5 concern" paragraph (lines ~96-102) with the new path + a pointer to the manifest. NOTE: + this contradicts nothing already in D-244/T-750 — T-750''s own epic description lists furniture/props + production as Phase-4 parallel work, so populating the production path (not populating the world with + them — that''s Phase 5 per T-961) is in-scope now. + ACCEPTANCE: client/assets/models/{furniture,props}/.gitkeep exist; manifest.json schema documented + (either inline in SKILL.md or as a comment/example in the file itself, following the character + manifest''s convention); SKILL.md''s stale "does not exist" paragraph is corrected. + +DELIVERABLE (c) — docs/assets/visual/conventions.md (NEW FILE — no prior art to amend): + - Model naming: _.glb (e.g. table_baroque.glb, chair_modernist.glb) — + mirrors the spike''s existing baroque_table.glb/modernist_chair.glb pattern loosely; pick one + order and state it (category-first matches the character-asset clothing convention + _; the spike used descriptor-first — this is a naming decision to make, + not silently pick — see lead note below on who rules it). + - Mask sidecar: _mask.png, single-channel per the character-asset convention (Section 3 + of character-asset-organization.md) UNLESS the item needs multi-region tinting, in which case follow + the RGBA channel-routed convention from that doc''s Section 11 (T-1089) rather than reinventing one. + - Texture naming keyed to D-235 tokens: PBR maps for building-exterior surfaces should name by + the ratified ObjectTag vocabulary (D-235 amendment, wiki/economics/object_tag_vocabulary.toml) — + e.g. concrete_wall_basecolor.png, pitched_roof_normal.png — NOT the old renderer/README.md + __.png scheme, which is direction-indexed for the retired 4-direction + sprite render and does not apply to PBR texture maps at all (there is no "direction" for a texture, + only map type: basecolor/normal/roughness). + - Door state handling — DO NOT port a state-frame suffix convention. The 2026-06-16 draft proposed + _open/_closed GLB variants; this is sprite-era thinking (baked state = baked frame). + For a 3D object the door''s open/closed/locked/sealed state (T-979/T-998''s DoorSpec: Open/Closed/ + TemporalWindow/Locked/Sealed) is a RUNTIME transform/animation on one door .glb, not separate baked + models — but the actual mechanism (AnimationPlayer swing vs. procedural hinge rotation vs. a + door-model convention with a named "open" bone) is a Phase-5 rendering decision (player control + + in-world rendering, per the cascade), not a Phase-4 asset-catalog concern. This ticket + should record ONLY the negative constraint (no state-frame suffix files) and defer the positive + mechanism to whichever Phase-5 ticket handles door rendering. Do not invent a runtime convention here. + - Godot TileSet resource organization — OUT OF SCOPE, removed from ticket. client/scripts/rendering/ + tile_renderer.gd''s TileSet is an explicitly-labeled placeholder ("programmatic TileSet with placeholder + colored rectangles," D-014) and the cascade rule states current in-world rendering code is + "left as-is until Phase 5 — do not build on it or rip it out before then." A /.tres + convention would be building on Phase-5-owned code from Phase 4. Do not author this section. + - Multi-tile footprint metadata: still relevant (a table/couch spans multiple sim tiles regardless + of sprite vs 3D) — keep this item, express as a manifest.json field (e.g. footprint_tiles: [w, h]) + rather than a filename convention. + ACCEPTANCE: conventions.md exists, covers model naming / mask sidecars / D-235-keyed texture naming / + multi-tile footprint metadata; explicitly states the door-state and TileSet items are deferred (with + reasons) rather than silently omitted, so a future refinement pass doesn''t have to re-derive why. + +SEQUENCING (unchanged from 2026-06-16 note): T-1053 (araminta briefing rewrite) softly depends on this +catalog. T-1049''s per-class stories reference these conventions — this ticket lands first / concurrently. + +Lead note (batch-5 activation): the model-naming-order call (category-first vs descriptor-first) is +delegated to araminta (art seat) this batch — her ruling lands in conventions.md with a one-line +rationale; surfaced to Jeroen in the batch report for cheap veto. VERDICT: READY.', 'in_progress', 'high', 'justine', 'visual', NULL, '2026-06-12 10:40:59', '2026-07-25 18:11:45.570', NULL, '1c88b124701c748774a3a03ef3a0bc18', 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 ('06FBPTXPV39JX54HYP83RZJFDM', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Write the Phase-4 visual style bible into docs/assets/visual/palette.md', '(description follows in first append) + +Refinement (batch-5 Si pass, 2026-07-25) — READY. + +CURRENT STATE: docs/assets/visual/palette.md exists as a STUB (correctly re-scoped by D-244 — no +sprite-era content survives in it) — 4 bullets: camera angle (D-019, now superseded by D-148 for +gameplay — palette.md''s bullet is stale and must be corrected), entity colors (D-033), environmental +neutrality (D-045), "functional warmth." It explicitly defers to "art direction workshop synthesis and +first visual sprint" — this ticket IS that synthesis pass for Phase 4. + +WHAT D-235 ALREADY FIXES (do not re-litigate): the building-exterior material/roof/facade/street +vocabulary (WallMaterial/RoofForm/FacadeRhythm/StreetSurface, ratified as the 28-template +architecture_trait_catalog.toml / wiki/economics/object_tag_vocabulary.toml) and the fallback-to-generic +mechanism. D-235 is the LOGICAL token layer ("what material is this wall") — Phase 4+ scope per its own +Implementation note ("textured render + the bulk of the theme library are Phase 5+"). T-1052 is the +RENDERED-LOOK layer on top: given a WallMaterial token, what does concrete_wall actually look like +(hue range, texture grain, weathering baseline) when art is eventually produced against it. + +WHAT T-1052 MUST STILL AUTHOR (net-new, no existing decision covers this): + - Correct the camera-angle bullet: D-148 (30 deg low-angle Camera3D, supersedes D-019) is the GAMEPLAY + camera; D-019''s -72.5 deg survives only for the offline renderer (2D-artwork / retired sprite mode, + per renderer/README.md). palette.md''s current bullet conflates these — fix it to name both and + which applies where. + - A concrete color/material register for the D-235 token vocabulary: not new tokens, but the RANGE + each token maps to (e.g. "stone_wall reads as warm grey-tan, not cool grey" / "corrugated_metal + weathers to rust-orange, not staying silver") — the thing D-235 explicitly parked for Phase 5+ art + production but which a style bible can pre-specify so future asset generation has a target. + - Reconcile with docs/design/visual-grammar-v01.md and docs/workshops/art-direction-mood-board/ + workshop-outcomes.md (both pre-cascade, v0.1-era, sprite-framed) — extract what survives (color + relationships, "functional warmth" identity, D-033 palette) vs what''s superseded by D-235/D-244 + (era-based material ladder — explicitly retired by D-235''s own text: "Era is NOT a material filter + here"). Do not leave two contradictory style documents live without a note in one pointing at the + other''s supersession. + - Texture "grain"/rendering treatment for the D-244 3D-object reality: since objects are lit 3D meshes + now (not flat sprite fills), the style bible should say something about the toon/PBR balance — + precedent exists in client/assets/characters/shaders/ (toon.gdshader, toon_masked.gdshader, + outline.gdshader) and the glb-gen postprocess convention (roughness=1.0/specular=0.0 "for toon + compatibility" per .claude/skills/glb-gen/SKILL.md''s setup_materials() note). Confirm whether + environment props share this toon treatment with characters, or diverge — this is a real open + question the style bible should answer, not assume. + +ACCEPTANCE: docs/assets/visual/palette.md is no longer a stub; camera-angle bullet corrected; +color/material register for D-235 tokens documented; toon-vs-PBR treatment for props stated; +supersession relationship to visual-grammar-v01.md / workshop-outcomes.md is explicit (either by +archiving/marking those superseded, or by palette.md stating exactly what it inherits from each). + +Lead note (batch-5 activation): the toon-vs-PBR treatment call for environment props is delegated to +araminta (art seat) this batch — her ruling lands in palette.md with rationale; surfaced to Jeroen in +the batch report for cheap veto. VERDICT: READY.', 'in_progress', 'medium', 'araminta', 'visual', 'D-235', '2026-06-12 10:40:59', '2026-07-25 18:11:46.095', NULL, '4df3e92b2bc003a7b73f860dd47704f2', 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); From f77e15d075d34cbb7854c0dd89887f6a8b80534f Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sat, 25 Jul 2026 20:20:04 +0200 Subject: [PATCH 2/9] docs(assets): 3D-model+texture catalog, glb-gen production path, conventions (T-1050) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instantiates the five D-244 catalog files (models/textures/artwork/icons/ effects — icons carries the 13 as-built SVGs, stance icons traced to their consumer and marked final), creates client/assets/models/{furniture,props} with a schema-documented manifest.json mirroring the character manifest, fixes glb-gen SKILL.md's stale 'path does not exist' paragraph, and authors docs/assets/visual/conventions.md: category-first model naming (araminta ruling), mask sidecars per the character convention, D-235-token-keyed texture naming, footprint_tiles as manifest metadata. Door-state and TileSet conventions are recorded as explicit deferrals (Phase-5 owned). visual/README.md carries both this ticket's count/link updates and T-1052's palette-section rewrite (shared file, committed here). Co-Authored-By: Claude Fable 5 --- .claude/skills/glb-gen/SKILL.md | 13 +- client/assets/models/furniture/.gitkeep | 0 client/assets/models/manifest.json | 24 ++++ client/assets/models/props/.gitkeep | 0 docs/assets/README.md | 3 +- docs/assets/visual/README.md | 12 +- docs/assets/visual/artwork.md | 41 ++++++ docs/assets/visual/conventions.md | 164 ++++++++++++++++++++++++ docs/assets/visual/effects.md | 41 ++++++ docs/assets/visual/icons.md | 50 ++++++++ docs/assets/visual/models.md | 48 +++++++ docs/assets/visual/textures.md | 42 ++++++ 12 files changed, 429 insertions(+), 9 deletions(-) create mode 100644 client/assets/models/furniture/.gitkeep create mode 100644 client/assets/models/manifest.json create mode 100644 client/assets/models/props/.gitkeep create mode 100644 docs/assets/visual/artwork.md create mode 100644 docs/assets/visual/conventions.md create mode 100644 docs/assets/visual/effects.md create mode 100644 docs/assets/visual/icons.md create mode 100644 docs/assets/visual/models.md create mode 100644 docs/assets/visual/textures.md diff --git a/.claude/skills/glb-gen/SKILL.md b/.claude/skills/glb-gen/SKILL.md index cc41c47e2..5d6c46930 100644 --- a/.claude/skills/glb-gen/SKILL.md +++ b/.claude/skills/glb-gen/SKILL.md @@ -95,11 +95,14 @@ Always mirror the category/subcategory path across stages so you can trace Final game-ready assets are copied to `spikes/3dpipeline/models/` for spike testing. For production, character GLBs go to `client/assets/characters/` (already in active use — see its `bodies/`, `clothing/`, `hair/`, -`skeleton/`, etc. subdirectories). `client/assets/models/` does not exist — -there is no established production path yet for non-character categories -(furniture, props); that's a Phase-5 concern (Player control & in-world -rendering, per CLAUDE.md's Development Cascade). Until then, keep -non-character output staged under `.tmp/glb-gen/postproc/`. +`skeleton/`, etc. subdirectories). Non-character categories (furniture, +props) go to `client/assets/models/{furniture,props}/` — register every +promoted asset in `client/assets/models/manifest.json` (schema documented +inline in that file; mirrors the character manifest's per-item pattern). +Promotion = repo-canonical (D-241): an asset entering the manifest AND +`client/assets/models/` **is** the promotion act — nothing stays "promoted" +in `spikes/`. Naming, mask-sidecar, and texture conventions are in +`docs/assets/visual/conventions.md`. ## Post-process in Blender diff --git a/client/assets/models/furniture/.gitkeep b/client/assets/models/furniture/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/client/assets/models/manifest.json b/client/assets/models/manifest.json new file mode 100644 index 000000000..5214e5788 --- /dev/null +++ b/client/assets/models/manifest.json @@ -0,0 +1,24 @@ +{ + "_comment": "Environment prop/furniture manifest — schema mirrors client/assets/characters/manifest.json's per-item entry pattern (see docs/architecture/character-asset-organization.md). Keyed by asset name (matches the .glb basename, no extension). One entry per asset, NOT per-body-type variant — environment props are not body-fitted (D-244). An asset entering this manifest AND client/assets/models/ IS the promotion act (D-241); nothing stays 'promoted' in spikes/. Naming/mask-sidecar/texture conventions: docs/assets/visual/conventions.md.", + "_fields": { + "name": "string — asset name, matches the manifest key and the .glb basename", + "category": "string — 'furniture' | 'props' (matches the subdirectory)", + "source_model": "string — path to the .glb under client/assets/models/, e.g. 'furniture/table_baroque.glb'", + "subdirectory": "string — 'furniture' | 'props' (same as category; kept as its own field to match the character-manifest precedent of not deriving one field from another)", + "status": "string — planned | in-progress | placeholder | final (docs/assets/README.md vocab — shared with the catalog tables so manifest and catalog never disagree)", + "footprint_tiles": "[width, height] — multi-tile sim footprint in whole tiles (1 tile = 1m, D-243). Omit (or [1, 1]) for single-tile props.", + "mask": "string, optional — path to the recolor mask sidecar, e.g. 'furniture/table_baroque_mask.png'. Omit if the asset has no tintable region." + }, + "_example_only_not_a_real_asset": { + "table_baroque": { + "category": "furniture", + "source_model": "furniture/table_baroque.glb", + "subdirectory": "furniture", + "status": "planned", + "footprint_tiles": [2, 1], + "mask": "furniture/table_baroque_mask.png" + } + }, + "furniture": {}, + "props": {} +} diff --git a/client/assets/models/props/.gitkeep b/client/assets/models/props/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/assets/README.md b/docs/assets/README.md index 991a0d03e..81f6aeec1 100644 --- a/docs/assets/README.md +++ b/docs/assets/README.md @@ -24,7 +24,7 @@ docs/assets/ README.md # This file — master index _templates/ audio.md # Row template + detailed entry template for audio - sprite.md # LEGACY object-sprite row template (D-244 retired sprites; model/texture/artwork template TBD) + sprite.md # LEGACY object-sprite row template (D-244 retired sprites; model/texture/artwork rows are inline in each visual/*.md file, T-1050) video.md # Row template + detailed entry template for video audio/ README.md # Audio pipeline index — categories, status summary @@ -40,6 +40,7 @@ docs/assets/ artwork.md # Flat 2D art — paintings, flags, billboards, signage (via /sprite-gen) icons.md # UI icons, status indicators (UI layer) effects.md # Shaders, particles, overlays + conventions.md # Model naming, mask sidecars, D-235-keyed texture naming, footprint metadata (T-1050) video/ README.md # Video pipeline index palette.md # Motion/editing style guide diff --git a/docs/assets/visual/README.md b/docs/assets/visual/README.md index f42bf8a6b..c5500d3a5 100644 --- a/docs/assets/visual/README.md +++ b/docs/assets/visual/README.md @@ -7,7 +7,8 @@ status: active # Visual Asset Pipeline -Status: **Stub** — awaiting first visual sprint. +Status: **Active** — catalog structure instantiated (T-1050); awaiting first +model/texture/artwork/effects production sprint. Per **[D-244](../../../governance/decisions/architecture.md#d-244)**: the in-world view renders **3D objects directly**. The only flattened 2D content is **textures** (PBR maps on 3D surfaces) and **flat 2D artwork** (paintings, flags, billboards, signage) applied as textures/decals on flat surfaces *within* the 3D world. **There is no per-object sprite layer** — game objects are not pre-rendered to sprites. @@ -19,12 +20,17 @@ Per **[D-244](../../../governance/decisions/architecture.md#d-244)**: the in-wor | Characters | — | — | 3D meshes composited at runtime via `CharacterVisualDescriptor` — see [character-asset-organization.md](../../architecture/character-asset-organization.md) | | Textures | [textures.md](textures.md) | 0 | PBR surface maps (BaseColor / Normal / Roughness) for 3D models | | Artwork | [artwork.md](artwork.md) | 0 | Flat 2D art — paintings, flags, billboards, signage, screen content — applied as textures/decals (via the `/sprite-gen` 2D-artwork generator) | -| Icons | [icons.md](icons.md) | 0 | UI icons, status indicators (2D, UI layer only) | +| Icons | [icons.md](icons.md) | 13 | UI icons, status indicators (2D, UI layer only) — as-built inventory of `client/assets/icons/` | | Effects | [effects.md](effects.md) | 0 | Shaders, particles, overlays | +Naming, mask-sidecar, D-235-keyed texture, and multi-tile footprint +conventions: [conventions.md](conventions.md). + ## Palette -See [palette.md](palette.md) for art direction, "the angle", and colour relationships. +See [palette.md](palette.md) for art direction: camera (D-148 gameplay / D-019 +offline-render), the toon-vs-PBR rendering treatment for environment props, the +D-235 ObjectTag color/material register, and entity colour relationships (D-033). ## Pipeline diff --git a/docs/assets/visual/artwork.md b/docs/assets/visual/artwork.md new file mode 100644 index 000000000..f9c7a794b --- /dev/null +++ b/docs/assets/visual/artwork.md @@ -0,0 +1,41 @@ +--- +title: "Artwork — Visual Assets" +description: "Flat 2D artwork asset catalog — paintings, flags, billboards, signage, screen content" +type: design +status: active +--- + +# Artwork — Visual Assets + +Flat 2D art — paintings, flags, billboards, signage, screen content — that +lives as a texture/decal on a flat surface *within* the 3D world. This is one +of the two flattened-2D exceptions under D-244 (the other is +[textures.md](textures.md)); it is not a sprite layer. + +## Generation Approach + +Produced via the `/sprite-gen` skill's **repurposed 2D-artwork role** +(`renderer/README.md`) — the offline Godot renderer originally built for the +retired object-sprite pipeline (#541 spike), now serving flat-artwork +rendering. Camera/lighting/resolution mechanics from that pipeline are +reused; the "render every entity/object/wall to sprites" framing in +`renderer/README.md` is legacy and does not apply here. + +Output is promoted to `client/assets/artwork/` (per `renderer/README.md`'s +own note: flat-artwork output goes to `client/assets/textures` or +`client/assets/artwork`, never `client/assets/sprites/`). + +## Assets + +| ID | Filename | Status | Type | Notes | Sprint/Ticket | +|----|----------|--------|------|-------|---------------| + +No artwork has entered production yet — this table is intentionally empty at +initial creation (T-1050). + +## Cross-References + +- [conventions.md](conventions.md) +- `renderer/README.md` — repurposed 2D-artwork render pipeline +- `.claude/skills/sprite-gen/SKILL.md` +- [D-244](../../../governance/decisions/architecture.md#d-244-asset-rendering--3d-objects-in-world-2d-limited-to-textures--flat-artwork) diff --git a/docs/assets/visual/conventions.md b/docs/assets/visual/conventions.md new file mode 100644 index 000000000..5e930bbdd --- /dev/null +++ b/docs/assets/visual/conventions.md @@ -0,0 +1,164 @@ +--- +title: "Visual Asset Conventions" +description: "Model naming, mask sidecars, D-235-keyed texture naming, and multi-tile footprint metadata for the 3D asset pipeline" +type: design +status: active +--- + +# Visual Asset Conventions + +Single home for 3D-model and texture naming conventions (D-244 — the in-world +view renders 3D objects directly; there is no per-object sprite layer). This +file is the cross-link target from `.claude/skills/glb-gen/SKILL.md` and the +catalog files in `docs/assets/visual/`. + +--- + +## 1. Model naming + +Format: `_.glb` + +Examples: `table_baroque.glb`, `chair_modernist.glb`, `door_sliding.glb`. + +**Ruling (delegated to Araminta, art seat, batch-5 activation — T-1052): +category-first, not descriptor-first.** Rationale: category-first matches +the shipped, in-active-use character-asset clothing convention +(`_` — `jacket_modern.glb`, `boots_modern.glb`, +`pants_formal.glb`, all live under `client/assets/characters/clothing/`) and +gives every future asset browser/manifest a stable alphabetical grouping by +type (all tables sort together, all chairs sort together). The 3dpipeline +spike's descriptor-first naming (`baroque_table.glb`, `modernist_chair.glb`, +`spikes/3dpipeline/README.md`) was pre-convention scratch work, not a +precedent to preserve — D-241 already treats spike naming as disposable at +promotion time. **Surfaced to Jeroen for cheap veto** (lead delegation note, +T-1050). + +`category` is the subdirectory-agnostic object type (`table`, `chair`, +`door`, `lamp`, `statue`); `descriptor` distinguishes style/variant +(`baroque`, `modernist`, `sliding`). Category is not required to match the +`furniture`/`props` subdirectory name — a `statue` and a `table` can both be +`furniture` or both be `props` depending on where they land; the manifest's +`category` field, not the filename, is authoritative for that split. + +--- + +## 2. Mask sidecars + +Convention: `_mask.png`, single-channel (R-only greyscale), sitting +next to the GLB — per the character-asset convention (Section 3, +`docs/architecture/character-asset-organization.md`). White = tintable +region, black = preserve original texture (`hint_default_black`: no mask +loaded = no tinting; assets without a mask render with their original +Trellis/authored texture). + +Example: `table_baroque.glb` + `table_baroque_mask.png` (this is the existing +Trellis convention already proven in the 3dpipeline spike, just re-ordered to +category-first per Section 1's ruling). + +**Multi-region tinting.** If a single-channel mask is insufficient (an item +needs more than one independently-tintable region), follow the RGBA +channel-routed convention from `character-asset-organization.md` Section 11 +(T-1089) — R/G/B/A → four independent tints, cross-fading correctly under +bilinear filtering — rather than reinventing a greyscale-band scheme (that +approach was tried for clothing and rejected; see that doc's Section 5 +correction). This is the same mechanism, not a parallel one: environment +props reuse the clothing pipeline's multi-region shader family rather than +inventing a props-specific one. + +--- + +## 3. Texture naming (D-235 tokens) + +PBR surface maps for building-exterior and environment surfaces are named by +the ratified **ObjectTag vocabulary** (D-235 amendment, T-995, +`wiki/economics/object_tag_vocabulary.toml`), **not** the retired +`renderer/README.md` `__.png` scheme. That +scheme is direction-indexed for the retired 4-direction sprite render +(`renderer/README.md`'s now-legacy object-sprite mode) and does not apply to +PBR texture maps at all — there is no "direction" for a texture, only **map +type** (basecolor / normal / roughness). + +Format: `_.png` + +Map types: `basecolor`, `normal`, `roughness`. (`metallic` and `ao` may be +added if a material needs them; keep the same `_` +shape.) + +Examples, drawn from the D-235 ratified tags: +- `concrete_wall_basecolor.png`, `concrete_wall_normal.png`, `concrete_wall_roughness.png` +- `pitched_roof_normal.png` +- `stone_wall_basecolor.png` +- `generic_wall_basecolor.png` (fallback-terminal placeholder — D-235's + fallback hierarchy: every specific tag degrades to a `generic_*` parent + until the specific texture ships, then upgrades in place) + +The full ratified axis lists (wall/roof/facade/street + the four `generic_*` +fallback terminals) live in `wiki/economics/object_tag_vocabulary.toml` — +this doc does not duplicate that list; it names the file-naming convention +that keys off it. + +--- + +## 4. Multi-tile footprint metadata + +A table or couch spans multiple sim tiles regardless of whether it's +rendered as a sprite or a 3D model — this requirement survived the D-244 +pivot unchanged. It is **not** expressed as a filename convention (a +sprite-era assumption); it is a `manifest.json` field: + +```json +"footprint_tiles": [2, 1] +``` + +`[width, height]` in whole tiles (1 tile = 1m, D-243's voxel unit). Single +Sunday-tile props omit the field or set it to `[1, 1]`. See +`client/assets/models/manifest.json`'s `_fields` block for the authoritative +field definition alongside the rest of the per-asset schema. + +--- + +## 5. Deferred — door state handling (NOT authored here) + +**Do not port a state-frame suffix convention** (e.g. `door_open.glb` / +`door_closed.glb`). An earlier draft of this ticket (2026-06-16, since +superseded) proposed exactly that — baked open/closed GLB variants — which is +sprite-era thinking (baked state = baked frame) carried over from the retired +object-sprite model. + +For a 3D object, the door's open/closed/locked/sealed state (T-979/T-998's +`DoorSpec`: `Open` / `Closed` / `TemporalWindow` / `Locked` / `Sealed`) is a +**runtime transform/animation on one door `.glb`**, not separate baked +models. The actual mechanism — `AnimationPlayer` swing vs. procedural hinge +rotation vs. a door-model convention with a named "open" bone — is a +**Phase-5 rendering decision** (player control + in-world rendering, per the +cascade), not a Phase-4 asset-catalog concern. + +This doc records only the negative constraint above (no state-frame suffix +files). The positive mechanism is deferred to whichever Phase-5 ticket +handles door rendering — do not invent a runtime convention here. + +--- + +## 6. Deferred — Godot TileSet resource organization (OUT OF SCOPE) + +A `/.tres` convention is **out of scope for this doc and +this phase**, removed from the ticket that spawned this file. Reason: +`client/scripts/rendering/tile_renderer.gd`'s `TileSet` is an +explicitly-labeled placeholder ("programmatic TileSet with placeholder +colored rectangles," D-014), and the cascade rule (`CLAUDE.md`) states the +current in-world rendering code is "left as-is until Phase 5 — do not build +on it or rip it out before then." Authoring a `.tres` resource-organization +convention now would mean building on Phase-5-owned code from Phase 4. This +section is intentionally not written; a future Phase-5 ticket owns it. + +--- + +## Cross-References + +- [D-244](../../../governance/decisions/architecture.md#d-244-asset-rendering--3d-objects-in-world-2d-limited-to-textures--flat-artwork) — 3D objects in-world; 2D limited to textures + flat artwork. +- [D-241](../../../governance/decisions/process.md#d-241-spike-binary-artifacts-are-local-only--gitignored-never-tracked) — spike binaries local-only; promotion = repo-canonical. +- [D-235](../../../governance/decisions/architecture.md#d-235-building-exterior-visual-grammar-and-material-vocabulary) — ObjectTag vocabulary, ratified in `wiki/economics/object_tag_vocabulary.toml` (T-995). +- [D-243](../../../governance/decisions/architecture.md#d-243) — spatial scale ladder (1m voxel/tile unit for `footprint_tiles`). +- `docs/architecture/character-asset-organization.md` — Sections 3, 5, 11 (mask sidecar precedent, multi-region RGBA convention). +- `client/assets/models/manifest.json` — the schema this doc's naming/metadata conventions feed. +- `.claude/skills/glb-gen/SKILL.md` — the production pipeline that writes to `client/assets/models/`. diff --git a/docs/assets/visual/effects.md b/docs/assets/visual/effects.md new file mode 100644 index 000000000..3f2dee7e5 --- /dev/null +++ b/docs/assets/visual/effects.md @@ -0,0 +1,41 @@ +--- +title: "Effects — Visual Assets" +description: "Shader, particle, and overlay asset catalog" +type: design +status: active +--- + +# Effects — Visual Assets + +Shaders, particles, and overlays — the visual-effects layer distinct from +models/textures/artwork/icons. Includes both 3D-scene shaders (toon, +recolor-mask, outline) and 2D `CanvasLayer` overlays (fog, cognitive-delay +cues). + +## Generation Approach + +Hand-authored GDShader files, proven in the 3dpipeline and quaternius- +aesthetic spikes (`toon.gdshader`, `toon_masked.gdshader`, +`outline.gdshader`, `toon_garment.gdshader` — see +`docs/architecture/character-asset-organization.md` Sections 2 and 11 for +the shader family already in production use under +`client/assets/characters/shaders/`). New effects (particles, screen-space +overlays) are authored per-need; there is no generation pipeline analogous to +`/glb-gen`/`/sprite-gen` for this category. + +## Assets + +| ID | Filename | Status | Type | Notes | Sprint/Ticket | +|----|----------|--------|------|-------|---------------| + +This table intentionally excludes the character shader family already +tracked and documented in `character-asset-organization.md` (`toon.gdshader`, +`toon_masked.gdshader`, `outline.gdshader`, `toon_garment.gdshader`) — that +doc is their canonical home. This file catalogs effects/particles/overlays +that don't already have a home elsewhere; none exist yet (T-1050 initial +creation). + +## Cross-References + +- `docs/architecture/character-asset-organization.md` — character shader family (canonical home, not duplicated here) +- [D-244](../../../governance/decisions/architecture.md#d-244-asset-rendering--3d-objects-in-world-2d-limited-to-textures--flat-artwork) diff --git a/docs/assets/visual/icons.md b/docs/assets/visual/icons.md new file mode 100644 index 000000000..982831d88 --- /dev/null +++ b/docs/assets/visual/icons.md @@ -0,0 +1,50 @@ +--- +title: "Icons — Visual Assets" +description: "UI icon asset catalog — as-built inventory of client/assets/icons/" +type: design +status: active +--- + +# Icons — Visual Assets + +UI icons and status indicators. 2D, UI layer only — not part of the D-244 3D +in-world pivot (icons render on `CanvasLayer`, not in the 3D scene). + +## Generation Approach + +Hand-authored SVGs, tinted at runtime via `res://shaders/icon_tint.gdshader` +(see `client/ui/stance_indicator.gd` for the tint-shader integration +pattern). + +## Assets + +13 SVGs already exist in `client/assets/icons/` — this is the as-built +inventory, not a zero-start table. + +| ID | Filename | Status | Category | Notes | Sprint/Ticket | +|----|----------|--------|----------|-------|---------------| +| ICO-001 | `icon_stance_walk.svg` | final | Stance | Wired into `client/ui/stance_indicator.gd` (`StanceIndicator` icon map), tinted via `icon_tint.gdshader`. | — | +| ICO-002 | `icon_stance_sprint.svg` | final | Stance | Wired into `stance_indicator.gd`. | — | +| ICO-003 | `icon_stance_careful.svg` | final | Stance | Wired into `stance_indicator.gd`. | — | +| ICO-004 | `icon_stance_crouch.svg` | final | Stance | Wired into `stance_indicator.gd`. | — | +| ICO-005 | `icon_action_examine.svg` | placeholder | Action | SVG exists; not yet wired into a script/scene. | — | +| ICO-006 | `icon_action_follow.svg` | placeholder | Action | SVG exists; not yet wired into a script/scene. | — | +| ICO-007 | `icon_action_observe.svg` | placeholder | Action | SVG exists; not yet wired into a script/scene. | — | +| ICO-008 | `icon_action_talk.svg` | placeholder | Action | SVG exists; not yet wired into a script/scene. | — | +| ICO-009 | `icon_item_access_token.svg` | placeholder | Item | SVG exists; not yet wired into a script/scene. | — | +| ICO-010 | `icon_item_comm_log.svg` | placeholder | Item | SVG exists; not yet wired into a script/scene. | — | +| ICO-011 | `icon_item_manifest.svg` | placeholder | Item | SVG exists; not yet wired into a script/scene. | — | +| ICO-012 | `icon_status_health.svg` | placeholder | Status | SVG exists; not yet wired into a script/scene. | — | +| ICO-013 | `icon_status_perception.svg` | placeholder | Status | SVG exists; not yet wired into a script/scene. | — | + +`Status` here follows the shared vocab (`docs/assets/README.md`): +`final` = shipped and wired into a consuming script/scene; `placeholder` = +asset exists on disk but has no confirmed runtime consumer yet. If a +teammate wires one of the placeholder icons into a script, flip its row to +`final` and note the consumer, matching the `icon_stance_*` rows above. + +## Cross-References + +- `client/assets/icons/` — the SVG files this table catalogs +- `client/ui/stance_indicator.gd` — the one confirmed consumer (stance icons) +- `res://shaders/icon_tint.gdshader` — runtime tint shader diff --git a/docs/assets/visual/models.md b/docs/assets/visual/models.md new file mode 100644 index 000000000..03ef17a5e --- /dev/null +++ b/docs/assets/visual/models.md @@ -0,0 +1,48 @@ +--- +title: "Models — Visual Assets" +description: "3D object/furniture/prop asset catalog — status table for the Trellis glb-gen pipeline" +type: design +status: active +--- + +# Models — Visual Assets + +3D objects and furniture/props rendered directly in-world as `.glb` meshes +(D-244 — no per-object sprite layer). Produced via the Trellis image→3D +pipeline (`/glb-gen`), promoted per D-241 into `client/assets/models/`. + +## Generation Approach + +- **Concept image:** `/image-gen` produces a concept PNG (square, plain dark + background, single object centered, 3/4 front view — see + `.claude/skills/glb-gen/SKILL.md` "Input Requirements"). +- **Image → 3D:** Trellis (`tower-of-joy:11510`) converts the concept image + to a raw `.glb` (`/glb-gen`). +- **Post-process:** Blender normalizes scale, centers the mesh, generates the + recolor mask sidecar, and sets up materials for toon rendering + (`.claude/skills/glb-gen/scripts/postprocess_glb.py`). +- **Promotion:** the finished `.glb` (+ mask, if tintable) lands in + `client/assets/models/{furniture,props}/` and is registered in + `client/assets/models/manifest.json` — that registration + placement act + **is** the promotion (D-241); it does not stay a "promoted" spike artifact. + +Naming, mask-sidecar, and multi-tile footprint conventions: +[conventions.md](conventions.md). + +## Assets + +| ID | Filename | Status | Category | Footprint | Notes | Sprint/Ticket | +|----|----------|--------|----------|-----------|-------|---------------| + +No models have entered production yet — this table is intentionally empty at +initial creation (T-1050). Rows are added as assets are promoted into +`client/assets/models/manifest.json`; keep this table's `Status` column in +sync with the manifest's `status` field (shared vocab: planned / +in-progress / placeholder / final, `docs/assets/README.md`). + +## Cross-References + +- [conventions.md](conventions.md) — naming, mask sidecars, footprint metadata +- `client/assets/models/manifest.json` — the machine-readable per-asset registry +- `.claude/skills/glb-gen/SKILL.md` — production pipeline +- [D-244](../../../governance/decisions/architecture.md#d-244-asset-rendering--3d-objects-in-world-2d-limited-to-textures--flat-artwork), [D-241](../../../governance/decisions/process.md#d-241-spike-binary-artifacts-are-local-only--gitignored-never-tracked) diff --git a/docs/assets/visual/textures.md b/docs/assets/visual/textures.md new file mode 100644 index 000000000..3ea1a6e29 --- /dev/null +++ b/docs/assets/visual/textures.md @@ -0,0 +1,42 @@ +--- +title: "Textures — Visual Assets" +description: "PBR surface map asset catalog for 3D model and building-exterior surfaces" +type: design +status: active +--- + +# Textures — Visual Assets + +PBR surface maps (BaseColor / Normal / Roughness) applied to 3D model and +building-exterior surfaces. The only flattened 2D content besides flat +artwork (D-244) — a texture has no "direction," only map type. + +## Generation Approach + +- **Building-exterior surfaces:** named by the ratified ObjectTag vocabulary + (D-235 amendment, T-995, `wiki/economics/object_tag_vocabulary.toml`) — see + [conventions.md](conventions.md) §3 for the naming convention and the + fallback-hierarchy note (every specific tag degrades to a `generic_*` + parent until its specific texture ships). +- **Model surfaces:** Trellis bakes a texture atlas directly onto the mesh + during `/glb-gen` (no separate texture-authoring step for most + furniture/props) — see [models.md](models.md). This file tracks + **standalone** PBR maps: building-exterior materials and any surface asset + authored or generated independent of a specific model. + +## Assets + +| ID | Filename | Status | Map Type | ObjectTag | Notes | Sprint/Ticket | +|----|----------|--------|----------|-----------|-------|---------------| + +No textures have entered production yet — this table is intentionally empty +at initial creation (T-1050). Per D-235, the texture library itself is +Phase 5+/post-launch work (behind the fallback chain); this file exists now +so the catalog structure is in place ahead of that work landing. + +## Cross-References + +- [conventions.md](conventions.md) — D-235-keyed texture naming +- [models.md](models.md) — model-baked textures (Trellis atlas bakes) +- [D-235](../../../governance/decisions/architecture.md#d-235-building-exterior-visual-grammar-and-material-vocabulary) — ObjectTag vocabulary + fallback hierarchy +- `wiki/economics/object_tag_vocabulary.toml` — the ratified tag registry From 7d5a774fa5274a477819e2d8f57060eb44510527 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sat, 25 Jul 2026 20:20:21 +0200 Subject: [PATCH 3/9] docs(assets): Phase-4 visual style bible in palette.md (T-1052) Authors the full style bible: camera table separating D-148 gameplay from D-019 offline-renderer use; the delegated toon-vs-PBR ruling (environment props share the character toon treatment, with a minimal-PBR carve-out for glass/polished metal so sightlines read truthfully under occlusion-based perception); a color/material register for every D-235 ObjectTag (hue, grain, D-217-keyed weathering); and explicit supersession notes both ways with visual-grammar-v01.md and the mood-board workshop transcript. Includes branch-side pql changelog rows (T-1050/T-1052 -> review); the worktree pre-commit export step failed benignly (write-through had already refreshed the files) so they are staged explicitly here. Co-Authored-By: Claude Fable 5 --- .pql/changelog/ticket_history/2026-07.sql | 1 + .pql/changelog/tickets/2026-07.sql | 49 +++ docs/assets/visual/palette.md | 287 +++++++++++++++++- docs/design/visual-grammar-v01.md | 12 +- .../workshop-outcomes.md | 11 + 5 files changed, 349 insertions(+), 11 deletions(-) diff --git a/.pql/changelog/ticket_history/2026-07.sql b/.pql/changelog/ticket_history/2026-07.sql index ef90fa3e7..daf72201c 100644 --- a/.pql/changelog/ticket_history/2026-07.sql +++ b/.pql/changelog/ticket_history/2026-07.sql @@ -2322,3 +2322,4 @@ INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, chang INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPV39JX54HYP83RZJFDM', 'status', 'backlog', 'in_progress', NULL, '2026-07-25 18:11:42', '2026-07-25 18:11:42.948', '2026-07-25 18:11:42.948', NULL, '613156e91577043373205250f21a72cd', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPJ910NNR5T2R9F9NWQR', 'assigned_to', NULL, 'justine', NULL, '2026-07-25 18:11:45', '2026-07-25 18:11:45.571', '2026-07-25 18:11:45.571', NULL, '7fe3851d234f1cafb2b4fc70c1f5eed8', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPV39JX54HYP83RZJFDM', 'assigned_to', NULL, 'araminta', NULL, '2026-07-25 18:11:46', '2026-07-25 18:11:46.095', '2026-07-25 18:11:46.095', NULL, '0d1336d72b0807fe85c054696e914350', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPV39JX54HYP83RZJFDM', 'status', 'in_progress', 'review', NULL, '2026-07-25 18:17:34', '2026-07-25 18:17:34.364', '2026-07-25 18:17:34.364', NULL, 'a903761710732a5f62b742cc7ca4acac', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/tickets/2026-07.sql b/.pql/changelog/tickets/2026-07.sql index 927168e82..07733599e 100644 --- a/.pql/changelog/tickets/2026-07.sql +++ b/.pql/changelog/tickets/2026-07.sql @@ -3568,3 +3568,52 @@ archiving/marking those superseded, or by palette.md stating exactly what it inh Lead note (batch-5 activation): the toon-vs-PBR treatment call for environment props is delegated to araminta (art seat) this batch — her ruling lands in palette.md with rationale; surfaced to Jeroen in the batch report for cheap veto. VERDICT: READY.', 'in_progress', 'medium', 'araminta', 'visual', 'D-235', '2026-06-12 10:40:59', '2026-07-25 18:11:46.095', NULL, '4df3e92b2bc003a7b73f860dd47704f2', 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 ('06FBPTXPV39JX54HYP83RZJFDM', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Write the Phase-4 visual style bible into docs/assets/visual/palette.md', '(description follows in first append) + +Refinement (batch-5 Si pass, 2026-07-25) — READY. + +CURRENT STATE: docs/assets/visual/palette.md exists as a STUB (correctly re-scoped by D-244 — no +sprite-era content survives in it) — 4 bullets: camera angle (D-019, now superseded by D-148 for +gameplay — palette.md''s bullet is stale and must be corrected), entity colors (D-033), environmental +neutrality (D-045), "functional warmth." It explicitly defers to "art direction workshop synthesis and +first visual sprint" — this ticket IS that synthesis pass for Phase 4. + +WHAT D-235 ALREADY FIXES (do not re-litigate): the building-exterior material/roof/facade/street +vocabulary (WallMaterial/RoofForm/FacadeRhythm/StreetSurface, ratified as the 28-template +architecture_trait_catalog.toml / wiki/economics/object_tag_vocabulary.toml) and the fallback-to-generic +mechanism. D-235 is the LOGICAL token layer ("what material is this wall") — Phase 4+ scope per its own +Implementation note ("textured render + the bulk of the theme library are Phase 5+"). T-1052 is the +RENDERED-LOOK layer on top: given a WallMaterial token, what does concrete_wall actually look like +(hue range, texture grain, weathering baseline) when art is eventually produced against it. + +WHAT T-1052 MUST STILL AUTHOR (net-new, no existing decision covers this): + - Correct the camera-angle bullet: D-148 (30 deg low-angle Camera3D, supersedes D-019) is the GAMEPLAY + camera; D-019''s -72.5 deg survives only for the offline renderer (2D-artwork / retired sprite mode, + per renderer/README.md). palette.md''s current bullet conflates these — fix it to name both and + which applies where. + - A concrete color/material register for the D-235 token vocabulary: not new tokens, but the RANGE + each token maps to (e.g. "stone_wall reads as warm grey-tan, not cool grey" / "corrugated_metal + weathers to rust-orange, not staying silver") — the thing D-235 explicitly parked for Phase 5+ art + production but which a style bible can pre-specify so future asset generation has a target. + - Reconcile with docs/design/visual-grammar-v01.md and docs/workshops/art-direction-mood-board/ + workshop-outcomes.md (both pre-cascade, v0.1-era, sprite-framed) — extract what survives (color + relationships, "functional warmth" identity, D-033 palette) vs what''s superseded by D-235/D-244 + (era-based material ladder — explicitly retired by D-235''s own text: "Era is NOT a material filter + here"). Do not leave two contradictory style documents live without a note in one pointing at the + other''s supersession. + - Texture "grain"/rendering treatment for the D-244 3D-object reality: since objects are lit 3D meshes + now (not flat sprite fills), the style bible should say something about the toon/PBR balance — + precedent exists in client/assets/characters/shaders/ (toon.gdshader, toon_masked.gdshader, + outline.gdshader) and the glb-gen postprocess convention (roughness=1.0/specular=0.0 "for toon + compatibility" per .claude/skills/glb-gen/SKILL.md''s setup_materials() note). Confirm whether + environment props share this toon treatment with characters, or diverge — this is a real open + question the style bible should answer, not assume. + +ACCEPTANCE: docs/assets/visual/palette.md is no longer a stub; camera-angle bullet corrected; +color/material register for D-235 tokens documented; toon-vs-PBR treatment for props stated; +supersession relationship to visual-grammar-v01.md / workshop-outcomes.md is explicit (either by +archiving/marking those superseded, or by palette.md stating exactly what it inherits from each). + +Lead note (batch-5 activation): the toon-vs-PBR treatment call for environment props is delegated to +araminta (art seat) this batch — her ruling lands in palette.md with rationale; surfaced to Jeroen in +the batch report for cheap veto. VERDICT: READY.', 'review', 'medium', 'araminta', 'visual', 'D-235', '2026-06-12 10:40:59', '2026-07-25 18:17:34.364', NULL, '580bf084c1ac42555f672f44b96b6b4a', 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); diff --git a/docs/assets/visual/palette.md b/docs/assets/visual/palette.md index a33965ab8..6bb9cb177 100644 --- a/docs/assets/visual/palette.md +++ b/docs/assets/visual/palette.md @@ -1,23 +1,290 @@ --- title: "Visual Palette" -description: "Art direction guide covering camera angle, entity colours, environmental neutrality, and functional warmth" +description: "Phase-4 visual style bible — camera, entity colours, environmental neutrality, D-235 material register, toon/PBR rendering treatment" type: design status: active +decision_refs: [D-019, D-033, D-045, D-148, D-217, D-232, D-235, D-244] +author: "Araminta" +updated: 2026-07-25 --- # Visual Palette -Status: **Stub** — reference art direction workshop output for current guidance. +Status: **Active** — Phase-4 style bible (T-1052). This document is the current +source of truth for rendered-look art direction. It supersedes the framing (not +every individual data point) of the two documents listed in +[Superseded documents](#superseded-documents) below. -See `docs/workshops/art-direction-mood-board/` for the established visual identity. +--- -## Key Decisions +## 1. Camera -- **Camera angle:** 15-20deg from vertical ("the angle"), rendered via orthographic Camera3D at -72.5deg from horizontal (D-019) -- **Entity colors:** Relationship-based per D-033 (green = known/friendly, amber = neutral, red = hostile, etc.) -- **Environmental neutrality:** Spaces don't visually shift with narrative state (D-045) -- **Functional warmth:** Industrial infrastructure that people made livable — not military, not luxury +Two camera records exist and apply to **different surfaces** — this was +previously conflated in this document; corrected here. -## Style Guide +| Camera | Angle | Where it applies | Status | +|--------|-------|-------------------|--------| +| **D-148** | 30° low-angle `Camera3D` tilt (−30° from horizontal in the confirmed preset convention — see D-148's 2026-07-06 editorial note), 45° static map rotation | **Gameplay** — the default in-world camera, Phase 5+ | Active, confirmed default | +| **D-019** | −72.5° from horizontal (midpoint of the old "15–20° from vertical" sprite-tilt range) | **Offline renderer only** — the retired 2D-sprite production pipeline (`renderer/`, now repurposed per D-244 as the flat-2D-artwork generator; see `renderer/README.md`) | Superseded for gameplay by D-148; survives only as an offline-render setting | -To be populated from art direction workshop synthesis and first visual sprint. +**Why both survive:** D-148 replaced D-019 as the *gameplay* camera when the +project pivoted to live 3D rendering (D-149) and then to fully 3D objects +(D-244) — there are no more pre-rendered sprites to view "at the angle" during +play. D-019's −72.5° Camera3D setting doesn't disappear, though: it is still +the correct setting for the **offline** render rig that produces flat 2D +artwork (paintings, flags, billboards, signage — the D-244(b) content class), +because that pipeline still renders a 3D scene down to a flat texture and +needs a camera angle to do it. Two different cameras, two different jobs: +one drives what the player sees, the other bakes a flat image asset. + +Do not describe D-019's angle as "the gameplay camera" anywhere — that +sentence is what this document is correcting. + +--- + +## 2. Rendering Treatment — Toon vs. PBR (delegated ruling) + +**Ruling (Araminta, delegated by team lead this batch — surfaced to Jeroen for +veto in the batch report):** + +**Environment props and furniture share the character toon treatment.** One +shader family, one look, across the whole in-world scene — with a single, +narrow, explicitly-named exception for glass and polished/mirror-finish metal, +which get a distinct minimal-PBR material rather than being forced through +the flat toon response. + +### 2.1 What "sharing the toon treatment" means concretely + +- Environment `.glb` models use the same shader family as characters: + `toon.gdshader` (flat lit/shadow-band ALBEDO, no PBR lighting response) or + `toon_masked.gdshader` (recolor-mask variant, for props that need + runtime tint — e.g. a favorite-color object, a faction-liveried crate) + where a tintable variant is needed. `outline.gdshader` (inverted-hull) applies + to entities and to any prop the visual hierarchy calls out for emphasis (see + §4 tie-in below) — not universally to every static prop, matching the + existing entity > object > structure outline-weight hierarchy. +- glb-gen's postprocess convention of forcing `roughness=1.0` / `specular=0.0` + "for toon compatibility" (`postprocess_glb.py::setup_materials()`) is + **correct and should stay the default** for furniture, crates, signage, + street furniture, wall/roof/facade instances — i.e. the D-235 ObjectTag + vocabulary's bulk. These are flat-lit like characters; PBR specular + highlights on a stucco wall or a wooden table would read as wet/glossy and + contradict the toon language players already learn from characters. +- **Exception — glass and polished/mirror metal:** `precision_glass`/ + `glass_curtain_wall` (facade), `smart_facade`, and any deliberately + reflective/polished metal prop (chrome fixture, mirror, glossy display + case) get a **separate minimal-PBR material**: `roughness` in the 0.05–0.2 + range and non-zero `specular`/`metallic` as appropriate, layered so the toon + shadow-band lighting model still governs the base surface response but a + specular highlight and partial reflection are allowed through. This is not + a full PBR material (no environment-map reflections, no fresnel-driven + rim lighting stack) — it is "toon base + a controlled specular term," + the smallest change that makes glass/chrome read as glass/chrome instead + of matte plastic. + +### 2.2 Rationale + +- **Visual grammar principle (mine, standing):** consistency compounds. A + station built from one shader family reads as one coherent object, the way + a real Rimworld or Hades scene does. Mixing a toon character against a + PBR-lit room breaks the "one world" read the moment the player looks at + their own hands next to a table. +- **The postprocess default already assumes this.** `setup_materials()`'s + comment ("for toon compatibility") was written for the character pipeline + but Trellis is the *same* generator for environment `.glb`s — there was + never a second postprocess path. Diverging environment props onto full PBR + would mean either forking the postprocess script or hand-overriding every + environment asset's material after generation. Sharing the treatment means + the existing pipeline is already correct and needs no new work. +- **The exception is functionally forced, not a taste call.** A flat-lit + `roughness=1.0` glass pane doesn't read as glass — it reads as a frosted + grey panel, which actively misleads the player about sightlines (can I see + through this?) in a game whose core mechanic is occlusion-based perception. + Where "can the player see through/into this" is gameplay-load-bearing + (`glass_curtain_wall`, `precision_glass`, shop windows, interior partitions), + the material needs to look transparent/reflective enough to answer that + question at a glance. This is a narrow, named carve-out — it does not + reopen "should props be PBR," it closes the one case where toon-only + actively lies to the player. +- **Scale check:** this holds at boxes-with-labels (a labelled box has no + material response either way, so the ruling is inert today) and at full + art fidelity (the shader assignment is a material-slot choice per asset, + not a structural decision that needs revisiting later). + +### 2.3 What this does NOT change + +- D-235's material/token vocabulary (§3) is unaffected — this ruling is about + shader/material *response*, not which WallMaterial/RoofForm tokens exist. +- The character shader files (`toon.gdshader`, `toon_masked.gdshader`, + `toon_garment.gdshader`, `outline.gdshader`) are not modified by this + ruling — environment props reuse them as-is or via a near-identical + environment-scoped shader variant if slot/uniform needs differ + (implementation detail for whoever writes the first environment shader, + not an art-direction fork). + +--- + +## 3. Color / Material Register — D-235 ObjectTag Vocabulary + +D-235 (amended 2026-07-07, T-995) ratified the **logical** token vocabulary — +`wiki/economics/object_tag_vocabulary.toml`, 4 axes (wall/roof/facade/street), +32 specific tags + 4 generic fallback parents. D-235's own Implementation note +explicitly parks the **rendered look** of those tokens for "Phase 5+ and +post-launch" art production. This section is that pre-specification: not new +tokens, but the register each shipped tag should render into once art exists, +so asset generation has a target from day one instead of drifting per-artist. + +**How to read this table:** "Hue range" is a rendered-look target for the +BaseColor map's dominant tone, expressed relative to D-235's existing rule +that *color is template-bounded* (a cultural palette cue, seed-selected within +range, "always within the template's register") — this section does not +override that; it gives the physically-plausible hue family each material +naturally sits in, which the template's seed then picks within. "Weathering +baseline" describes how the D-217 condition bands (Intact/Worn/Cracked/Broken, +driven by `prosperity_score`) should visually read for that material — i.e. +what "Worn" looks like on brick vs. on corrugated metal. + +### 3.1 Wall materials + +| Tag | Hue range | Grain / texture character | Weathering baseline (D-217 bands) | +|-----|-----------|---------------------------|-----------------------------------| +| `concrete_wall` | Neutral grey, warm-leaning (`#8a8680`–`#6e6a64`) — never blue-grey/cool-grey | Fine aggregate speckle, subtle formwork seams | Intact: even matte. Worn: rain-streak staining, hairline cracks. Cracked: spalling at edges, exposed aggregate. Broken: structural cracking, rebar-stain bleed. | +| `steel_frame` | Dark warm charcoal (`#3a3632`) frame, infill panel per adjacent tag | Visible bolted/riveted joints, slight surface oxidation tint even when "new" | Intact: factory-primer flat. Worn: surface rust bloom at joints/edges only. Cracked: running rust streaks below joints. Broken: structural rust-through, buckling. | +| `brick_wall` | Warm terracotta-to-ochre (`#9c5a3c`–`#c48a52`), never cool/pink brick | Visible coursing + mortar joint shadow, slight color variance per brick (batch variation) | Intact: crisp mortar lines. Worn: mortar erosion, efflorescence (white mineral bloom). Cracked: spalled brick faces, missing mortar. Broken: collapsed coursing, exposed cavity. | +| `rendered_wall` | Whatever the template's color_range picks — this tag is a **finish**, not a hue driver; render surface is smooth and matte regardless of picked hue | Smooth, minimal grain, occasional hairline render-crack pattern at Worn+ | Intact: clean flat. Worn: surface staining/streaking under sills and joints. Cracked: render cracking, patches of exposed substrate. Broken: large render loss, substrate fully exposed. | +| `stone_wall` | Warm grey-tan (`#a89e8c`–`#8c8172`) — **explicitly not cool grey**; this is the D-235 fallback-graph's flagship "gets it wrong if literal" case | Visible coursing, natural stone color variance tile-to-tile, tool-marked or rough-cut surface depending on template | Intact: clean-cut edges. Worn: lichen/mineral staining in shadowed recesses. Cracked: mortar loss between courses, chipped arrises. Broken: displaced/missing stone units. | +| `timber_wall` | Warm mid-brown (`#7a5c3e`–`#9c7a52`), never grey-driftwood unless template explicitly calls for weathered/coastal register | Visible grain direction, board/plank joint lines | Intact: sealed/painted finish. Worn: graying at exposed edges, finish wear. Cracked: splitting along grain, missing sealant. Broken: rot, structural failure at joints. | +| `stucco_wall` | Warm off-white to sand (`#d8cfc0`–`#c4b89e`) — whitewashed per the tag description, not pure white | Smooth, slight hand-applied texture variance | Intact: clean whitewash. Worn: staining streaks below any protrusion. Cracked: hairline cracking, patch-color mismatch. Broken: large loss exposing substrate. | +| `glass_curtain_wall` | Neutral-to-cool tint on the glass itself (`#c8d4dc` @ low opacity) — see §2.1 PBR exception, this tag needs the specular/transparency carve-out to read correctly | Minimal — reflection/transparency IS the surface character | Intact: clean, full reflectivity. Worn: dust film, reduced reflectivity. Cracked: visible cracked panes, some opacity/frosting. Broken: missing panes, boarded sections. | +| `composite_panel` | Whatever the template picks — this is a **prefab finish** tag like `rendered_wall`, hue is template-driven, not material-driven | Flat, uniform, visible panel-seam grid (regular rhythm, unlike masonry coursing) | Intact: crisp seams, clean finish. Worn: seam staining, minor panel discoloration. Cracked: panel warping, seam gaps. Broken: missing/detached panels. | +| `rammed_earth_wall` | Warm ochre-to-umber banding (`#8a6a48`–`#6e5236`) | Strong horizontal layer banding (the construction method's signature texture) | Intact: crisp banding. Worn: surface erosion softening band edges. Cracked: vertical cracking across bands. Broken: section collapse/erosion loss. | + +### 3.2 Roof forms + +| Tag | Hue range | Grain / texture character | Weathering baseline | +|-----|-----------|---------------------------|----------------------| +| `flat_roof` | Neutral dark grey (`#4a4844`) membrane | Flat, minimal detail, visible seam lines on membrane roofing | Intact→Broken: ponding stains, membrane cracking, vegetation growth at Broken. | +| `pitched_roof` | Warm dark grey-brown (`#4e463e`) generic shingle/slate register unless a specific tag (below) narrows it | Directional shingle/slate coursing | Moss/lichen accumulation in shadowed pitches as condition drops; missing units at Cracked+. | +| `corrugated_roof` | Weathers to **rust-orange** (`#a65c34` at Worn+), starts galvanized silver-grey (`#a8aaa8`) when Intact — **explicitly does not stay silver**, this is D-235's own worked example | Strong linear corrugation shadow pattern | Intact: clean galvanized sheen. Worn: rust bleeding from fastener points. Cracked: broad rust staining, sheet lifting at edges. Broken: rust-through holes, missing sheets. | +| `clay_tile_roof` | Warm terracotta (`#b56a3e`–`#c8825a`) | Individual tile coursing, slight per-tile hue variance | Moss/lichen in valleys, cracked/slipped/missing tiles as condition drops. | +| `terraced_roof` | Match the deck surface (paved/planted per template), not a distinct roof hue | Flat deck texture, visible rail/parapet line | Surface wear on deck material, planter/rail deterioration at lower bands. | +| `vaulted_roof` | Match wall material register (masonry vaults read as an extension of the wall) | Strong directional shadow from the vault curvature | Standard masonry weathering (see stone/brick wall rows). | +| `green_roof` | Vegetated green, seasonally variable — the one roof tag where hue is intentionally NOT template-locked | Organic, irregular, canopy-like from above | Condition reads as vegetation health/coverage, not surface damage — sparse/patchy at low prosperity, not "broken" in the structural sense. | + +### 3.3 Facade rhythms + +Facade tags are about **pattern**, not hue — they inherit the wall tag's +color register. Grain/weathering guidance: + +| Tag | Grain / texture character | Weathering note | +|-----|---------------------------|-------------------| +| `regular_facade` | Even punched-window grid, no added ornament | Weathers uniformly with the wall tag beneath it. | +| `ornamental_facade` | Raised/carved detail catches shadow distinctly from the flat wall plane | Ornament erodes/chips *before* the flat wall does — detail loss is the earliest visible sign of Worn. | +| `industrial_glazing` | Regular steel-mullion grid, factory-sash proportions (wide, short panes) | Mullion rust (if `steel_frame`) precedes glazing damage. | +| `arcade_facade` | Strong repeating vertical shadow rhythm from the colonnade | Ground-level wear concentrates at column bases (foot traffic), not evenly. | +| `shuttered_facade` | Shutters as a distinct, often higher-saturation accent color against the wall's muted register — this is the one facade tag allowed a saturation bump (bounded by §5's object-tier ceiling below) | Shutter paint fades/peels faster than the wall behind it — a secondary, faster weathering clock on the same building. | +| `screen_facade` | Fine repeating perforation/louvre pattern, strong dappled shadow | Screen material (usually metal or timber lattice) weathers per its own material, independent of the wall plane behind. | +| `colonnade` | Freestanding column rhythm, deep shadow gaps between wall plane and columns | Column bases weather fastest (ground contact, foot traffic) — same principle as arcade_facade. | +| `lattice_screen` | Fine timber/metal lattice, mashrabiya-register — highest facade information-density texture in the set | Individual lattice elements can go missing at Cracked+ without the whole screen reading as "broken." | + +### 3.4 Street surfaces + +| Tag | Hue range | Grain / texture character | Weathering baseline | +|-----|-----------|---------------------------|----------------------| +| `paved` | Neutral grey (`#66645e`) | Fine asphalt/poured texture, minimal joint pattern | Cracking, pothole pattern, patch-color mismatch (repairs never quite match original pour age). | +| `cobble` | Warm grey-tan, matches `stone_wall`'s register | Strong individual-unit texture, visible mortar/sand joints | Displaced/missing setts at low condition, not surface cracking (it's a unit system, not a monolithic pour). | +| `packed_earth` | Warm ochre-brown (`#7a6248`) | Irregular, rutted, no joint pattern | Erosion channels, puddling, vegetation encroachment at edges as condition drops (or rises — an unmaintained packed-earth street can look "worse" toward Intact-adjacent if it's simply less used, this tag is condition-light). | +| `canal_way` | Water surface — hue driven by reflection/sediment, not a fixed material hue | N/A — water plane, not a solid surface | Water clarity/debris accumulation stands in for the condition ladder here. | +| `elevated_walkway` | Match structural material used (steel_frame/timber/composite per template) | Grated or planked, visible support structure below | Structural weathering per the material tag, plus grate/plank gap wear from foot traffic. | +| `heavy_haul` | Neutral dark grey (`#4e4c48`), reinforced-surface register | Deep rut/track wear pattern, wider joint spacing than `paved` | Track-line wear concentrates on wheel paths, not the full surface — an asymmetric wear pattern unlike `paved`'s even cracking. | +| `boardwalk` | Warm grey-brown weathered timber (`#8a7a64`), coastal/wetland register — assume some grey weathering even at Intact, unlike `timber_wall` | Plank coursing, visible gap joints | Individual plank replacement creates natural color patchwork even at high condition — this is the one street tag where slight inconsistency reads as *authentic*, not neglected. | + +### 3.5 Generic fallback placeholders + +`generic_wall` / `generic_roof` / `generic_facade` / `generic_street` render +as a **flat, neutral, low-saturation grey** (`#6e6c68`, roughly the mean of +the specific tags' hue ranges above) with **no grain and no weathering +variation** — condition bands still apply structurally (a generic-fallback +wall can still be "Broken" in silhouette/damage terms) but carry no material +texture, so the player reads "this is a wall, its specific material hasn't +loaded yet" rather than mistaking it for a deliberate concrete/stone choice. +This is the visual expression of D-235's fallback chain — a placeholder must +look *unfinished*, not like a legitimate flat concrete building, or its +eventual upgrade-in-place would read as an inconsistent hue reskin instead of +"detail arriving." + +--- + +## 4. What Survives from Prior Documents + +Per D-244 (2026-06-16) and this ticket's acceptance criteria, the following +principles from the pre-cascade documents are **inherited**, restated here in +current (3D, D-235-token) terms rather than the old (2D sprite, era-ladder) +terms they were originally written in: + +| Inherited principle | Original source | Current expression | +|----------------------|------------------|----------------------| +| **D-033 entity color = relationship to player** | visual-grammar-v01.md §3 | Unchanged — still the live decision, still the single most important color system in the game. Applies to entities regardless of render fidelity (rectangle, sprite, or 3D mesh). | +| **"Functional warmth"** — industrial infrastructure that people made livable, not military/not luxury | workshop-outcomes.md visual identity statements + Gore's naming | Standing mood target for the whole D-235 register above: hue ranges lean warm (terracotta, ochre, warm grey) rather than cool/clinical by default; the *cool* end of the palette is reserved for deliberately institutional template registers, not the default. | +| **Environmental neutrality — strict zero shift (D-045)** | workshop-outcomes.md §1.10 | Unchanged, still active. The D-235 material register above is diegetic/static — condition bands (D-217) shift with simulated `prosperity_score`, never with narrative/investigation state. This document adds no new exception to D-045. | +| **Entity always wins visual ties** (visual hierarchy: entity > object > structure) | visual-grammar-v01.md §1.4 / workshop-outcomes.md §1.4 | Survives as a rendering-order and saturation-hierarchy principle. In 3D terms: entity materials/outlines render at the highest effective saturation and are never occluded in a way that reads ambiguous against a similarly-toned prop. The saturation ceiling table (below) replaces the old sprite-outline-weight table. | +| **Object favorite-color saturation ceiling** | visual-grammar-v01.md §3.6 | Restated: entity colors (D-033) remain the highest-saturation elements in any scene; object/prop colors (including the `shuttered_facade` accent exception in §3.3) stay moderate; structure/material register (§3) stays lowest. Same three-tier ceiling, now anchored to D-235 tags instead of "Era 1/2/3" object palettes. | +| **Readability over beauty** | workshop-outcomes.md Principle 1 | Standing design principle, unchanged, applies to 3D rendering exactly as it applied to sprites. | +| **Lighting-driven atmosphere, not baked mood** | workshop-outcomes.md Principle 2 / visual-grammar-v01.md's "sprites are shape templates" | Re-expressed in §2 above: toon shading gives shape + local shadow-band response; scene lighting (not baked texture darkening) still carries mood. The specific mechanism (Light2D CanvasModulate) is retired with the sprite pipeline, but the principle — materials stay neutral, the lighting rig does the emotional work — carries forward to whatever 3D lighting setup Phase 5 builds. | + +--- + +## 5. Superseded Documents + +Two earlier documents overlap this one. Both are **explicitly marked +superseded** here (and should carry a pointer back to this section) so no two +contradictory style documents stay live at the same time: + +- **`docs/design/visual-grammar-v01.md`** — already carries its own + superseded banner (2026-06-12, pointing at D-235/D-228 + + character-visuals-spec.md). That banner is correct and unchanged by this + ticket. What's **specifically superseded**: the entire sprite-era + production model (§2 entity sprite dimensions, §4 z-level stack as sprite + layers, §5 typography-for-sprite-canvas sizing, §6 two-tier animation + frame-count tables, §9 resolution chain / Nano Banana workflow, and the + **three-construction-era material ladder** (§1.2–§1.4's Era 1/2/3 zone + palettes) — the era ladder is the exact thing D-235 retired by name + ("Era is NOT a material filter here"). What's **not superseded, just + relocated**: D-033 entity color values and D-045 environmental neutrality, + both restated current in §4 above. +- **`docs/workshops/art-direction-mood-board/workshop-outcomes.md`** — this + is a workshop transcript (type: workshop, status: archived) and correctly + stays archived as a historical record; it is **not** live style guidance + and should not be read as such. Its **candidate decisions** (D-042 through + D-051 as originally proposed in §7) were formally recorded as the + perception.md decisions cited throughout this document (D-033, D-043, + D-044, D-045, D-046 etc.) — those decision records are the living + artifact, not the workshop prose. Its **three-construction-era material + system** (§1.8) is superseded by D-235 for the same reason as above. Its + **camera angle content** (§1.2, §9.3) is superseded by D-148 per §1 above. + Everything else in it (lighting three-reference model, fog cone shape, + two-tier animation, insert overlay bloom spec, z-stack layer *concept* — + not its sprite-specific implementation detail) remains valid design + intent, inherited into current decisions rather than into this document + directly. + +**Going forward:** this document (`palette.md`) is the single live style +bible for Phase 4+ rendered look. New style guidance should land here, not in +a new parallel document, unless it's genuinely a different concern (e.g. the +model-naming/manifest conventions living in `docs/assets/visual/conventions.md`, +authored separately under T-1050). + +--- + +## Appendix — Decision Cross-References + +| Decision | Relevance | +|----------|-----------| +| D-019 | Original top-down camera + offline-renderer −72.5° angle. Superseded for gameplay by D-148; survives for the offline 2D-artwork render rig. | +| D-033 | Entity color = relationship to player. Unchanged, still the primary information-bearing color system. | +| D-045 | Environmental neutrality — strict zero shift. Governs how the D-235 material register (§3) may and may not respond to game state. | +| D-148 | 30° low-angle gameplay `Camera3D`, supersedes D-019 for gameplay. | +| D-217 | Tile condition thresholds (prosperity_score bands) — the weathering-baseline mechanism referenced throughout §3. | +| D-232 | Trait-template catalog — era reframed as maintenance/wear + past-vogue holdover, not a material-tech ladder. Backing rationale for retiring the Era 1/2/3 ladder in §5. | +| D-235 | Building exterior visual grammar + ratified ObjectTag vocabulary (`object_tag_vocabulary.toml`). §3 is this document's rendered-look layer on top of D-235's logical tokens. | +| D-244 | 3D objects in-world; 2D limited to textures + flat artwork. Establishes the toon-shader-family context for §2 and retires the sprite catalog framing this document used to have. | diff --git a/docs/design/visual-grammar-v01.md b/docs/design/visual-grammar-v01.md index 4851d2308..35f2cfc2b 100644 --- a/docs/design/visual-grammar-v01.md +++ b/docs/design/visual-grammar-v01.md @@ -8,9 +8,19 @@ decision_refs: [D-033, D-043, D-044, D-045, D-046, D-047, D-048, D-049, D-052, D author: "Araminta" created: 2026-02-19 updated: 2026-02-20 -superseded_by: "D-235/D-228" +superseded_by: "D-235/D-228, docs/assets/visual/palette.md" --- > **SUPERSEDED (2026-06-12, cascade-refocus sweep):** superseded by D-235/D-228 (generated-world visual grammar) + docs/design/character-visuals-spec.md (character rules). Kept as historical record; do not build against it. +> +> **Style-bible pointer (2026-07-25, T-1052):** the live Phase-4 style bible is +> `docs/assets/visual/palette.md`. Its §4/§5 spell out exactly what survives from +> this document (D-033 colors, "functional warmth," the visual-hierarchy/saturation +> principles) versus what's superseded (the sprite-era production model: entity +> sprite sizing, the 8-layer sprite z-stack, sprite typography sizing, animation +> frame counts, the resolution chain, and — specifically — the **three-construction-era +> material ladder** in §1.2–§1.4, which D-235 retired by name: "Era is NOT a material +> filter here"). Read palette.md first; come back here only for historical +> attribution. # The Settled Reach — v0.1 Visual Grammar diff --git a/docs/workshops/art-direction-mood-board/workshop-outcomes.md b/docs/workshops/art-direction-mood-board/workshop-outcomes.md index e36d7b0f6..e183f2109 100644 --- a/docs/workshops/art-direction-mood-board/workshop-outcomes.md +++ b/docs/workshops/art-direction-mood-board/workshop-outcomes.md @@ -18,6 +18,17 @@ decision_refs: [D-015, D-019, D-024, D-031, D-033, D-035, D-041, D-042, D-043, D **Rounds:** 3 + Closing + Post-workshop technical session (Tyre, Araminta, Stig) **Status:** Complete — all items resolved, ready for formal decision recording. Pipeline test in progress. +> **Archived transcript, not live style guidance (2026-07-25, T-1052):** this is a +> workshop record — read it for attribution and reasoning history, not as current +> art direction. Its candidate decisions (§7) were formally recorded as the +> perception.md/architecture.md decisions cited throughout (D-033, D-043–D-051); those +> D-records are the living artifact. Its camera-angle content (§1.2, §9.3) is +> superseded by [D-148](../../../governance/decisions/architecture.md#d-148-30-low-angle-camera-with-45-map-rotation--supersedes-d-019). +> Its three-construction-era material system (§1.8) is superseded by +> [D-235](../../../governance/decisions/architecture.md#d-235) ("Era is NOT a material +> filter here"). The current Phase-4 style bible is +> [`docs/assets/visual/palette.md`](../../assets/visual/palette.md) — start there. + --- ## Visual Identity Statements From 2ca70d0228dad7bfcdab888461df00244f91d4cc Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sat, 25 Jul 2026 20:36:47 +0200 Subject: [PATCH 4/9] =?UTF-8?q?docs(assets):=20PR=20#211=20review=20fixes?= =?UTF-8?q?=20=E2=80=94=20ratified=20glazing=20tokens,=20D-257,=20manifest?= =?UTF-8?q?=20skip=20contract?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round (Hoshe + Tyre, both REQUEST_CHANGES) fully addressed: palette.md's glass carve-out re-keyed from retired never-shipped tokens (precision_glass/smart_facade) to the ratified glass_curtain_wall (wall) + industrial_glazing (facade); the toon/PBR treatment promoted from a docs-only ruling to D-257 (architecture, cross-refs D-235/D-244/D-043/ D-044/D-033) with palette.md §2 now citing it as authority; D-149 + D-257 added to decision_refs; manifest.json _comment gains the explicit underscore-prefix loader skip contract; master pipeline table Visual row Stub -> Active; conventions.md mask pointer Section 3 -> 5; pre-existing broken D-066 links in the mood-board transcript repaired (leave-cleaner). Co-Authored-By: Claude Fable 5 --- client/assets/models/manifest.json | 2 +- docs/assets/README.md | 2 +- docs/assets/visual/conventions.md | 2 +- docs/assets/visual/palette.md | 28 +++++++++++-------- .../workshop-outcomes.md | 4 +-- governance/README.md | 1 + governance/decisions/architecture.md | 14 ++++++++++ 7 files changed, 37 insertions(+), 16 deletions(-) diff --git a/client/assets/models/manifest.json b/client/assets/models/manifest.json index 5214e5788..c88b88f5a 100644 --- a/client/assets/models/manifest.json +++ b/client/assets/models/manifest.json @@ -1,5 +1,5 @@ { - "_comment": "Environment prop/furniture manifest — schema mirrors client/assets/characters/manifest.json's per-item entry pattern (see docs/architecture/character-asset-organization.md). Keyed by asset name (matches the .glb basename, no extension). One entry per asset, NOT per-body-type variant — environment props are not body-fitted (D-244). An asset entering this manifest AND client/assets/models/ IS the promotion act (D-241); nothing stays 'promoted' in spikes/. Naming/mask-sidecar/texture conventions: docs/assets/visual/conventions.md.", + "_comment": "Environment prop/furniture manifest — schema mirrors client/assets/characters/manifest.json's per-item entry pattern (see docs/architecture/character-asset-organization.md). Keyed by asset name (matches the .glb basename, no extension). One entry per asset, NOT per-body-type variant — environment props are not body-fitted (D-244). An asset entering this manifest AND client/assets/models/ IS the promotion act (D-241); nothing stays 'promoted' in spikes/. Naming/mask-sidecar/texture conventions: docs/assets/visual/conventions.md. CONTRACT: any top-level key prefixed with '_' (this key, '_fields', '_example_only_not_a_real_asset') is metadata/documentation, not an asset category — loaders MUST skip '_'-prefixed keys and must not iterate them as if they were real category/asset entries.", "_fields": { "name": "string — asset name, matches the manifest key and the .glb basename", "category": "string — 'furniture' | 'props' (matches the subdirectory)", diff --git a/docs/assets/README.md b/docs/assets/README.md index 81f6aeec1..eca4af308 100644 --- a/docs/assets/README.md +++ b/docs/assets/README.md @@ -14,7 +14,7 @@ Centralized tracking for all production assets across pipelines. Each pipeline h | Pipeline | Index | Palette | Status | |----------|-------|---------|--------| | [Audio](audio/README.md) | [audio/README.md](audio/README.md) | [audio/palette.md](audio/palette.md) | Active — Sprint 7 | -| [Visual](visual/README.md) | [visual/README.md](visual/README.md) | [visual/palette.md](visual/palette.md) | Stub | +| [Visual](visual/README.md) | [visual/README.md](visual/README.md) | [visual/palette.md](visual/palette.md) | Active — catalog + style bible instantiated (T-1050/T-1052) | | [Video](video/README.md) | [video/README.md](video/README.md) | [video/palette.md](video/palette.md) | Stub | ## Structure diff --git a/docs/assets/visual/conventions.md b/docs/assets/visual/conventions.md index 5e930bbdd..0e30b17ed 100644 --- a/docs/assets/visual/conventions.md +++ b/docs/assets/visual/conventions.md @@ -45,7 +45,7 @@ T-1050). ## 2. Mask sidecars Convention: `_mask.png`, single-channel (R-only greyscale), sitting -next to the GLB — per the character-asset convention (Section 3, +next to the GLB — per the character-asset convention (Section 5, `docs/architecture/character-asset-organization.md`). White = tintable region, black = preserve original texture (`hint_default_black`: no mask loaded = no tinting; assets without a mask render with their original diff --git a/docs/assets/visual/palette.md b/docs/assets/visual/palette.md index 6bb9cb177..7454384d9 100644 --- a/docs/assets/visual/palette.md +++ b/docs/assets/visual/palette.md @@ -3,7 +3,7 @@ title: "Visual Palette" description: "Phase-4 visual style bible — camera, entity colours, environmental neutrality, D-235 material register, toon/PBR rendering treatment" type: design status: active -decision_refs: [D-019, D-033, D-045, D-148, D-217, D-232, D-235, D-244] +decision_refs: [D-019, D-033, D-045, D-148, D-149, D-217, D-232, D-235, D-244, D-257] author: "Araminta" updated: 2026-07-25 --- @@ -42,10 +42,13 @@ sentence is what this document is correcting. --- -## 2. Rendering Treatment — Toon vs. PBR (delegated ruling) +## 2. Rendering Treatment — Toon vs. PBR -**Ruling (Araminta, delegated by team lead this batch — surfaced to Jeroen for -veto in the batch report):** +**Authority: [D-257](../../../governance/decisions/architecture.md#d-257)** +(environment props share the character toon shading treatment; minimal-PBR +carve-out for glazing). This section is the practical guidance derived from +that record — the binding decision and its full rationale live in D-257, not +here. **Environment props and furniture share the character toon treatment.** One shader family, one look, across the whole in-world scene — with a single, @@ -70,10 +73,11 @@ the flat toon response. vocabulary's bulk. These are flat-lit like characters; PBR specular highlights on a stucco wall or a wooden table would read as wet/glossy and contradict the toon language players already learn from characters. -- **Exception — glass and polished/mirror metal:** `precision_glass`/ - `glass_curtain_wall` (facade), `smart_facade`, and any deliberately - reflective/polished metal prop (chrome fixture, mirror, glossy display - case) get a **separate minimal-PBR material**: `roughness` in the 0.05–0.2 +- **Exception — glass and polished/mirror metal:** the ratified D-235 glazing + tokens — `glass_curtain_wall` (wall axis) and `industrial_glazing` (facade + axis) — and any deliberately reflective/polished metal prop (chrome + fixture, mirror, glossy display case) get a **separate minimal-PBR + material**: `roughness` in the 0.05–0.2 range and non-zero `specular`/`metallic` as appropriate, layered so the toon shadow-band lighting model still governs the base surface response but a specular highlight and partial reflection are allowed through. This is not @@ -101,8 +105,8 @@ the flat toon response. grey panel, which actively misleads the player about sightlines (can I see through this?) in a game whose core mechanic is occlusion-based perception. Where "can the player see through/into this" is gameplay-load-bearing - (`glass_curtain_wall`, `precision_glass`, shop windows, interior partitions), - the material needs to look transparent/reflective enough to answer that + (`glass_curtain_wall`, `industrial_glazing`, shop windows, interior + partitions), the material needs to look transparent/reflective enough to answer that question at a glance. This is a narrow, named carve-out — it does not reopen "should props be PBR," it closes the one case where toon-only actively lies to the player. @@ -284,7 +288,9 @@ authored separately under T-1050). | D-033 | Entity color = relationship to player. Unchanged, still the primary information-bearing color system. | | D-045 | Environmental neutrality — strict zero shift. Governs how the D-235 material register (§3) may and may not respond to game state. | | D-148 | 30° low-angle gameplay `Camera3D`, supersedes D-019 for gameplay. | +| D-149 | 3D characters rendered live in scene, not pre-rendered sprites. Backing precedent for §1's camera reasoning (no more sprites to render "at the angle" during play) and for §2's shared-shader-family argument (characters and environment props come off the same live-3D pipeline). | | D-217 | Tile condition thresholds (prosperity_score bands) — the weathering-baseline mechanism referenced throughout §3. | | D-232 | Trait-template catalog — era reframed as maintenance/wear + past-vogue holdover, not a material-tech ladder. Backing rationale for retiring the Era 1/2/3 ladder in §5. | -| D-235 | Building exterior visual grammar + ratified ObjectTag vocabulary (`object_tag_vocabulary.toml`). §3 is this document's rendered-look layer on top of D-235's logical tokens. | +| D-235 | Building exterior visual grammar + ratified ObjectTag vocabulary (`object_tag_vocabulary.toml`). §3 is this document's rendered-look layer on top of D-235's logical tokens; §2's glazing carve-out names its ratified `glass_curtain_wall`/`industrial_glazing` tokens. | | D-244 | 3D objects in-world; 2D limited to textures + flat artwork. Establishes the toon-shader-family context for §2 and retires the sprite catalog framing this document used to have. | +| D-257 | Environment props share the character toon shading treatment; minimal-PBR carve-out for glazing. The binding authority for §2 — this document states the practical guidance, D-257 is the decision record. | diff --git a/docs/workshops/art-direction-mood-board/workshop-outcomes.md b/docs/workshops/art-direction-mood-board/workshop-outcomes.md index e183f2109..4ccbb7b88 100644 --- a/docs/workshops/art-direction-mood-board/workshop-outcomes.md +++ b/docs/workshops/art-direction-mood-board/workshop-outcomes.md @@ -90,7 +90,7 @@ Each participant's one-sentence answer to "What is this game's visual identity?" ### 1.3 World Composition — Tile-Based -**Dual-scale grid: 0.5m sim tiles, 1m visual tiles (2x retina factor, [D-066](../../decisions/architecture.md#d-066-dual-scale-grid--05m-simulation-1m-visual-2x-retina-factor)). Visual tiles are 64x64px.** +**Dual-scale grid: 0.5m sim tiles, 1m visual tiles (2x retina factor, [D-066](../../../governance/decisions/architecture.md#d-066-dual-scale-grid--05m-simulation-1m-visual-2x-retina-factor)). Visual tiles are 64x64px.** - Structural tiles (walls, floors, doors): 64x64px per visual tile (= 2x2 sim tiles), muted, minimal outlines, zone palette + era differentiation - Object tiles (furniture, equipment, containers): 64x64px per visual tile, medium outline (1px), Rimworld object detail as target. All world geometry is 2x2 sim tile minimum. @@ -119,7 +119,7 @@ Each participant's one-sentence answer to "What is this game's visual identity?" ### 1.5 Entity System -**24x32 pixel footprint within 64x64px visual tiles. Entities occupy 1x1 sim tiles (0.5m) but render across a 2x2 sim tile sprite footprint per [D-066](../../decisions/architecture.md#d-066-dual-scale-grid--05m-simulation-1m-visual-2x-retina-factor).** +**24x32 pixel footprint within 64x64px visual tiles. Entities occupy 1x1 sim tiles (0.5m) but render across a 2x2 sim tile sprite footprint per [D-066](../../../governance/decisions/architecture.md#d-066-dual-scale-grid--05m-simulation-1m-visual-2x-retina-factor).** - Entity smaller than visual tile = clear figure-ground relationship - D-033 color as primary information signal diff --git a/governance/README.md b/governance/README.md index 48c27a505..14e9e6ae1 100644 --- a/governance/README.md +++ b/governance/README.md @@ -322,6 +322,7 @@ line in place — keep the Q-record for the audit trail rather than deleting it. - [D-254: Standalone Atlas companion app — `make atlas`, dual-connection reader](decisions/architecture.md#d-254-standalone-atlas-companion-app--make-atlas-dual-connection-reader) — _architecture_ - [D-255: Body Map Viewer — stepped Atlas render architecture (supersedes the T-1143 continuous-ladder mechanism)](decisions/architecture.md#d-255-body-map-viewer--stepped-atlas-render-architecture-supersedes-the-t-1143-continuous-ladder-mechanism) — _architecture_ - [D-256: Canonical sampling convention — one absolute-metre derive core; the batch layer is a survey raster](decisions/architecture.md#d-256-canonical-sampling-convention--one-absolute-metre-derive-core-the-batch-layer-is-a-survey-raster) — _architecture_ +- [D-257: Environment props share the character toon shading treatment; minimal-PBR carve-out for glazing](decisions/architecture.md#d-257-environment-props-share-the-character-toon-shading-treatment-minimal-pbr-carve-out-for-glazing) — _architecture_ ## Open questions diff --git a/governance/decisions/architecture.md b/governance/decisions/architecture.md index d887d467b..a98cd2cf7 100644 --- a/governance/decisions/architecture.md +++ b/governance/decisions/architecture.md @@ -2313,4 +2313,18 @@ Technical foundation decisions that constrain implementation: engine, client-ser --- +### D-257: Environment props share the character toon shading treatment; minimal-PBR carve-out for glazing + +- **Date:** 2026-07-25 +- **Decision:** Environment props and furniture (the D-235 ObjectTag vocabulary's rendered assets — walls, roofs, facades, street furniture, and freestanding furniture/prop `.glb`s produced via `/glb-gen`) render with the **same toon shader family already used for characters** (`toon.gdshader` / `toon_masked.gdshader`, per `client/assets/characters/shaders/`), not a distinct PBR material response. The glb-gen postprocess convention of forcing `roughness=1.0`/`specular=0.0` ("for toon compatibility," `postprocess_glb.py::setup_materials()`) is confirmed as the correct default for this vocabulary's bulk. + - **Named carve-out — glass and polished/mirror metal.** The ratified glazing tokens — **`glass_curtain_wall`** (D-235 wall axis) and **`industrial_glazing`** (D-235 facade axis) — plus any deliberately reflective/polished-metal prop (chrome fixture, mirror, glossy display case), get a **minimal-PBR material** layered over the toon base: `roughness` in the 0.05–0.2 range with non-zero `specular`/`metallic`, not a full PBR stack (no environment-map reflections, no fresnel-driven rim lighting). The toon shadow-band lighting model still governs the base surface response; only a controlled specular/transparency term is added. + - This binds all future environment-shader and glb-gen-postprocess work; it is not a per-asset style suggestion. +- **Rationale:** Consistency compounds — a station built from one shader family reads as one coherent object; mixing a toon character against a PBR-lit room breaks the "one world" read the instant the player looks at their own hands next to a table (D-043/D-044 visual-hierarchy principles: entities and objects share one legible material language, distinguished by outline weight and saturation tier, not by rendering technology). The postprocess default already assumes this — `setup_materials()`'s "for toon compatibility" comment was written against the one Trellis pipeline that produces both character and environment `.glb`s, so sharing the treatment requires no new postprocess fork. The glazing carve-out is functionally forced, not a taste call: a flat-lit `roughness=1.0` glass pane reads as a frosted grey panel, which actively misleads the player about sightlines in a game whose core mechanic is occlusion-based perception (D-033's asymmetric-information model depends on the player correctly judging what they can and cannot see through). The carve-out is narrow and named — it closes only the case where toon-only actively lies to the player about transparency, and does not reopen "should props be PBR" generally. +- **Raised by:** Araminta (T-1052, delegated art-direction ruling), reviewed by Tyre (PR #211 — flagged the original carve-out's use of retired pre-amendment example tokens `precision_glass`/`smart_facade`, corrected here to the ratified D-235 tokens). +- **Implementation:** `docs/assets/visual/palette.md` §2 (practical guidance, cites this record as authority). Applies to all environment-prop material setup from Phase 4 asset production onward; no existing shipped assets to migrate as of this record's date. +- **Cross-reference:** [D-235](#d-235) (ratified ObjectTag vocabulary — `glass_curtain_wall`/`industrial_glazing` are its tokens, not this record's invention), [D-244](#d-244) (3D objects in-world; establishes the shared Trellis/glb-gen pipeline this record's postprocess argument depends on), [D-043/D-044](perception.md#d-043-art-direction--visual-style-functional-warmth) (visual-hierarchy/"functional warmth" rationale — entity/object/structure share one material language), [D-033](perception.md#d-033-entity-color--relationship-to-player) (the occlusion/asymmetric-information rationale for the glazing carve-out). +- **Dissent:** None. + +--- + *110 decisions (D-001 through D-256, excluding gaps). Last updated: 2026-07-24 (D-256 — canonical sampling convention: one absolute-metre derive core (`derive_at_metres` family), batch pseudo-grid re-scoped as `SurveyCellPos` survey raster (real newtype), thin-wrapper unification fixing the three-latitude inconsistency and the region-(0,0) climate collapse, exact-position feature judgments, step-canvas fencing; discharges the T-1174 blocker on D-255's T-1181 envelope).* From 9ac98542d19eebf332e74d4452f6694d6749ef67 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sat, 25 Jul 2026 20:45:46 +0200 Subject: [PATCH 5/9] fix(client): isolate viewer tests from the machine-shared atlas disk cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit user://atlas_cache/ is one directory for every worktree gate run, live capture driver, and real play session, and T-1183's Tier-2/3 lookup short-circuits BEFORE test_mode's silent-no-op IPC — so a warm shared cache delivers real canvases into tests written against 'nothing ever arrives'. Caught live: a concurrent GJ380c Global capture flipped the two before-any-canvas viewer tests in another worktree's push gate. Adds disk_cache_root_override on StepCanvasViewer (threads into StepCanvasRequest's existing test-injection-only seam; production leaves it empty) and routes every direct viewer construction in the viewer + legend suites through a _make_viewer() helper pinning an isolated root. First slice of T-1193's viewer test seam; screen-built viewers still share the default root (noted on T-1193). Co-Authored-By: Claude Fable 5 --- client/tests/test_step_canvas_legend.gd | 21 +++- client/tests/test_step_canvas_viewer.gd | 100 +++++++++++------- .../atlas/step_canvas/step_canvas_viewer.gd | 10 +- 3 files changed, 84 insertions(+), 47 deletions(-) diff --git a/client/tests/test_step_canvas_legend.gd b/client/tests/test_step_canvas_legend.gd index c6a3e3055..fb182efa9 100644 --- a/client/tests/test_step_canvas_legend.gd +++ b/client/tests/test_step_canvas_legend.gd @@ -9,9 +9,20 @@ extends GdUnitTestSuite const LegendScript := preload("res://ui/implant/apps/atlas/step_canvas/step_canvas_legend.gd") const StepCanvasTransport := preload("res://ui/implant/apps/atlas/step_canvas/step_canvas_transport.gd") +## Same Tier-2/3 isolation as test_step_canvas_viewer.gd's _make_viewer() +## (T-1193 first slice): the default disk-cache root is machine-shared, and a +## warm cache delivers canvases into tests entered on real body ids. +const TEST_DISK_CACHE_ROOT := "user://test_step_canvas_legend_cache/" + + +func _make_viewer() -> StepCanvasViewer: + var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + v.disk_cache_root_override = TEST_DISK_CACHE_ROOT + return v + func test_legend_starts_hidden_before_refresh() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) var legend = LegendScript.new(v) auto_free(legend) @@ -19,7 +30,7 @@ func test_legend_starts_hidden_before_refresh() -> void: func test_refresh_makes_the_legend_visible_and_populates_content() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) var legend = LegendScript.new(v) @@ -45,7 +56,7 @@ func test_refresh_without_a_viewer_does_not_crash() -> void: ## default, one section per active toggle" contract the retired ## atlas_window_legend.gd's own refresh() doc described. func test_refresh_reflects_the_active_overlay_toggle() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) var legend = LegendScript.new(v) @@ -63,7 +74,7 @@ func test_refresh_reflects_the_active_overlay_toggle() -> void: func test_reposition_sets_a_fixed_panel_margin_position() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) var legend = LegendScript.new(v) auto_free(legend) @@ -81,7 +92,7 @@ func test_reposition_sets_a_fixed_panel_margin_position() -> void: ## column, with room to spare — never right up against it, and certainly ## never past it into where the canvas is centered from. func test_legend_actual_right_edge_stays_inside_the_reserved_column() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) var legend = LegendScript.new(v) auto_free(legend) diff --git a/client/tests/test_step_canvas_viewer.gd b/client/tests/test_step_canvas_viewer.gd index 4b94338b9..a81a1cfea 100644 --- a/client/tests/test_step_canvas_viewer.gd +++ b/client/tests/test_step_canvas_viewer.gd @@ -16,16 +16,34 @@ const StepCanvasTransport := preload("res://ui/implant/apps/atlas/step_canvas/st ## .cache/screenshots/t1183-eyeball-run2/02-region.png. const GJ1C_GLOBAL_EXTENT := Vector2i(177, 88) +## Isolated Tier-2/3 disk-cache root for every directly-constructed viewer +## (T-1193 first slice). The default user://atlas_cache/ is SHARED across the +## whole machine — every worktree's gate run, live capture driver, and real +## play session writes the same directory — and T-1183's disk-cache lookup +## short-circuits BEFORE test_mode's silent-no-op IPC, so a warm shared cache +## delivers real canvases into tests written against "nothing ever arrives" +## (2026-07-25: a concurrent live GJ380c Global capture flipped the two +## before-any-canvas tests in another worktree's gate run). In test_mode the +## isolated root stays empty forever: no canvas ever arrives, so the cache +## never writes. +const TEST_DISK_CACHE_ROOT := "user://test_step_canvas_viewer_cache/" + + +func _make_viewer() -> StepCanvasViewer: + var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + v.disk_cache_root_override = TEST_DISK_CACHE_ROOT + return v + func test_enter_lands_on_the_global_opener() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) assert_str(v.get_held_rung()).is_equal(StepCanvasTransport.RUNG_GLOBAL) func test_get_body_id_reflects_the_entered_body() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) assert_str(v.get_body_id()).is_equal("") v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) @@ -35,7 +53,7 @@ func test_get_body_id_reflects_the_entered_body() -> void: ## Scrolling one notch descends the ladder — cursor-anchored, so a cursor ## position must be supplied; the rung index advances by exactly one. func test_scroll_rung_descends_one_notch() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) v._scroll_rung(1, Vector2(400.0, 300.0)) @@ -43,7 +61,7 @@ func test_scroll_rung_descends_one_notch() -> void: func test_scroll_rung_clamps_at_the_deepest_rung() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) for _i in range(10): @@ -52,7 +70,7 @@ func test_scroll_rung_clamps_at_the_deepest_rung() -> void: func test_reset_to_global_returns_from_a_deep_rung() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) v._scroll_rung(1, Vector2(400.0, 300.0)) @@ -71,7 +89,7 @@ func test_reset_to_global_returns_from_a_deep_rung() -> void: ## regression (scroll_step() clamping at index 0 meant _scroll_rung() ## returned before ever reaching a reset call). func test_scroll_out_at_global_after_a_pan_resets_the_view() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) assert_str(v.get_held_rung()).is_equal(StepCanvasTransport.RUNG_GLOBAL) @@ -94,7 +112,7 @@ func test_scroll_out_at_global_after_a_pan_resets_the_view() -> void: ## (un-drifted) Global frame must stay a no-op — the reset is edge-triggered ## on genuine drift, not a per-scroll unconditional reset. func test_scroll_out_at_undrifted_global_is_a_no_op() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) v._scroll_rung(-1, Vector2(400.0, 300.0)) @@ -103,21 +121,21 @@ func test_scroll_out_at_undrifted_global_is_a_no_op() -> void: func test_overlay_visibility_defaults_to_off_for_every_toggle() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) for def: Dictionary in v.get_overlay_defs(): assert_bool(v.is_overlay_visible(def["id"])).is_false() func test_set_overlay_visible_updates_state() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.set_overlay_visible("gen_dw_temp", true) assert_bool(v.is_overlay_visible("gen_dw_temp")).is_true() func test_set_overlay_visible_unknown_id_is_a_no_op() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.set_overlay_visible("not_a_real_overlay", true) assert_bool(v.is_overlay_visible("not_a_real_overlay")).is_false() @@ -201,7 +219,7 @@ static func _synthetic_canvas(width: int, height: int) -> Dictionary: func test_maybe_refloat_is_inert_before_any_canvas_has_arrived() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) v._scroll_rung(1, Vector2(400.0, 300.0)) # District — footprint still ZERO, nothing arrived @@ -214,7 +232,7 @@ func test_maybe_refloat_is_inert_before_any_canvas_has_arrived() -> void: ## the held canvas keeps drawing, no re-request (§4/§5's "only when a pan ## carries the view past the held window's edge"). func test_maybe_refloat_does_not_refloat_on_a_small_pan() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(800.0, 600.0) # a real viewport size — _maybe_refloat's # drift math is relative to get_rect().size's own center; leaving this at @@ -244,7 +262,7 @@ func test_maybe_refloat_does_not_refloat_on_a_small_pan() -> void: ## world_center, view_offset reset to ZERO (the canvas re-centers under the ## new request). func test_maybe_refloat_refloats_once_the_pan_crosses_the_edge_threshold() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) v._scroll_rung(1, Vector2(400.0, 300.0)) # District @@ -266,7 +284,7 @@ func test_maybe_refloat_refloats_once_the_pan_crosses_the_edge_threshold() -> vo ## Global never re-floats on pan (D-255(a): its canvas is the whole body, ## no edge to cross) — even with a real texture held and a huge drift. func test_maybe_refloat_is_a_no_op_at_global_rung() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) v._terrain_layer.rebuild_from_canvas(_synthetic_canvas(200, 100), v.get_held_rung(), "") @@ -284,7 +302,7 @@ func test_maybe_refloat_is_a_no_op_at_global_rung() -> void: func test_edge_scroll_suppressed_without_application_focus() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(800.0, 600.0) v._app_has_focus = false @@ -293,7 +311,7 @@ func test_edge_scroll_suppressed_without_application_focus() -> void: func test_edge_scroll_suppressed_when_cursor_has_never_moved_over_the_control() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(800.0, 600.0) # _last_mouse_pos defaults to (-1, -1) — an impossible in-bounds position, @@ -303,7 +321,7 @@ func test_edge_scroll_suppressed_when_cursor_has_never_moved_over_the_control() func test_edge_scroll_active_near_the_left_edge() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(800.0, 600.0) v._app_has_focus = true @@ -315,7 +333,7 @@ func test_edge_scroll_active_near_the_left_edge() -> void: func test_edge_scroll_active_near_the_right_edge() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(800.0, 600.0) v._app_has_focus = true @@ -325,7 +343,7 @@ func test_edge_scroll_active_near_the_right_edge() -> void: func test_edge_scroll_inactive_well_inside_the_viewport() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(800.0, 600.0) v._app_has_focus = true @@ -345,7 +363,7 @@ func test_edge_scroll_inactive_well_inside_the_viewport() -> void: ## ever cached under it, so the sweep is a true no-op read (no writes to the ## real user://atlas_cache/ directory this test could leak). func test_enter_runs_the_disk_cache_visit_sweep_without_crashing() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "T1183_sweep_smoke_test_body", "body_radius_km": 6238.4}, {}) # If the wiring is broken (e.g. calling a method that doesn't exist), the @@ -358,7 +376,7 @@ func test_enter_runs_the_disk_cache_visit_sweep_without_crashing() -> void: ## Timer node, not a _process()-driven counter), autostarts, and is set to ## the documented coarse interval — never a sub-frame or per-frame value. func test_disk_sweep_timer_is_coarse_and_autostarts() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) var timer: Timer = v.get_node("DiskSweepTimer") assert_object(timer).is_not_null() @@ -383,7 +401,7 @@ func test_disk_sweep_timer_is_coarse_and_autostarts() -> void: ## non-blocking test speed) against an injected-root request so this test ## touches no real cache files. func test_disk_sweep_timeout_handler_runs_background_sweep_for_the_current_body() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "T1183_sweep_smoke_test_body", "body_radius_km": 6238.4}, {}) # No live server, no cached entries for this body — the assertion is @@ -422,7 +440,7 @@ static func _land_global_canvas(v: StepCanvasViewer, width: int, height: int) -> func test_global_canvas_arrival_populates_the_body_extent_cap_source() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {}) TestStepCanvasViewer._land_global_canvas(v, GJ1C_GLOBAL_EXTENT.x, GJ1C_GLOBAL_EXTENT.y) @@ -434,7 +452,7 @@ func test_global_canvas_arrival_populates_the_body_extent_cap_source() -> void: ## scrolling to Region and firing its request must produce a CAPPED extent ## — never the raw viewport-fit 384x216 that overran the body on both axes. func test_region_request_extent_is_capped_to_the_landed_global_extent() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(1920.0, 1080.0) # the T-1183 eyeball's own viewport v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {}) @@ -457,7 +475,7 @@ func test_region_request_extent_is_capped_to_the_landed_global_extent() -> void: ## ZERO — the Region request must go out UNCAPPED (server clamps ## independently) rather than silently collapsing to a zero-cell request. func test_region_request_extent_is_uncapped_before_the_global_echo_lands() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(1920.0, 1080.0) v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {}) @@ -481,7 +499,7 @@ func test_region_request_extent_is_uncapped_before_the_global_echo_lands() -> vo ## same key, capped or not, never a key built from one extent and served ## under another. func test_capped_extent_matches_what_the_request_actually_sends() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(1920.0, 1080.0) v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {}) @@ -515,7 +533,7 @@ func test_capped_extent_matches_what_the_request_actually_sends() -> void: ## proving the cap is spacing-keyed, not applied indiscriminately to every ## rung once a Global extent is known. func test_district_request_extent_is_never_capped_by_the_global_extent() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(1920.0, 1080.0) v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {}) @@ -538,7 +556,7 @@ func test_district_request_extent_is_never_capped_by_the_global_extent() -> void ## region grid") — this proves it, through the real enter()/land/enter() ## sequence rather than asserting the field directly only. func test_global_body_extent_resets_on_a_different_body_and_does_not_leak() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(1920.0, 1080.0) @@ -575,7 +593,7 @@ func test_global_body_extent_resets_on_a_different_body_and_does_not_leak() -> v ## have computed a non-zero centering offset (unless the canvas happens to ## exactly fill the viewport, not the case here). func test_global_canvas_arrival_centers_the_view_not_top_left() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(1920.0, 1080.0) v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {}) @@ -596,7 +614,7 @@ func test_global_canvas_arrival_centers_the_view_not_top_left() -> void: ## legend-reserved 1920x1080 case (R=9, scale=1.8) AND the 4K case (R=20, ## scale=4.0) — the SAME formula, no separate coverage-threshold branch. func test_global_canvas_scale_yields_an_exact_integer_pixels_per_gridunit_ratio() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(1920.0, 1080.0) v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {}) @@ -618,7 +636,7 @@ func test_global_canvas_scale_yields_an_exact_integer_pixels_per_gridunit_ratio( ## too (20/5 = 4.0) — confirms the 1080p case above isn't a coincidence of ## a small viewport, just the same formula at a different achievable ratio. func test_global_canvas_scale_at_4k_also_yields_an_exact_integer_ratio() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(3840.0, 2160.0) v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {}) @@ -633,7 +651,7 @@ func test_global_canvas_scale_at_4k_also_yields_an_exact_integer_ratio() -> void ## stays 1.0 once the player has descended past Global, even though a ## Global canvas was landed earlier in the same session. func test_fixed_rung_canvas_scale_stays_one_after_descending_from_global() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(1920.0, 1080.0) v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {}) @@ -654,7 +672,7 @@ func test_fixed_rung_canvas_scale_stays_one_after_descending_from_global() -> vo ## and centered, its drawn rect's LEFT edge must be at or past the legend's ## own right edge, never underneath it. func test_global_canvas_left_edge_never_overlaps_the_legend_column() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(1920.0, 1080.0) v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {}) @@ -671,7 +689,7 @@ func test_global_canvas_left_edge_never_overlaps_the_legend_column() -> void: ## one — _notification(NOTIFICATION_RESIZED) wires _recompute_canvas_transform() ## for exactly this case. func test_resize_recenters_an_already_held_global_canvas() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(1920.0, 1080.0) v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {}) @@ -695,7 +713,7 @@ func test_resize_recenters_an_already_held_global_canvas() -> void: ## under 1.0, end-to-end through the real viewer wiring, not just the pure ## transport function this mirrors. func test_global_canvas_scale_never_drops_below_native_on_a_narrow_viewport() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.size = Vector2(348.0 + StepCanvasTransport.LEGEND_COLUMN_PX, 1080.0) v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {}) @@ -720,7 +738,7 @@ func test_global_canvas_scale_never_drops_below_native_on_a_narrow_viewport() -> ## (verified end-to-end, through act(), in test_atlas_agent_interface.gd; ## this is the narrower unit-level check directly against the viewer). func test_jump_to_sets_held_rung_and_world_center() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) @@ -733,7 +751,7 @@ func test_jump_to_sets_held_rung_and_world_center() -> void: ## Omitting `rung` keeps whatever rung is currently held — the "revisit ## within the same rung" common case shouldn't require repeating it. func test_jump_to_keeps_current_rung_when_omitted() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) v._scroll_rung(1, Vector2(400.0, 300.0)) # Region @@ -746,7 +764,7 @@ func test_jump_to_keeps_current_rung_when_omitted() -> void: ## Jumping to Global always forces world_center to ZERO — Global has no ## panned-center concept (mirrors _scroll_rung()'s own Global-rung handling). func test_jump_to_global_forces_world_center_to_zero() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) v._scroll_rung(1, Vector2(400.0, 300.0)) # Region @@ -757,7 +775,7 @@ func test_jump_to_global_forces_world_center_to_zero() -> void: func test_jump_to_unrecognized_rung_is_a_no_op() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) var rung_before: String = v.get_held_rung() @@ -770,7 +788,7 @@ func test_jump_to_unrecognized_rung_is_a_no_op() -> void: func test_get_current_canvas_summary_before_any_canvas_arrives() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) @@ -786,7 +804,7 @@ func test_get_current_canvas_summary_before_any_canvas_arrives() -> void: ## StepCanvasAnnotationLayer._draw_settlements()'s own dedup discipline — ## covering the same cell id twice must not double-count). func test_get_current_canvas_summary_counts_match_a_fixture_canvas() -> void: - var v: StepCanvasViewer = auto_free(StepCanvasViewer.new()) + var v: StepCanvasViewer = _make_viewer() add_child(v) v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}) v._scroll_rung(1, Vector2(400.0, 300.0)) # Region diff --git a/client/ui/implant/apps/atlas/step_canvas/step_canvas_viewer.gd b/client/ui/implant/apps/atlas/step_canvas/step_canvas_viewer.gd index b20154c5f..e5801611e 100644 --- a/client/ui/implant/apps/atlas/step_canvas/step_canvas_viewer.gd +++ b/client/ui/implant/apps/atlas/step_canvas/step_canvas_viewer.gd @@ -150,6 +150,14 @@ var _legend_panel = null var _request = null # StepCanvasRequest var _disk_sweep_timer: Timer = null # T-1183 coarse background sweep trigger +## Test-injection-only (T-1193 first slice): threads into StepCanvasRequest's +## disk_cache_root seam so tests can isolate the Tier-2/3 disk cache from the +## machine-shared user://atlas_cache/ (one directory for every worktree gate, +## live capture driver, and real play session — a warm shared cache delivers +## canvases into tests written against a cold one). Set BEFORE add_child(); +## the request is constructed in _ready(). Production always leaves this "". +var disk_cache_root_override: String = "" + func _ready() -> void: anchor_right = 1.0 @@ -176,7 +184,7 @@ func _ready() -> void: _annotation_layer.name = "AnnotationLayer" _canvas.add_child(_annotation_layer) - _request = StepCanvasRequest.new(self) + _request = StepCanvasRequest.new(self, disk_cache_root_override) _request.name = "Request" add_child(_request) _request.canvas_ready.connect(_on_canvas_ready) From 08acbcb7df6b7fb3671d57f1f168fe2c5f81eb8d Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sat, 25 Jul 2026 20:46:07 +0200 Subject: [PATCH 6/9] =?UTF-8?q?chore(meta):=20pql=20changelog=20=E2=80=94?= =?UTF-8?q?=20T-1193=20first-slice=20incident=20record?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .pql/changelog/ticket_history/2026-07.sql | 4 + .pql/changelog/tickets/2026-07.sql | 112 ++++++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/.pql/changelog/ticket_history/2026-07.sql b/.pql/changelog/ticket_history/2026-07.sql index ef90fa3e7..05af02ef9 100644 --- a/.pql/changelog/ticket_history/2026-07.sql +++ b/.pql/changelog/ticket_history/2026-07.sql @@ -2322,3 +2322,7 @@ INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, chang INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPV39JX54HYP83RZJFDM', 'status', 'backlog', 'in_progress', NULL, '2026-07-25 18:11:42', '2026-07-25 18:11:42.948', '2026-07-25 18:11:42.948', NULL, '613156e91577043373205250f21a72cd', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPJ910NNR5T2R9F9NWQR', 'assigned_to', NULL, 'justine', NULL, '2026-07-25 18:11:45', '2026-07-25 18:11:45.571', '2026-07-25 18:11:45.571', NULL, '7fe3851d234f1cafb2b4fc70c1f5eed8', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPV39JX54HYP83RZJFDM', 'assigned_to', NULL, 'araminta', NULL, '2026-07-25 18:11:46', '2026-07-25 18:11:46.095', '2026-07-25 18:11:46.095', NULL, '0d1336d72b0807fe85c054696e914350', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPJ910NNR5T2R9F9NWQR', 'status', 'in_progress', 'review', NULL, '2026-07-25 18:19:48', '2026-07-25 18:19:48.053', '2026-07-25 18:19:48.053', NULL, '2fa6ce22c32b396a2ea7ac1dd6f64dc0', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSHSYM96N2M9GJTQMF58E4WC', 'description', 'Flagged by stig during the T-1189/T-1192 round (2026-07-25): test_step_canvas_viewer.gd has no injected disk-cache-root seam (unlike test_step_canvas_request.gd, which injects a disposable root), so viewer tests that exercise the disk-cache path against a real-looking body_id (e.g. GJ1c) read/write the REAL user://atlas_cache/ shared across runs — a stale real entry from earlier manual sessions can leak into test behavior. Current mitigation (used by the new T-1189/T-1192 tests and the existing sweep smoke test): distinctive synthetic body_ids. Proper fix: give the viewer suite the same injectable cache-root seam the request suite has (constructor/setter injection through StepCanvasRequest), and migrate the synthetic-body_id tests onto it. Small, test-only.', 'Flagged by stig during the T-1189/T-1192 round (2026-07-25): test_step_canvas_viewer.gd has no injected disk-cache-root seam (unlike test_step_canvas_request.gd, which injects a disposable root), so viewer tests that exercise the disk-cache path against a real-looking body_id (e.g. GJ1c) read/write the REAL user://atlas_cache/ shared across runs — a stale real entry from earlier manual sessions can leak into test behavior. Current mitigation (used by the new T-1189/T-1192 tests and the existing sweep smoke test): distinctive synthetic body_ids. Proper fix: give the viewer suite the same injectable cache-root seam the request suite has (constructor/setter injection through StepCanvasRequest), and migrate the synthetic-body_id tests onto it. Small, test-only. + +First slice landed 2026-07-25 (main, 9ac98542d) via a live incident: PR #211''s gate failed on test_step_canvas_viewer''s two before-any-canvas tests because user://atlas_cache/ is machine-shared (every worktree gate, live capture driver, and play session write the same directory) and the T-1183 disk lookup short-circuits before test_mode''s no-op IPC — a concurrent GJ380c Global live capture (T-1157 eyeball work) warmed the exact key the tests assume cold. Fix: StepCanvasViewer.disk_cache_root_override threading into StepCanvasRequest''s existing test-injection seam; viewer + legend suites route construction through _make_viewer() helpers pinning isolated roots. REMAINING for this ticket: screen-constructed viewers (RegionalScreen builds its viewer internally — no override path), and any future suite constructing viewers directly should use the helper pattern; consider a gdUnit-wide guard (e.g. a shared test base or a lint) so new suites cannot silently ride the shared root.', NULL, '2026-07-25 18:45:59', '2026-07-25 18:45:59.847', '2026-07-25 18:45:59.847', NULL, 'dd331004eee53f809c993e6845b52cbd', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/tickets/2026-07.sql b/.pql/changelog/tickets/2026-07.sql index 927168e82..bbb38eb7c 100644 --- a/.pql/changelog/tickets/2026-07.sql +++ b/.pql/changelog/tickets/2026-07.sql @@ -3568,3 +3568,115 @@ archiving/marking those superseded, or by palette.md stating exactly what it inh Lead note (batch-5 activation): the toon-vs-PBR treatment call for environment props is delegated to araminta (art seat) this batch — her ruling lands in palette.md with rationale; surfaced to Jeroen in the batch report for cheap veto. VERDICT: READY.', 'in_progress', 'medium', 'araminta', 'visual', 'D-235', '2026-06-12 10:40:59', '2026-07-25 18:11:46.095', NULL, '4df3e92b2bc003a7b73f860dd47704f2', 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 ('06FBPTXPJ910NNR5T2R9F9NWQR', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Create the visual production-status catalog; fix glb-gen production path + naming conventions', '(description follows in first append) + +--- +Refinement (2026-06-16, /whats-next Si pass) — FULL scope (user decision). Two deliverables + conventions. + +(1) CATALOG: instantiate the 5 docs/assets/visual/*.md files (sprites, furniture, tilesets, icons, effects) with per-category status tables. Template: docs/assets/_templates/sprite.md (SPR/FUR/TILE/ICO/FX prefixes); status vocab planned/in-progress/placeholder/final (docs/assets/README.md). Audio side (docs/assets/audio/README.md) is the working model. + +(2) glb-gen PRODUCTION PATH: SKILL.md:115 names client/assets/models/ which does NOT exist. Create it + furniture/ + props/ subdirs (mirror the spike) + .gitkeep. Define client/assets/models/manifest.json schema MIRRORING the character manifest (docs/architecture/character-asset-organization.md) — per-asset entries: name, category, source-model path, subdirectory. Promotion convention per D-241 (promotion = repo-canonical). Update SKILL.md to point at the new path + reference the manifest. + +(3) CONVENTIONS (author NOW, full scope): door state-frame suffixes (e.g. _open / _closed), Godot TileSet resource organization (/.tres), multi-tile footprint metadata, and renderer/textures/ naming keyed to D-235 tokens (current renderer/README.md:80-95 covers sprites only: __.png). Author these in docs/assets/visual/conventions.md (single home) and cross-link from the SKILL. + +OVERLAP: T-1053 (rewrite araminta briefing) softly depends on this catalog — note the edge. T-1049''s per-class stories will reference these conventions — do T-1050 first / concurrently so T-1049 can cite them. + +--- +HELD 2026-06-16 (separated from the cascade batch; needs re-scoping before resuming). +The asset pipeline this ticket assumes is sprite-centric (docs/assets/visual/sprites.md +"Entities", _templates/sprite.md, SPR/FUR/TILE/ICO/FX prefixes, the /sprite-gen +render-3D->2D-sprites step). Direction correction: we are NOT producing sprites — Trellis +produces 3D objects (.glb) displayed in-world; the only flattened 2D content is TEXTURES. +Re-scope this ticket around a 3D-model + texture catalog (not a sprite catalog) before +activating. See the sprite-vs-3D root-cause note (this session). + +Batch-5 Si pass correction (2026-07-25): the 2026-06-16 refinement block above is INVALIDATED by the HELD note that follows it — the sprite-centric scope (SPR/FUR prefixes, sprite catalog, /sprite-gen) contradicts the recorded direction (Trellis produces 3D .glb models; only flattened 2D content is textures). Do not implement as written. A re-scope pass (3D-model + texture catalog, manifest mirroring character-asset-organization, D-241 promotion, D-235-keyed texture naming) is running as part of batch 5; T-1052 (style bible) and T-1051 (title-only) get refinement passes in the same sweep. Sequencing per T-1049''s own note stands: this ticket''s conventions land first, T-1049/T-1053 consume them. + +Refinement (batch-5 Si pass, 2026-07-25) — SUPERSEDES the 2026-06-16 block above (sprite-centric, invalidated by D-244). READY. + +CONTEXT: D-244 (confirmed, 2026-06-16) already re-scoped the catalog structure and repointed +renderer/README.md, sprite-gen SKILL.md, docs/assets/README.md, docs/assets/visual/{README,palette}.md, +and docs/assets/_templates/sprite.md (marked legacy) at the 3D-model reality. This ticket is now +narrower than its original draft: instantiate what D-244 named but did not fill in, plus author +conventions.md (net-new — no D-244 amendment touched it). + +DELIVERABLE (a) — Category catalog files, per docs/assets/README.md''s already-updated structure: + - docs/assets/visual/models.md — 3D objects/props (.glb via /glb-gen). Row template TBD (see + conventions.md below) since _templates/sprite.md is explicitly legacy/retired for this use. + - docs/assets/visual/textures.md — PBR surface maps (BaseColor/Normal/Roughness) for 3D models. + - docs/assets/visual/artwork.md — flat 2D art (paintings/flags/billboards/signage) via /sprite-gen''s + repurposed 2D-artwork role. + - docs/assets/visual/icons.md — UI icons (13 SVGs already exist in client/assets/icons/ — this file + should reflect that as-built inventory, not start at zero). + - docs/assets/visual/effects.md — shaders/particles/overlays. + Each file follows docs/assets/audio/README.md''s model: per-category status table (planned/in-progress/ + placeholder/final per docs/assets/README.md''s vocab), current counts. Update docs/assets/visual/README.md''s + category table counts once files exist (currently all show 0, which will stay accurate at initial creation + — do not invent placeholder rows). + ACCEPTANCE: 5 files exist, cross-linked from visual/README.md (already has the links — verify they resolve), + each with a real (possibly empty) status table matching the audio/README.md pattern. + +DELIVERABLE (b) — glb-gen production path: + - Create client/assets/models/ with furniture/ and props/ subdirectories (mirroring the proven + spike layout at spikes/3dpipeline/models/{furniture,props}/) + .gitkeep in each. + - Define client/assets/models/manifest.json, schema mirroring docs/architecture/character-asset-organization.md''s + per-asset-entry pattern (that doc''s clothing/accessory entries are the closest precedent — a flat + per-item record, not per-body-type variants since environment props aren''t body-fitted). Minimum + fields: name, category (furniture|props), source_model (path under client/assets/models/), subdirectory, + status (planned/in-progress/placeholder/final — reuse the docs/assets/README.md vocab so catalog and + manifest never disagree). Promotion = repo-canonical per D-241 — an asset entering manifest.json AND + client/assets/models/ IS the promotion act; nothing stays "promoted" in spikes/. + - Update .claude/skills/glb-gen/SKILL.md — replace the "client/assets/models/ does not exist... that''s + a Phase-5 concern" paragraph (lines ~96-102) with the new path + a pointer to the manifest. NOTE: + this contradicts nothing already in D-244/T-750 — T-750''s own epic description lists furniture/props + production as Phase-4 parallel work, so populating the production path (not populating the world with + them — that''s Phase 5 per T-961) is in-scope now. + ACCEPTANCE: client/assets/models/{furniture,props}/.gitkeep exist; manifest.json schema documented + (either inline in SKILL.md or as a comment/example in the file itself, following the character + manifest''s convention); SKILL.md''s stale "does not exist" paragraph is corrected. + +DELIVERABLE (c) — docs/assets/visual/conventions.md (NEW FILE — no prior art to amend): + - Model naming: _.glb (e.g. table_baroque.glb, chair_modernist.glb) — + mirrors the spike''s existing baroque_table.glb/modernist_chair.glb pattern loosely; pick one + order and state it (category-first matches the character-asset clothing convention + _; the spike used descriptor-first — this is a naming decision to make, + not silently pick — see lead note below on who rules it). + - Mask sidecar: _mask.png, single-channel per the character-asset convention (Section 3 + of character-asset-organization.md) UNLESS the item needs multi-region tinting, in which case follow + the RGBA channel-routed convention from that doc''s Section 11 (T-1089) rather than reinventing one. + - Texture naming keyed to D-235 tokens: PBR maps for building-exterior surfaces should name by + the ratified ObjectTag vocabulary (D-235 amendment, wiki/economics/object_tag_vocabulary.toml) — + e.g. concrete_wall_basecolor.png, pitched_roof_normal.png — NOT the old renderer/README.md + __.png scheme, which is direction-indexed for the retired 4-direction + sprite render and does not apply to PBR texture maps at all (there is no "direction" for a texture, + only map type: basecolor/normal/roughness). + - Door state handling — DO NOT port a state-frame suffix convention. The 2026-06-16 draft proposed + _open/_closed GLB variants; this is sprite-era thinking (baked state = baked frame). + For a 3D object the door''s open/closed/locked/sealed state (T-979/T-998''s DoorSpec: Open/Closed/ + TemporalWindow/Locked/Sealed) is a RUNTIME transform/animation on one door .glb, not separate baked + models — but the actual mechanism (AnimationPlayer swing vs. procedural hinge rotation vs. a + door-model convention with a named "open" bone) is a Phase-5 rendering decision (player control + + in-world rendering, per the cascade), not a Phase-4 asset-catalog concern. This ticket + should record ONLY the negative constraint (no state-frame suffix files) and defer the positive + mechanism to whichever Phase-5 ticket handles door rendering. Do not invent a runtime convention here. + - Godot TileSet resource organization — OUT OF SCOPE, removed from ticket. client/scripts/rendering/ + tile_renderer.gd''s TileSet is an explicitly-labeled placeholder ("programmatic TileSet with placeholder + colored rectangles," D-014) and the cascade rule states current in-world rendering code is + "left as-is until Phase 5 — do not build on it or rip it out before then." A /.tres + convention would be building on Phase-5-owned code from Phase 4. Do not author this section. + - Multi-tile footprint metadata: still relevant (a table/couch spans multiple sim tiles regardless + of sprite vs 3D) — keep this item, express as a manifest.json field (e.g. footprint_tiles: [w, h]) + rather than a filename convention. + ACCEPTANCE: conventions.md exists, covers model naming / mask sidecars / D-235-keyed texture naming / + multi-tile footprint metadata; explicitly states the door-state and TileSet items are deferred (with + reasons) rather than silently omitted, so a future refinement pass doesn''t have to re-derive why. + +SEQUENCING (unchanged from 2026-06-16 note): T-1053 (araminta briefing rewrite) softly depends on this +catalog. T-1049''s per-class stories reference these conventions — this ticket lands first / concurrently. + +Lead note (batch-5 activation): the model-naming-order call (category-first vs descriptor-first) is +delegated to araminta (art seat) this batch — her ruling lands in conventions.md with a one-line +rationale; surfaced to Jeroen in the batch report for cheap veto. VERDICT: READY.', 'review', 'high', 'justine', 'visual', NULL, '2026-06-12 10:40:59', '2026-07-25 18:19:48.052', NULL, '111c7b03cb5bc345c306a64fcd8a76d0', 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 ('06FSHSYM96N2M9GJTQMF58E4WC', 'task', '06FBPPMZNNEV052DBYYY3A897C', 'StepCanvasViewer test suite lacks an injected disk-cache-root seam — tests share the real user://atlas_cache', 'Flagged by stig during the T-1189/T-1192 round (2026-07-25): test_step_canvas_viewer.gd has no injected disk-cache-root seam (unlike test_step_canvas_request.gd, which injects a disposable root), so viewer tests that exercise the disk-cache path against a real-looking body_id (e.g. GJ1c) read/write the REAL user://atlas_cache/ shared across runs — a stale real entry from earlier manual sessions can leak into test behavior. Current mitigation (used by the new T-1189/T-1192 tests and the existing sweep smoke test): distinctive synthetic body_ids. Proper fix: give the viewer suite the same injectable cache-root seam the request suite has (constructor/setter injection through StepCanvasRequest), and migrate the synthetic-body_id tests onto it. Small, test-only. + +First slice landed 2026-07-25 (main, 9ac98542d) via a live incident: PR #211''s gate failed on test_step_canvas_viewer''s two before-any-canvas tests because user://atlas_cache/ is machine-shared (every worktree gate, live capture driver, and play session write the same directory) and the T-1183 disk lookup short-circuits before test_mode''s no-op IPC — a concurrent GJ380c Global live capture (T-1157 eyeball work) warmed the exact key the tests assume cold. Fix: StepCanvasViewer.disk_cache_root_override threading into StepCanvasRequest''s existing test-injection seam; viewer + legend suites route construction through _make_viewer() helpers pinning isolated roots. REMAINING for this ticket: screen-constructed viewers (RegionalScreen builds its viewer internally — no override path), and any future suite constructing viewers directly should use the helper pattern; consider a gdUnit-wide guard (e.g. a shared test base or a lint) so new suites cannot silently ride the shared root.', 'backlog', 'low', NULL, 'client', NULL, '2026-07-25 10:52:37.833', '2026-07-25 18:45:59.847', NULL, '00c87aff1d2d9c4406944b52fd04c6f0', 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); From bea6c5fc4f1f209c47414743f383561c851aae41 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sat, 25 Jul 2026 20:52:27 +0200 Subject: [PATCH 7/9] chore(meta): update changelog Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f360d09e..0bf2ae72b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ Format based on [Keep a Changelog](https://keepachangelog.com/). ## [Unreleased] ### Added +- **The visual asset pipeline has a production home** (D-244, T-1050) — the 3D-model/texture/artwork/icon/effect catalogs are instantiated with live status tables (the icon set's 13 as-built SVGs inventoried with their real wiring), `client/assets/models/` exists as the promotion target for glb-gen output with a schema-documented manifest, and a single conventions document fixes model naming (category-first), mask sidecars, and D-235-token texture naming — with door-state and TileSet conventions recorded as explicit Phase-5 deferrals rather than silent gaps +- **The Phase-4 visual style bible** (D-257, T-1052) — palette.md is no longer a stub: gameplay vs offline-render cameras untangled, a rendered-look register for every ratified building material/roof/facade/street tag (hue range, grain, weathering), and the newly-recorded D-257 rendering treatment — environment props share the character toon look, with a minimal-PBR carve-out for glazing so glass reads truthfully about sightlines. Exactly one live style authority remains; the superseded v0.1 documents now say so themselves - **The Atlas can be driven by agents** (D-226, T-971) — a named-intent control channel (AtlasAgentInterface) lets automated QA agents navigate the real Atlas UI through the exact same handlers a click calls: open/select/descend, jump straight to any world coordinate at any zoom step, toggle overlays, and read back a summary of what's on screen — turning the hand-scripted verification drives into a first-class, repeatable QA capability. In-process consumers only for now; a shipped reference driver replaces the ad-hoc scripts ### Changed @@ -15,6 +17,7 @@ Format based on [Keep a Changelog](https://keepachangelog.com/). - **Internal: legacy map-window field retired** (T-1159) — the obsolete duplicate zoom-granularity field was removed from the map wire protocol and caches; no player-visible change ### Fixed +- **Map viewer unit tests no longer flake against a warm map cache** (T-1193 first slice) — the viewer test suites rode the machine-shared on-disk atlas cache, so a live play or capture session visiting the same body could flip "nothing has arrived yet" tests in any later test run; viewers under test now pin an isolated cache root through a proper injection seam - **One coastline at every zoom step** (T-1160) — the whole-body and Region views drew a structurally different coastline than the closer zoom steps (they skipped the deterministic coast-detail warp — about 5% of coastal cells disagreed, displaced up to ~19 km). All six zoom steps now draw the same coastline at their own density: zooming in refines the line instead of moving it. (Game version → 0.4.3: old on-disk map caches refresh automatically) - **Climate now respects latitude on both hemispheres** (T-1186) — a sign-convention bug made every northern hemisphere read as polar (+90°) and the southern hemisphere read as compressed northern tropics: temperate lakes at 76°S, ice only at the top of the world map. Temperature baselines, glaciation, and everything derived from them now band correctly north AND south of the equator — the same spot near the antarctic circle that used to derive as an 18 °C ice-free lake now comes out at 2 °C under light glaciation - **Lakes deepen from the shore** (T-1188) — lake shorelines no longer render as hard flat-blue step-edges: every lake cell now carries its settled-hydrology depth band, and the map shades lakes from pale at the water's edge to dark at the deepest point of the basin — the same visual grammar ocean coastlines already had — each lake shaded relative to its own deepest point, so shallow ponds and deep basins both read correctly. (Game version 0.4.0 → 0.4.2: old on-disk map caches refresh automatically) From 7f704a08c7025605ec758b6f5b7f35f6f51dbc68 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sat, 25 Jul 2026 20:55:15 +0200 Subject: [PATCH 8/9] =?UTF-8?q?chore(meta):=20pql=20changelog=20=E2=80=94?= =?UTF-8?q?=20visual=20track=20closed=20(T-1050,=20T-1052=20done)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .pql/changelog/ticket_history/2026-07.sql | 2 + .pql/changelog/tickets/2026-07.sql | 158 ++++++++++++++++++++++ 2 files changed, 160 insertions(+) diff --git a/.pql/changelog/ticket_history/2026-07.sql b/.pql/changelog/ticket_history/2026-07.sql index f725e0ad7..996e4f60e 100644 --- a/.pql/changelog/ticket_history/2026-07.sql +++ b/.pql/changelog/ticket_history/2026-07.sql @@ -2327,3 +2327,5 @@ INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, chang INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSHSYM96N2M9GJTQMF58E4WC', 'description', 'Flagged by stig during the T-1189/T-1192 round (2026-07-25): test_step_canvas_viewer.gd has no injected disk-cache-root seam (unlike test_step_canvas_request.gd, which injects a disposable root), so viewer tests that exercise the disk-cache path against a real-looking body_id (e.g. GJ1c) read/write the REAL user://atlas_cache/ shared across runs — a stale real entry from earlier manual sessions can leak into test behavior. Current mitigation (used by the new T-1189/T-1192 tests and the existing sweep smoke test): distinctive synthetic body_ids. Proper fix: give the viewer suite the same injectable cache-root seam the request suite has (constructor/setter injection through StepCanvasRequest), and migrate the synthetic-body_id tests onto it. Small, test-only.', 'Flagged by stig during the T-1189/T-1192 round (2026-07-25): test_step_canvas_viewer.gd has no injected disk-cache-root seam (unlike test_step_canvas_request.gd, which injects a disposable root), so viewer tests that exercise the disk-cache path against a real-looking body_id (e.g. GJ1c) read/write the REAL user://atlas_cache/ shared across runs — a stale real entry from earlier manual sessions can leak into test behavior. Current mitigation (used by the new T-1189/T-1192 tests and the existing sweep smoke test): distinctive synthetic body_ids. Proper fix: give the viewer suite the same injectable cache-root seam the request suite has (constructor/setter injection through StepCanvasRequest), and migrate the synthetic-body_id tests onto it. Small, test-only. First slice landed 2026-07-25 (main, 9ac98542d) via a live incident: PR #211''s gate failed on test_step_canvas_viewer''s two before-any-canvas tests because user://atlas_cache/ is machine-shared (every worktree gate, live capture driver, and play session write the same directory) and the T-1183 disk lookup short-circuits before test_mode''s no-op IPC — a concurrent GJ380c Global live capture (T-1157 eyeball work) warmed the exact key the tests assume cold. Fix: StepCanvasViewer.disk_cache_root_override threading into StepCanvasRequest''s existing test-injection seam; viewer + legend suites route construction through _make_viewer() helpers pinning isolated roots. REMAINING for this ticket: screen-constructed viewers (RegionalScreen builds its viewer internally — no override path), and any future suite constructing viewers directly should use the helper pattern; consider a gdUnit-wide guard (e.g. a shared test base or a lint) so new suites cannot silently ride the shared root.', NULL, '2026-07-25 18:45:59', '2026-07-25 18:45:59.847', '2026-07-25 18:45:59.847', NULL, 'dd331004eee53f809c993e6845b52cbd', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPJ910NNR5T2R9F9NWQR', 'status', 'review', 'done', NULL, '2026-07-25 18:55:08', '2026-07-25 18:55:08.780', '2026-07-25 18:55:08.780', NULL, '544c6b5e89dafda431434ef0a9525881', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPV39JX54HYP83RZJFDM', 'status', 'review', 'done', NULL, '2026-07-25 18:55:08', '2026-07-25 18:55:08.787', '2026-07-25 18:55:08.787', NULL, '1ee2a69f3be3f8cc551a4d9c725b18f5', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/tickets/2026-07.sql b/.pql/changelog/tickets/2026-07.sql index 55b1b55f0..bc217daed 100644 --- a/.pql/changelog/tickets/2026-07.sql +++ b/.pql/changelog/tickets/2026-07.sql @@ -3729,3 +3729,161 @@ rationale; surfaced to Jeroen in the batch report for cheap veto. VERDICT: READY 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 ('06FSHSYM96N2M9GJTQMF58E4WC', 'task', '06FBPPMZNNEV052DBYYY3A897C', 'StepCanvasViewer test suite lacks an injected disk-cache-root seam — tests share the real user://atlas_cache', 'Flagged by stig during the T-1189/T-1192 round (2026-07-25): test_step_canvas_viewer.gd has no injected disk-cache-root seam (unlike test_step_canvas_request.gd, which injects a disposable root), so viewer tests that exercise the disk-cache path against a real-looking body_id (e.g. GJ1c) read/write the REAL user://atlas_cache/ shared across runs — a stale real entry from earlier manual sessions can leak into test behavior. Current mitigation (used by the new T-1189/T-1192 tests and the existing sweep smoke test): distinctive synthetic body_ids. Proper fix: give the viewer suite the same injectable cache-root seam the request suite has (constructor/setter injection through StepCanvasRequest), and migrate the synthetic-body_id tests onto it. Small, test-only. First slice landed 2026-07-25 (main, 9ac98542d) via a live incident: PR #211''s gate failed on test_step_canvas_viewer''s two before-any-canvas tests because user://atlas_cache/ is machine-shared (every worktree gate, live capture driver, and play session write the same directory) and the T-1183 disk lookup short-circuits before test_mode''s no-op IPC — a concurrent GJ380c Global live capture (T-1157 eyeball work) warmed the exact key the tests assume cold. Fix: StepCanvasViewer.disk_cache_root_override threading into StepCanvasRequest''s existing test-injection seam; viewer + legend suites route construction through _make_viewer() helpers pinning isolated roots. REMAINING for this ticket: screen-constructed viewers (RegionalScreen builds its viewer internally — no override path), and any future suite constructing viewers directly should use the helper pattern; consider a gdUnit-wide guard (e.g. a shared test base or a lint) so new suites cannot silently ride the shared root.', 'backlog', 'low', NULL, 'client', NULL, '2026-07-25 10:52:37.833', '2026-07-25 18:45:59.847', NULL, '00c87aff1d2d9c4406944b52fd04c6f0', 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 ('06FBPTXPJ910NNR5T2R9F9NWQR', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Create the visual production-status catalog; fix glb-gen production path + naming conventions', '(description follows in first append) + +--- +Refinement (2026-06-16, /whats-next Si pass) — FULL scope (user decision). Two deliverables + conventions. + +(1) CATALOG: instantiate the 5 docs/assets/visual/*.md files (sprites, furniture, tilesets, icons, effects) with per-category status tables. Template: docs/assets/_templates/sprite.md (SPR/FUR/TILE/ICO/FX prefixes); status vocab planned/in-progress/placeholder/final (docs/assets/README.md). Audio side (docs/assets/audio/README.md) is the working model. + +(2) glb-gen PRODUCTION PATH: SKILL.md:115 names client/assets/models/ which does NOT exist. Create it + furniture/ + props/ subdirs (mirror the spike) + .gitkeep. Define client/assets/models/manifest.json schema MIRRORING the character manifest (docs/architecture/character-asset-organization.md) — per-asset entries: name, category, source-model path, subdirectory. Promotion convention per D-241 (promotion = repo-canonical). Update SKILL.md to point at the new path + reference the manifest. + +(3) CONVENTIONS (author NOW, full scope): door state-frame suffixes (e.g. _open / _closed), Godot TileSet resource organization (/.tres), multi-tile footprint metadata, and renderer/textures/ naming keyed to D-235 tokens (current renderer/README.md:80-95 covers sprites only: __.png). Author these in docs/assets/visual/conventions.md (single home) and cross-link from the SKILL. + +OVERLAP: T-1053 (rewrite araminta briefing) softly depends on this catalog — note the edge. T-1049''s per-class stories will reference these conventions — do T-1050 first / concurrently so T-1049 can cite them. + +--- +HELD 2026-06-16 (separated from the cascade batch; needs re-scoping before resuming). +The asset pipeline this ticket assumes is sprite-centric (docs/assets/visual/sprites.md +"Entities", _templates/sprite.md, SPR/FUR/TILE/ICO/FX prefixes, the /sprite-gen +render-3D->2D-sprites step). Direction correction: we are NOT producing sprites — Trellis +produces 3D objects (.glb) displayed in-world; the only flattened 2D content is TEXTURES. +Re-scope this ticket around a 3D-model + texture catalog (not a sprite catalog) before +activating. See the sprite-vs-3D root-cause note (this session). + +Batch-5 Si pass correction (2026-07-25): the 2026-06-16 refinement block above is INVALIDATED by the HELD note that follows it — the sprite-centric scope (SPR/FUR prefixes, sprite catalog, /sprite-gen) contradicts the recorded direction (Trellis produces 3D .glb models; only flattened 2D content is textures). Do not implement as written. A re-scope pass (3D-model + texture catalog, manifest mirroring character-asset-organization, D-241 promotion, D-235-keyed texture naming) is running as part of batch 5; T-1052 (style bible) and T-1051 (title-only) get refinement passes in the same sweep. Sequencing per T-1049''s own note stands: this ticket''s conventions land first, T-1049/T-1053 consume them. + +Refinement (batch-5 Si pass, 2026-07-25) — SUPERSEDES the 2026-06-16 block above (sprite-centric, invalidated by D-244). READY. + +CONTEXT: D-244 (confirmed, 2026-06-16) already re-scoped the catalog structure and repointed +renderer/README.md, sprite-gen SKILL.md, docs/assets/README.md, docs/assets/visual/{README,palette}.md, +and docs/assets/_templates/sprite.md (marked legacy) at the 3D-model reality. This ticket is now +narrower than its original draft: instantiate what D-244 named but did not fill in, plus author +conventions.md (net-new — no D-244 amendment touched it). + +DELIVERABLE (a) — Category catalog files, per docs/assets/README.md''s already-updated structure: + - docs/assets/visual/models.md — 3D objects/props (.glb via /glb-gen). Row template TBD (see + conventions.md below) since _templates/sprite.md is explicitly legacy/retired for this use. + - docs/assets/visual/textures.md — PBR surface maps (BaseColor/Normal/Roughness) for 3D models. + - docs/assets/visual/artwork.md — flat 2D art (paintings/flags/billboards/signage) via /sprite-gen''s + repurposed 2D-artwork role. + - docs/assets/visual/icons.md — UI icons (13 SVGs already exist in client/assets/icons/ — this file + should reflect that as-built inventory, not start at zero). + - docs/assets/visual/effects.md — shaders/particles/overlays. + Each file follows docs/assets/audio/README.md''s model: per-category status table (planned/in-progress/ + placeholder/final per docs/assets/README.md''s vocab), current counts. Update docs/assets/visual/README.md''s + category table counts once files exist (currently all show 0, which will stay accurate at initial creation + — do not invent placeholder rows). + ACCEPTANCE: 5 files exist, cross-linked from visual/README.md (already has the links — verify they resolve), + each with a real (possibly empty) status table matching the audio/README.md pattern. + +DELIVERABLE (b) — glb-gen production path: + - Create client/assets/models/ with furniture/ and props/ subdirectories (mirroring the proven + spike layout at spikes/3dpipeline/models/{furniture,props}/) + .gitkeep in each. + - Define client/assets/models/manifest.json, schema mirroring docs/architecture/character-asset-organization.md''s + per-asset-entry pattern (that doc''s clothing/accessory entries are the closest precedent — a flat + per-item record, not per-body-type variants since environment props aren''t body-fitted). Minimum + fields: name, category (furniture|props), source_model (path under client/assets/models/), subdirectory, + status (planned/in-progress/placeholder/final — reuse the docs/assets/README.md vocab so catalog and + manifest never disagree). Promotion = repo-canonical per D-241 — an asset entering manifest.json AND + client/assets/models/ IS the promotion act; nothing stays "promoted" in spikes/. + - Update .claude/skills/glb-gen/SKILL.md — replace the "client/assets/models/ does not exist... that''s + a Phase-5 concern" paragraph (lines ~96-102) with the new path + a pointer to the manifest. NOTE: + this contradicts nothing already in D-244/T-750 — T-750''s own epic description lists furniture/props + production as Phase-4 parallel work, so populating the production path (not populating the world with + them — that''s Phase 5 per T-961) is in-scope now. + ACCEPTANCE: client/assets/models/{furniture,props}/.gitkeep exist; manifest.json schema documented + (either inline in SKILL.md or as a comment/example in the file itself, following the character + manifest''s convention); SKILL.md''s stale "does not exist" paragraph is corrected. + +DELIVERABLE (c) — docs/assets/visual/conventions.md (NEW FILE — no prior art to amend): + - Model naming: _.glb (e.g. table_baroque.glb, chair_modernist.glb) — + mirrors the spike''s existing baroque_table.glb/modernist_chair.glb pattern loosely; pick one + order and state it (category-first matches the character-asset clothing convention + _; the spike used descriptor-first — this is a naming decision to make, + not silently pick — see lead note below on who rules it). + - Mask sidecar: _mask.png, single-channel per the character-asset convention (Section 3 + of character-asset-organization.md) UNLESS the item needs multi-region tinting, in which case follow + the RGBA channel-routed convention from that doc''s Section 11 (T-1089) rather than reinventing one. + - Texture naming keyed to D-235 tokens: PBR maps for building-exterior surfaces should name by + the ratified ObjectTag vocabulary (D-235 amendment, wiki/economics/object_tag_vocabulary.toml) — + e.g. concrete_wall_basecolor.png, pitched_roof_normal.png — NOT the old renderer/README.md + __.png scheme, which is direction-indexed for the retired 4-direction + sprite render and does not apply to PBR texture maps at all (there is no "direction" for a texture, + only map type: basecolor/normal/roughness). + - Door state handling — DO NOT port a state-frame suffix convention. The 2026-06-16 draft proposed + _open/_closed GLB variants; this is sprite-era thinking (baked state = baked frame). + For a 3D object the door''s open/closed/locked/sealed state (T-979/T-998''s DoorSpec: Open/Closed/ + TemporalWindow/Locked/Sealed) is a RUNTIME transform/animation on one door .glb, not separate baked + models — but the actual mechanism (AnimationPlayer swing vs. procedural hinge rotation vs. a + door-model convention with a named "open" bone) is a Phase-5 rendering decision (player control + + in-world rendering, per the cascade), not a Phase-4 asset-catalog concern. This ticket + should record ONLY the negative constraint (no state-frame suffix files) and defer the positive + mechanism to whichever Phase-5 ticket handles door rendering. Do not invent a runtime convention here. + - Godot TileSet resource organization — OUT OF SCOPE, removed from ticket. client/scripts/rendering/ + tile_renderer.gd''s TileSet is an explicitly-labeled placeholder ("programmatic TileSet with placeholder + colored rectangles," D-014) and the cascade rule states current in-world rendering code is + "left as-is until Phase 5 — do not build on it or rip it out before then." A /.tres + convention would be building on Phase-5-owned code from Phase 4. Do not author this section. + - Multi-tile footprint metadata: still relevant (a table/couch spans multiple sim tiles regardless + of sprite vs 3D) — keep this item, express as a manifest.json field (e.g. footprint_tiles: [w, h]) + rather than a filename convention. + ACCEPTANCE: conventions.md exists, covers model naming / mask sidecars / D-235-keyed texture naming / + multi-tile footprint metadata; explicitly states the door-state and TileSet items are deferred (with + reasons) rather than silently omitted, so a future refinement pass doesn''t have to re-derive why. + +SEQUENCING (unchanged from 2026-06-16 note): T-1053 (araminta briefing rewrite) softly depends on this +catalog. T-1049''s per-class stories reference these conventions — this ticket lands first / concurrently. + +Lead note (batch-5 activation): the model-naming-order call (category-first vs descriptor-first) is +delegated to araminta (art seat) this batch — her ruling lands in conventions.md with a one-line +rationale; surfaced to Jeroen in the batch report for cheap veto. VERDICT: READY.', 'done', 'high', 'justine', 'visual', NULL, '2026-06-12 10:40:59', '2026-07-25 18:55:08.780', NULL, '6001e87cac297aa603a16cdf4724057a', 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 ('06FBPTXPV39JX54HYP83RZJFDM', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Write the Phase-4 visual style bible into docs/assets/visual/palette.md', '(description follows in first append) + +Refinement (batch-5 Si pass, 2026-07-25) — READY. + +CURRENT STATE: docs/assets/visual/palette.md exists as a STUB (correctly re-scoped by D-244 — no +sprite-era content survives in it) — 4 bullets: camera angle (D-019, now superseded by D-148 for +gameplay — palette.md''s bullet is stale and must be corrected), entity colors (D-033), environmental +neutrality (D-045), "functional warmth." It explicitly defers to "art direction workshop synthesis and +first visual sprint" — this ticket IS that synthesis pass for Phase 4. + +WHAT D-235 ALREADY FIXES (do not re-litigate): the building-exterior material/roof/facade/street +vocabulary (WallMaterial/RoofForm/FacadeRhythm/StreetSurface, ratified as the 28-template +architecture_trait_catalog.toml / wiki/economics/object_tag_vocabulary.toml) and the fallback-to-generic +mechanism. D-235 is the LOGICAL token layer ("what material is this wall") — Phase 4+ scope per its own +Implementation note ("textured render + the bulk of the theme library are Phase 5+"). T-1052 is the +RENDERED-LOOK layer on top: given a WallMaterial token, what does concrete_wall actually look like +(hue range, texture grain, weathering baseline) when art is eventually produced against it. + +WHAT T-1052 MUST STILL AUTHOR (net-new, no existing decision covers this): + - Correct the camera-angle bullet: D-148 (30 deg low-angle Camera3D, supersedes D-019) is the GAMEPLAY + camera; D-019''s -72.5 deg survives only for the offline renderer (2D-artwork / retired sprite mode, + per renderer/README.md). palette.md''s current bullet conflates these — fix it to name both and + which applies where. + - A concrete color/material register for the D-235 token vocabulary: not new tokens, but the RANGE + each token maps to (e.g. "stone_wall reads as warm grey-tan, not cool grey" / "corrugated_metal + weathers to rust-orange, not staying silver") — the thing D-235 explicitly parked for Phase 5+ art + production but which a style bible can pre-specify so future asset generation has a target. + - Reconcile with docs/design/visual-grammar-v01.md and docs/workshops/art-direction-mood-board/ + workshop-outcomes.md (both pre-cascade, v0.1-era, sprite-framed) — extract what survives (color + relationships, "functional warmth" identity, D-033 palette) vs what''s superseded by D-235/D-244 + (era-based material ladder — explicitly retired by D-235''s own text: "Era is NOT a material filter + here"). Do not leave two contradictory style documents live without a note in one pointing at the + other''s supersession. + - Texture "grain"/rendering treatment for the D-244 3D-object reality: since objects are lit 3D meshes + now (not flat sprite fills), the style bible should say something about the toon/PBR balance — + precedent exists in client/assets/characters/shaders/ (toon.gdshader, toon_masked.gdshader, + outline.gdshader) and the glb-gen postprocess convention (roughness=1.0/specular=0.0 "for toon + compatibility" per .claude/skills/glb-gen/SKILL.md''s setup_materials() note). Confirm whether + environment props share this toon treatment with characters, or diverge — this is a real open + question the style bible should answer, not assume. + +ACCEPTANCE: docs/assets/visual/palette.md is no longer a stub; camera-angle bullet corrected; +color/material register for D-235 tokens documented; toon-vs-PBR treatment for props stated; +supersession relationship to visual-grammar-v01.md / workshop-outcomes.md is explicit (either by +archiving/marking those superseded, or by palette.md stating exactly what it inherits from each). + +Lead note (batch-5 activation): the toon-vs-PBR treatment call for environment props is delegated to +araminta (art seat) this batch — her ruling lands in palette.md with rationale; surfaced to Jeroen in +the batch report for cheap veto. VERDICT: READY.', 'done', 'medium', 'araminta', 'visual', 'D-235', '2026-06-12 10:40:59', '2026-07-25 18:55:08.787', NULL, '0c2f33e6e2b24c61cff46c61d2df9a8f', 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); From ae45d5e21ac0b51c9a4cbb874c3635dd68118ed2 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sat, 25 Jul 2026 21:00:46 +0200 Subject: [PATCH 9/9] =?UTF-8?q?chore(meta):=20pql=20changelog=20=E2=80=94?= =?UTF-8?q?=20batch-6=20prep=20(T-1049=20re-scoped,=20T-1053=20refined,=20?= =?UTF-8?q?T-1196=20body=20pool=20recorded)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .pql/changelog/ticket_history/2026-07.sql | 275 ++++++++++++++++++++++ .pql/changelog/tickets/2026-07.sql | 212 +++++++++++++++++ 2 files changed, 487 insertions(+) diff --git a/.pql/changelog/ticket_history/2026-07.sql b/.pql/changelog/ticket_history/2026-07.sql index 996e4f60e..bec430f07 100644 --- a/.pql/changelog/ticket_history/2026-07.sql +++ b/.pql/changelog/ticket_history/2026-07.sql @@ -2329,3 +2329,278 @@ INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, chang First slice landed 2026-07-25 (main, 9ac98542d) via a live incident: PR #211''s gate failed on test_step_canvas_viewer''s two before-any-canvas tests because user://atlas_cache/ is machine-shared (every worktree gate, live capture driver, and play session write the same directory) and the T-1183 disk lookup short-circuits before test_mode''s no-op IPC — a concurrent GJ380c Global live capture (T-1157 eyeball work) warmed the exact key the tests assume cold. Fix: StepCanvasViewer.disk_cache_root_override threading into StepCanvasRequest''s existing test-injection seam; viewer + legend suites route construction through _make_viewer() helpers pinning isolated roots. REMAINING for this ticket: screen-constructed viewers (RegionalScreen builds its viewer internally — no override path), and any future suite constructing viewers directly should use the helper pattern; consider a gdUnit-wide guard (e.g. a shared test base or a lint) so new suites cannot silently ride the shared root.', NULL, '2026-07-25 18:45:59', '2026-07-25 18:45:59.847', '2026-07-25 18:45:59.847', NULL, 'dd331004eee53f809c993e6845b52cbd', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPJ910NNR5T2R9F9NWQR', 'status', 'review', 'done', NULL, '2026-07-25 18:55:08', '2026-07-25 18:55:08.780', '2026-07-25 18:55:08.780', NULL, '544c6b5e89dafda431434ef0a9525881', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPV39JX54HYP83RZJFDM', 'status', 'review', 'done', NULL, '2026-07-25 18:55:08', '2026-07-25 18:55:08.787', '2026-07-25 18:55:08.787', NULL, '1ee2a69f3be3f8cc551a4d9c725b18f5', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPE8FR7TGEM7X6QVPV4M', 'description', '(description follows in first append) + +Filed 2026-06-12 from the fable-ous.md audit (S-43; citations adversarially verified). +T-961 (two-sentence description, no acceptance criteria, no brief links) is the only asset-production ticket under T-750, while the epic names eight classes; cars, barns, TVs, billboards appear in NO ticket at all (verified against the full corpus). Refine into child stories per class — station walls, rural walls, doors, floors, furniture/props, street furniture (lamp posts), signage/screens (billboards/TVs), rural structures (barns) — each gated on a one-page brief; explicitly record which classes are deferred (cars: see Q-067 audit note). Decide Q-119 (generic-parent placeholder set as first milestone) before sequencing. +Audit per-class status matrix (verified 2026-06-12): +# docs/assets/ survey (13 files + downloads/) + +| File | One-line summary | +|---|---| +| README.md | Master index: audio/visual/video pipelines, status vocabulary (planned/in-progress/placeholder/final), structure that promises visual/{sprites,furniture,tilesets,icons,effects}.md | +| _templates/audio.md | Row + detailed-entry template for audio assets | +| _templates/sprite.md | Row + detailed-entry template for sprites (ID prefixes SPR/FUR/TILE/ICO/FX, status, method, directions columns) | +| _templates/video.md | Row + detailed-entry template for video | +| audio/README.md | ACTIVE production-status catalog — per-category counts table (12 assets tracked), generation tools, bus architecture | +| audio/palette.md | Sonic style bible | +| audio/ambient.md / sfx.md / ui.md | Per-category audio asset tables | +| visual/README.md | STUB — links 5 category files that do not exist, all counts 0 | +| visual/palette.md | STUB — 4 bullet decisions (D-019/D-033/D-045, "functional warmth"), defers to art-direction workshop | +| video/README.md / palette.md | Stubs | +| downloads/ | Purchased character sources (Quaternius Universal Base Characters, Modular Character Outfits FBX) — characters only | + +# Per-asset-class matrix + +| Class | Written brief | Style guide | Naming/res conventions | Gen path | Produced assets | Tickets | +|---|---|---|---|---|---|---| +| Walls (station) | art-direction workshop-outcomes.md §3.3 (v0.1.1 set, pre-cascade) + docs/design/placeholder-art-spec-v01.md §2.1 | visual-grammar-v01.md §1/§4.3, workshop-outcomes §1.8 era palette (partly superseded by D-235), D-235 WallMaterial vocab | renderer/README.md:80-95 (`__.png`, 1024→256→64 chain) | working — renderer/models/wall_structural.tscn rendered | wall_structural ×4 dirs in client/assets/sprites/; wall_bar_green model unrendered; 2 textures | T-961 (backlog), T-988 (token vocab, backlog) | +| Walls (rural) | none found | D-235 tokens only (timber/rammed-earth etc.) | renderer/README.md (generic) | same path, unused | none | T-961 text mention only | +| Doors | placeholder-art-spec-v01.md §2.1 (procedural rects, v0.1) + workshop-outcomes §3.3 `door_sliding` | none for generated world | none for open/closed state frames | same path, unused | none | T-979/T-998 (DoorSpec — logical only), T-961 | +| Floors | placeholder-art-spec-v01.md §2.1 + workshop-outcomes §3.3 (4 floor sprites) | D-228 FloorMaterial axis (open vocab) | 64px visual tile (placeholder-art-spec §1) | same path, unused | none — client/assets/tilesets/ is .gitkeep only | T-959 (tile fill, logical), T-961 | +| Styled buildings | D-232 + D-235 + wiki/economics/architecture_trait_catalog.toml (T-1005 done, ~35 visual bundles) | D-235 material/color-register grammar — tokens only, no rendered art direction | token→fallback-parent chain (catalog header :34-44) | no model/texture path defined for building exteriors | none | T-977, T-988, T-994, T-1004 (all backlog), T-1005 (done) | +| Furniture | docs/assets/visual/furniture.md MISSING (linked); workshop-outcomes §3.3 lists 8-10 objects (pre-cascade) | era table §1.8 only | character-asset-organization.md explicitly excludes furniture ("follow Trellis spike conventions") | proven in spike | spikes/3dpipeline/models/furniture/ ×3 GLB (unpromoted); zero in client/assets | T-961, T-995 (ObjectTag vocab, backlog) | +| Items | docs/design/icon-set-v01.md (HUD/inventory icons only) | D-086 icon constraints | SVG icon_ prefix (client/assets/icons/) | n/a for icons | 13 SVGs in client/assets/icons/ | none for in-world item sprites | +| Cars/vehicles | none found | none | none | spike only | spikes/3dpipeline/models/props/vw_beetle.glb (spike) | none — Q-067 open ("driveable vs NPC traffic?") | +| Lamp posts | none found (zero repo hits) | none | none | unused | none | T-961 text mention only | +| Barns | none found (only T-750 epic text) | none | none | unused | none | none | +| TVs | none found (only T-750 epic text) | none | none | unused | none | none | +| Billboards | none found (only T-750 epic text; other hits = LOD impostors) | none | none | unused | none | none | +| Characters (reference class — the working model) | docs/architecture/character-asset-organization.md + docs/design/character-visuals-spec.md | yes | full (manifest.json, seg_ naming, body-type keys) | proven end-to-end | 1002 files in client/assets/characters/ | done | + +# Generation path (verified files) +image-gen (.claude/skills/image-gen/SKILL.md + scripts/image_connector.py, Gemini) → glb-gen (SKILL.md + tooling/db/trellis_connector.py + tooling/trellis-batch.sh + Blender postprocess scripts) → sprite-gen (SKILL.md + scripts/render.sh) → renderer/ (project.godot, render_scene.tscn, render_export.gd; D-019 camera, 3-light rig, 1024/256/64 chain) → client/assets/sprites/. renderer/output/ holds only .gdignore (intermediates gitignored). + +# Produced assets in client/assets (non-.import files) +audio 16 · characters 1002 · fonts 3 · icons 13 · planets 7 · sprites 9 (npc_generic ×4, wall_structural ×4, .gitkeep) · theme 1 · tilesets 1 (.gitkeep only) + +--- +Refinement (2026-06-16, /whats-next Si pass) — READY. This is a PLANNING task: break T-961 into per-class child stories. + +CLASS LIST (8 stories, parented under T-961, each gated on a one-page brief): station walls, rural walls, doors, floors, furniture/props, street furniture (lamp posts), signage/screens (TVs/billboards), rural structures (barns). +- CARS (9th class named in T-750): DEFERRED — Q-067 needs a phase decision (Phase-4 deliverable vs Phase-5+) before any car story. File a deferral note, not a story. +- Q-119 (generic-parent placeholder set as milestone-0): NOT a blocker — sequencing only. On each relevant story note that the generic parents (generic_wall/roof/facade/street + generic_door/floor/furniture; D-235 / wiki/economics/architecture_trait_catalog.toml:33-41) are milestone-0 IF Q-119 resolves "yes". + +BRIEF TEMPLATE: none exists. Create docs/assets/_templates/asset-brief.md (scope · visual reference · naming/resolution conventions · generation path · acceptance), or adopt docs/_templates/design.md. Each story states where its brief + catalog file live under docs/assets/visual/. +READ before filing: docs/assets/README.md, docs/design/{placeholder-art-spec-v01,visual-grammar-v01}.md, docs/workshops/art-direction-mood-board/workshop-outcomes.md, D-235/D-228/D-232, docs/architecture/character-asset-organization.md (the reference model). +NOTE: T-1050 (sibling) owns the catalog + naming conventions these stories reference — sequence T-1049 to consume T-1050''s conventions where they overlap. + +--- +HELD 2026-06-16 (separated from the cascade batch; needs re-scoping before resuming). +The asset pipeline this ticket assumes is sprite-centric (docs/assets/visual/sprites.md +"Entities", _templates/sprite.md, SPR/FUR/TILE/ICO/FX prefixes, the /sprite-gen +render-3D->2D-sprites step). Direction correction: we are NOT producing sprites — Trellis +produces 3D objects (.glb) displayed in-world; the only flattened 2D content is TEXTURES. +Re-scope this ticket around a 3D-model + texture catalog (not a sprite catalog) before +activating. See the sprite-vs-3D root-cause note (this session).', '(description follows in first append) + +Filed 2026-06-12 from the fable-ous.md audit (S-43; citations adversarially verified). +T-961 (two-sentence description, no acceptance criteria, no brief links) is the only asset-production ticket under T-750, while the epic names eight classes; cars, barns, TVs, billboards appear in NO ticket at all (verified against the full corpus). Refine into child stories per class — station walls, rural walls, doors, floors, furniture/props, street furniture (lamp posts), signage/screens (billboards/TVs), rural structures (barns) — each gated on a one-page brief; explicitly record which classes are deferred (cars: see Q-067 audit note). Decide Q-119 (generic-parent placeholder set as first milestone) before sequencing. +Audit per-class status matrix (verified 2026-06-12): +# docs/assets/ survey (13 files + downloads/) + +| File | One-line summary | +|---|---| +| README.md | Master index: audio/visual/video pipelines, status vocabulary (planned/in-progress/placeholder/final), structure that promises visual/{sprites,furniture,tilesets,icons,effects}.md | +| _templates/audio.md | Row + detailed-entry template for audio assets | +| _templates/sprite.md | Row + detailed-entry template for sprites (ID prefixes SPR/FUR/TILE/ICO/FX, status, method, directions columns) | +| _templates/video.md | Row + detailed-entry template for video | +| audio/README.md | ACTIVE production-status catalog — per-category counts table (12 assets tracked), generation tools, bus architecture | +| audio/palette.md | Sonic style bible | +| audio/ambient.md / sfx.md / ui.md | Per-category audio asset tables | +| visual/README.md | STUB — links 5 category files that do not exist, all counts 0 | +| visual/palette.md | STUB — 4 bullet decisions (D-019/D-033/D-045, "functional warmth"), defers to art-direction workshop | +| video/README.md / palette.md | Stubs | +| downloads/ | Purchased character sources (Quaternius Universal Base Characters, Modular Character Outfits FBX) — characters only | + +# Per-asset-class matrix + +| Class | Written brief | Style guide | Naming/res conventions | Gen path | Produced assets | Tickets | +|---|---|---|---|---|---|---| +| Walls (station) | art-direction workshop-outcomes.md §3.3 (v0.1.1 set, pre-cascade) + docs/design/placeholder-art-spec-v01.md §2.1 | visual-grammar-v01.md §1/§4.3, workshop-outcomes §1.8 era palette (partly superseded by D-235), D-235 WallMaterial vocab | renderer/README.md:80-95 (`__.png`, 1024→256→64 chain) | working — renderer/models/wall_structural.tscn rendered | wall_structural ×4 dirs in client/assets/sprites/; wall_bar_green model unrendered; 2 textures | T-961 (backlog), T-988 (token vocab, backlog) | +| Walls (rural) | none found | D-235 tokens only (timber/rammed-earth etc.) | renderer/README.md (generic) | same path, unused | none | T-961 text mention only | +| Doors | placeholder-art-spec-v01.md §2.1 (procedural rects, v0.1) + workshop-outcomes §3.3 `door_sliding` | none for generated world | none for open/closed state frames | same path, unused | none | T-979/T-998 (DoorSpec — logical only), T-961 | +| Floors | placeholder-art-spec-v01.md §2.1 + workshop-outcomes §3.3 (4 floor sprites) | D-228 FloorMaterial axis (open vocab) | 64px visual tile (placeholder-art-spec §1) | same path, unused | none — client/assets/tilesets/ is .gitkeep only | T-959 (tile fill, logical), T-961 | +| Styled buildings | D-232 + D-235 + wiki/economics/architecture_trait_catalog.toml (T-1005 done, ~35 visual bundles) | D-235 material/color-register grammar — tokens only, no rendered art direction | token→fallback-parent chain (catalog header :34-44) | no model/texture path defined for building exteriors | none | T-977, T-988, T-994, T-1004 (all backlog), T-1005 (done) | +| Furniture | docs/assets/visual/furniture.md MISSING (linked); workshop-outcomes §3.3 lists 8-10 objects (pre-cascade) | era table §1.8 only | character-asset-organization.md explicitly excludes furniture ("follow Trellis spike conventions") | proven in spike | spikes/3dpipeline/models/furniture/ ×3 GLB (unpromoted); zero in client/assets | T-961, T-995 (ObjectTag vocab, backlog) | +| Items | docs/design/icon-set-v01.md (HUD/inventory icons only) | D-086 icon constraints | SVG icon_ prefix (client/assets/icons/) | n/a for icons | 13 SVGs in client/assets/icons/ | none for in-world item sprites | +| Cars/vehicles | none found | none | none | spike only | spikes/3dpipeline/models/props/vw_beetle.glb (spike) | none — Q-067 open ("driveable vs NPC traffic?") | +| Lamp posts | none found (zero repo hits) | none | none | unused | none | T-961 text mention only | +| Barns | none found (only T-750 epic text) | none | none | unused | none | none | +| TVs | none found (only T-750 epic text) | none | none | unused | none | none | +| Billboards | none found (only T-750 epic text; other hits = LOD impostors) | none | none | unused | none | none | +| Characters (reference class — the working model) | docs/architecture/character-asset-organization.md + docs/design/character-visuals-spec.md | yes | full (manifest.json, seg_ naming, body-type keys) | proven end-to-end | 1002 files in client/assets/characters/ | done | + +# Generation path (verified files) +image-gen (.claude/skills/image-gen/SKILL.md + scripts/image_connector.py, Gemini) → glb-gen (SKILL.md + tooling/db/trellis_connector.py + tooling/trellis-batch.sh + Blender postprocess scripts) → sprite-gen (SKILL.md + scripts/render.sh) → renderer/ (project.godot, render_scene.tscn, render_export.gd; D-019 camera, 3-light rig, 1024/256/64 chain) → client/assets/sprites/. renderer/output/ holds only .gdignore (intermediates gitignored). + +# Produced assets in client/assets (non-.import files) +audio 16 · characters 1002 · fonts 3 · icons 13 · planets 7 · sprites 9 (npc_generic ×4, wall_structural ×4, .gitkeep) · theme 1 · tilesets 1 (.gitkeep only) + +--- +Refinement (2026-06-16, /whats-next Si pass) — READY. This is a PLANNING task: break T-961 into per-class child stories. + +CLASS LIST (8 stories, parented under T-961, each gated on a one-page brief): station walls, rural walls, doors, floors, furniture/props, street furniture (lamp posts), signage/screens (TVs/billboards), rural structures (barns). +- CARS (9th class named in T-750): DEFERRED — Q-067 needs a phase decision (Phase-4 deliverable vs Phase-5+) before any car story. File a deferral note, not a story. +- Q-119 (generic-parent placeholder set as milestone-0): NOT a blocker — sequencing only. On each relevant story note that the generic parents (generic_wall/roof/facade/street + generic_door/floor/furniture; D-235 / wiki/economics/architecture_trait_catalog.toml:33-41) are milestone-0 IF Q-119 resolves "yes". + +BRIEF TEMPLATE: none exists. Create docs/assets/_templates/asset-brief.md (scope · visual reference · naming/resolution conventions · generation path · acceptance), or adopt docs/_templates/design.md. Each story states where its brief + catalog file live under docs/assets/visual/. +READ before filing: docs/assets/README.md, docs/design/{placeholder-art-spec-v01,visual-grammar-v01}.md, docs/workshops/art-direction-mood-board/workshop-outcomes.md, D-235/D-228/D-232, docs/architecture/character-asset-organization.md (the reference model). +NOTE: T-1050 (sibling) owns the catalog + naming conventions these stories reference — sequence T-1049 to consume T-1050''s conventions where they overlap. + +--- +HELD 2026-06-16 (separated from the cascade batch; needs re-scoping before resuming). +The asset pipeline this ticket assumes is sprite-centric (docs/assets/visual/sprites.md +"Entities", _templates/sprite.md, SPR/FUR/TILE/ICO/FX prefixes, the /sprite-gen +render-3D->2D-sprites step). Direction correction: we are NOT producing sprites — Trellis +produces 3D objects (.glb) displayed in-world; the only flattened 2D content is TEXTURES. +Re-scope this ticket around a 3D-model + texture catalog (not a sprite catalog) before +activating. See the sprite-vs-3D root-cause note (this session). + +Refinement (batch-6 Si pass, 2026-07-25) — SUPERSEDES the 2026-06-16 READY block +(sprite-era, invalidated by D-244) and the 2026-06-16 HELD note. T-1050 (catalog + +conventions.md + manifest.json) and T-1052 (palette.md style bible) are both DONE +(PR #211, merged 2026-07-25) — the conventions this ticket needed to consume now +exist and are read, not hypothetical. + +SCOPE, 3D REALITY: T-961 breaks into per-asset-class stories, each gated on a +one-page brief. A "written brief" is now a short doc that states, per class: +(1) scope — which manifest category (furniture|props) and which D-235 ObjectTag +axis if any; (2) visual reference — cites palette.md §3''s hue/grain/weathering +register for the relevant token(s), or palette.md §2 toon/PBR default for classes +with no D-235 token; (3) naming — conventions.md §1 model naming +(_.glb), §2 mask sidecar if tintable, §4 footprint_tiles if +multi-tile; (4) generation path — /image-gen concept → /glb-gen (Trellis) → +postprocess → promotion into client/assets/models/{furniture,props}/ + +manifest.json entry (conventions.md, models.md); (5) acceptance — N assets at +"planned" status registered in the manifest with a real manifest.json entry, +status flips as production proceeds (models.md''s shared status vocab). + +BRIEF TEMPLATE (create docs/assets/_templates/asset-brief.md): the five headers +above (scope / visual reference / naming / gen path / acceptance), one page, +adopting docs/_templates/design.md frontmatter. Do NOT reuse _templates/sprite.md +(SPR/FUR/TILE/ICO/FX prefixes) — explicitly retired by D-244/T-1050. + +CLASS LIST — station walls, rural walls, doors, floors, furniture/props already +have partial prior art (station walls: wall_structural GLB exists unrendered per +the 2026-06-12 audit matrix; doors: DoorSpec logical model exists, T-979/T-998; +floors: D-228 FloorMaterial axis exists) — each gets a real story + brief, citing +its relevant D-235 axis (wall/roof/facade/street) or, for doors/floors/furniture, +the non-ObjectTag conventions (mask sidecars, footprint_tiles). + +STREET FURNITURE / SIGNAGE-SCREENS / RURAL STRUCTURES (lamp posts, barns, +TVs, billboards — T-1051''s four classes): RECOMMEND FORMAL DEFERRAL, not real +briefs, this batch. Rationale: Phase 4''s deliverable is "deterministic +walkable-world data + per-layer Atlas maps + asset catalog" (cascade table) — +outside-in generation (D-166) needs building EXTERIOR templates +(D-235 wall/roof/facade/street axes) to make districts/blocks walkable and +Atlas-legible. Lamp posts (street furniture), TVs/billboards (signage/screens +placed as interior or exterior set-dressing), and barns (a rural building +*type*, not a wall/roof/facade axis token) are furniture-tier detail objects +that populate a walkable exterior once it exists — they read as Phase 6 +"detail coloring / room-level population" (cascade table) or, at best, late +Phase 4 polish, not blocking asset classes for getting the world walkable. +Zero repo hits exist for any of the four beyond epic text (T-1049''s own 2026-06-12 +audit matrix, unchanged) — there is no partial work to protect by deferring. +Treat identically to Q-067''s car deferral: file a deferral note on the story +(or fold into one placeholder story "T-1051 classes — deferred pending Phase 4 +exterior-generation completion"), do not write real briefs now. Distinguish from +cars only in degree, not in kind — Q-067 is a still-open decision question +(vehicle simulation is a bigger unresolved design surface); the T-1051 four are +simpler set-dressing whose deferral needs no open question, just a scope note, +because nothing about their eventual brief depends on an undecided mechanic. + +Q-119 (generic-parent placeholder set as milestone-0) is STILL OPEN (unresolved +as of this pass) — unchanged from the prior block: not a blocker, sequencing-only. +Each relevant story notes the generic_wall/roof/facade/street (+ generic +door/floor/furniture) parents as milestone-0 IF Q-119 resolves yes. + +READ before filing: docs/assets/visual/{conventions,palette,models,textures, +artwork}.md (T-1050/T-1052, merged), D-244, D-257, D-235 (ratified token list), +D-243 (footprint unit), docs/architecture/character-asset-organization.md +(mask/manifest precedent). + +VERDICT: READY (activation = batch 6; the T-1051 deferral recommendation above is +ratified at activation, then T-1051 is updated to match the ruling this ticket records).', NULL, '2026-07-25 19:00:33', '2026-07-25 19:00:33.469', '2026-07-25 19:00:33.469', NULL, 'f74a4367cd0a4eb2664f4d575e7d31c2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPZ0ZTM1QQXD5K972CMM', 'description', '(description follows in first append)', '(description follows in first append) + +Refinement (batch-6 Si pass, 2026-07-25) — READY. + +WHAT''S WRONG WITH THE CURRENT BRIEFING: frozen at the 2026-03-13 v0.2 pivot. +(1) Frames the role around "v0.2 Pivot — Key Changes" — v0.2 is formally dropped +("No scoping negotiations. Build the base systems fully"), so the framing section +is obsolete, not just outdated. (2) Assumes the sprite-era pipeline throughout +("Phase 1-3 placeholder art pipeline," "sprite replacements" in Phase 4) — +contradicted by D-244 (3D objects in-world, no per-object sprite layer) and D-149 +(live 3D characters). (3) Cites D-019 as simply "top-down art style" with no note +that D-148 superseded it for gameplay (palette.md §1 had to correct exactly this +conflation in its own stub — the briefing repeats the same error). (4) Zone visual +grammar / tile palette framing predates D-235''s ObjectTag vocabulary and D-232''s +trait-template catalog entirely. (5) Character customisation section (D-126, +Q-WTF-039/040) predates the shipped D-159–D-164 body-type + slot-compositing +system and the wardrobe/garment-token pipeline — describes a design question that +has since been built. (6) No mention of the Atlas at all (D-255 stepped ladder, +D-169/D-170 implant UI) despite araminta being a named co-designer of D-255''s +named-feature encoding/payload schema/lakes work. + +WHAT THE REWRITE MUST CONTAIN: +- Role framing: visual art direction for the Phase-4 asset pipeline + Atlas + map-art rendering (not "generator spike prerequisites"). +- palette.md (T-1052, merged) as the SINGLE LIVE STYLE AUTHORITY — point to it, + do not restate camera/color/material content (avoids a second drifting copy; + visual-grammar-v01.md / workshop-outcomes.md supersession is palette.md §5''s + to state, the briefing defers). +- conventions.md (T-1050, merged) for naming/mask/manifest mechanics — note the + category-first naming ruling (conventions.md §1) and the D-257 toon/PBR ruling + with glazing carve-out as HER standing rulings she''ll be asked to extend. +- D-235 ObjectTag vocabulary as the logical layer she renders a look on top of — + co-maintained with Miri per D-235''s own text. +- The character wardrobe pipeline (character-asset-organization.md) as the proven + reference model conventions.md imitates (mask sidecars, manifest schema). +- D-255 stepped Atlas as active/ongoing work she co-designed. +- Current open items: T-1049 (per-class asset stories, re-scoped this batch), the + D-257 glazing shader implementation (unassigned per palette.md §2.3), Q-119 + (generic-parent milestone question, hers to weigh in on per D-235 + co-maintenance). +- Drop: v0.2 pivot section, Q-WTF-039/040, tycoon-bookmark/apartment-wakeup + framing, and the v0.2-era decision list (D-114/D-117/D-119/D-122/D-128/D-131/ + D-135) — verify each against current decision status before dropping outright; + a few (D-033 entity color, D-126 customisation dimensions) may still have live + sub-parts even though the surrounding section is obsolete. + +ACCEPTANCE: rewritten briefing reflects the current Phase-4 landscape (D-235, +D-244, D-257, D-255), points to palette.md/conventions.md as live authorities +rather than duplicating content, drops v0.2-pivot and superseded-question +framing, lists araminta''s actual current/open tickets. + +SEQUENCING: independent of T-1049 (different files, no shared input) — both can +run in the same batch.', NULL, '2026-07-25 19:00:36', '2026-07-25 19:00:36.464', '2026-07-25 19:00:36.464', NULL, 'ebe504fa1213997aad9ae74f82212f6f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSM3MQ5GFSPKKFJF6GGZN8CM', 'description', 'Araminta''s PR #192 companion note, kept open by her PR #208 ruling (option b): the Quarter-vs-District relief-differentiation check was accepted for the T-1160 merge on a plains/river substitution (GJ380c world center -17723392,-10532864 — Quarter clearly shows finer higher-frequency texture than District), but plains texture and jagged tectonic relief are different visual regimes and the sharper case still needs its own capture. Task: identify the body pool with tectonics:high (or planet_class values mapping to TectonicClass::Volcanic/Active) and RECORD the pool in this ticket first — the PR #208 eyeball proved GJ380c/GJ820Bc are both tectonics:low via exhaustive two-Global color scans, don''t re-run that search blind — then capture quarter-relief vs district-relief at a true Alpine/MountainPass crop (MorphologyZone 13/14) at the same world center, matched crop, and have Araminta judge the differentiation. Reuse the pr208_driver fixed-center --step protocol (T-1157 harness inventory). Related: T-1160, PR #192, PR #208.', 'Araminta''s PR #192 companion note, kept open by her PR #208 ruling (option b): the Quarter-vs-District relief-differentiation check was accepted for the T-1160 merge on a plains/river substitution (GJ380c world center -17723392,-10532864 — Quarter clearly shows finer higher-frequency texture than District), but plains texture and jagged tectonic relief are different visual regimes and the sharper case still needs its own capture. Task: identify the body pool with tectonics:high (or planet_class values mapping to TectonicClass::Volcanic/Active) and RECORD the pool in this ticket first — the PR #208 eyeball proved GJ380c/GJ820Bc are both tectonics:low via exhaustive two-Global color scans, don''t re-run that search blind — then capture quarter-relief vs district-relief at a true Alpine/MountainPass crop (MorphologyZone 13/14) at the same world center, matched crop, and have Araminta judge the differentiation. Reuse the pr208_driver fixed-center --step protocol (T-1157 harness inventory). Related: T-1160, PR #192, PR #208. + +POOL RECORDED (2026-07-25, read-only survey per this ticket''s own first step — +do not re-run the search blind). + +MAPPING MECHANISM: server/src/atlas/district_profile.rs:296-312 +derive_tectonic_class(): BodyParams::tectonic_activity authored override first +("volcanic"/"active"/"tidally_forced", NO column in current systems.db schema and +no authored overrides in the wiki TOMLs — so today the override path is dead), +else planet_class fallback: "volcanic" → Volcanic; "oceanic"/"ocean_world" → +Active; everything else → Stable. Sanity anchors agree with the PR #208 eyeball: +GJ380c (arid) and GJ820Bc (arid) both → Stable. + +THE FULL POOL (7 bodies): +- Volcanic: GJ0c (Venus, no heightmap), GJ0f-1 (Io, no heightmap), GJ4056b + (heightmap, airless, relief_energy_q=1.0), GJ660Ad (Seongho Prime, heightmap, + thin atmosphere, relief_energy_q=1.0) +- Active: GJ1002b (Caparica, heightmap, island world, standard atmosphere), + GJ103c (Vlieland, heightmap, island world, standard atmosphere), GJ447c + (Marevna, heightmap, ocean world) — all relief_energy_q=0.70 per D-239 §5. + +CAPTURE RECOMMENDATION (lead): for the true Alpine/MountainPass (MorphologyZone +13/14) crop this ticket wants, the Active terrestrial island worlds GJ1002b and +GJ103c are the primary candidates (coastal orogeny, standard climate — closest +analogue to the GJ380c plains/river substitution being replaced). The Volcanic +pair (GJ4056b, GJ660Ad) are alternates with maximum relief energy but +airless/thin atmospheres — shield/lava-field morphology, likely NOT +Alpine-classed; use only if the Active pair fails to produce MorphologyZone +13/14 crops. GJ0c/GJ0f-1 lack terrain_reference and cannot open in the Atlas.', NULL, '2026-07-25 19:00:39', '2026-07-25 19:00:39.731', '2026-07-25 19:00:39.731', NULL, '194ab5efa1ff3b059c0296830334f801', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/tickets/2026-07.sql b/.pql/changelog/tickets/2026-07.sql index bc217daed..29536e861 100644 --- a/.pql/changelog/tickets/2026-07.sql +++ b/.pql/changelog/tickets/2026-07.sql @@ -3887,3 +3887,215 @@ archiving/marking those superseded, or by palette.md stating exactly what it inh Lead note (batch-5 activation): the toon-vs-PBR treatment call for environment props is delegated to araminta (art seat) this batch — her ruling lands in palette.md with rationale; surfaced to Jeroen in the batch report for cheap veto. VERDICT: READY.', 'done', 'medium', 'araminta', 'visual', 'D-235', '2026-06-12 10:40:59', '2026-07-25 18:55:08.787', NULL, '0c2f33e6e2b24c61cff46c61d2df9a8f', 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 ('06FBPTXPE8FR7TGEM7X6QVPV4M', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Refine T-961 into per-asset-class stories, each gated on a written brief', '(description follows in first append) + +Filed 2026-06-12 from the fable-ous.md audit (S-43; citations adversarially verified). +T-961 (two-sentence description, no acceptance criteria, no brief links) is the only asset-production ticket under T-750, while the epic names eight classes; cars, barns, TVs, billboards appear in NO ticket at all (verified against the full corpus). Refine into child stories per class — station walls, rural walls, doors, floors, furniture/props, street furniture (lamp posts), signage/screens (billboards/TVs), rural structures (barns) — each gated on a one-page brief; explicitly record which classes are deferred (cars: see Q-067 audit note). Decide Q-119 (generic-parent placeholder set as first milestone) before sequencing. +Audit per-class status matrix (verified 2026-06-12): +# docs/assets/ survey (13 files + downloads/) + +| File | One-line summary | +|---|---| +| README.md | Master index: audio/visual/video pipelines, status vocabulary (planned/in-progress/placeholder/final), structure that promises visual/{sprites,furniture,tilesets,icons,effects}.md | +| _templates/audio.md | Row + detailed-entry template for audio assets | +| _templates/sprite.md | Row + detailed-entry template for sprites (ID prefixes SPR/FUR/TILE/ICO/FX, status, method, directions columns) | +| _templates/video.md | Row + detailed-entry template for video | +| audio/README.md | ACTIVE production-status catalog — per-category counts table (12 assets tracked), generation tools, bus architecture | +| audio/palette.md | Sonic style bible | +| audio/ambient.md / sfx.md / ui.md | Per-category audio asset tables | +| visual/README.md | STUB — links 5 category files that do not exist, all counts 0 | +| visual/palette.md | STUB — 4 bullet decisions (D-019/D-033/D-045, "functional warmth"), defers to art-direction workshop | +| video/README.md / palette.md | Stubs | +| downloads/ | Purchased character sources (Quaternius Universal Base Characters, Modular Character Outfits FBX) — characters only | + +# Per-asset-class matrix + +| Class | Written brief | Style guide | Naming/res conventions | Gen path | Produced assets | Tickets | +|---|---|---|---|---|---|---| +| Walls (station) | art-direction workshop-outcomes.md §3.3 (v0.1.1 set, pre-cascade) + docs/design/placeholder-art-spec-v01.md §2.1 | visual-grammar-v01.md §1/§4.3, workshop-outcomes §1.8 era palette (partly superseded by D-235), D-235 WallMaterial vocab | renderer/README.md:80-95 (`__.png`, 1024→256→64 chain) | working — renderer/models/wall_structural.tscn rendered | wall_structural ×4 dirs in client/assets/sprites/; wall_bar_green model unrendered; 2 textures | T-961 (backlog), T-988 (token vocab, backlog) | +| Walls (rural) | none found | D-235 tokens only (timber/rammed-earth etc.) | renderer/README.md (generic) | same path, unused | none | T-961 text mention only | +| Doors | placeholder-art-spec-v01.md §2.1 (procedural rects, v0.1) + workshop-outcomes §3.3 `door_sliding` | none for generated world | none for open/closed state frames | same path, unused | none | T-979/T-998 (DoorSpec — logical only), T-961 | +| Floors | placeholder-art-spec-v01.md §2.1 + workshop-outcomes §3.3 (4 floor sprites) | D-228 FloorMaterial axis (open vocab) | 64px visual tile (placeholder-art-spec §1) | same path, unused | none — client/assets/tilesets/ is .gitkeep only | T-959 (tile fill, logical), T-961 | +| Styled buildings | D-232 + D-235 + wiki/economics/architecture_trait_catalog.toml (T-1005 done, ~35 visual bundles) | D-235 material/color-register grammar — tokens only, no rendered art direction | token→fallback-parent chain (catalog header :34-44) | no model/texture path defined for building exteriors | none | T-977, T-988, T-994, T-1004 (all backlog), T-1005 (done) | +| Furniture | docs/assets/visual/furniture.md MISSING (linked); workshop-outcomes §3.3 lists 8-10 objects (pre-cascade) | era table §1.8 only | character-asset-organization.md explicitly excludes furniture ("follow Trellis spike conventions") | proven in spike | spikes/3dpipeline/models/furniture/ ×3 GLB (unpromoted); zero in client/assets | T-961, T-995 (ObjectTag vocab, backlog) | +| Items | docs/design/icon-set-v01.md (HUD/inventory icons only) | D-086 icon constraints | SVG icon_ prefix (client/assets/icons/) | n/a for icons | 13 SVGs in client/assets/icons/ | none for in-world item sprites | +| Cars/vehicles | none found | none | none | spike only | spikes/3dpipeline/models/props/vw_beetle.glb (spike) | none — Q-067 open ("driveable vs NPC traffic?") | +| Lamp posts | none found (zero repo hits) | none | none | unused | none | T-961 text mention only | +| Barns | none found (only T-750 epic text) | none | none | unused | none | none | +| TVs | none found (only T-750 epic text) | none | none | unused | none | none | +| Billboards | none found (only T-750 epic text; other hits = LOD impostors) | none | none | unused | none | none | +| Characters (reference class — the working model) | docs/architecture/character-asset-organization.md + docs/design/character-visuals-spec.md | yes | full (manifest.json, seg_ naming, body-type keys) | proven end-to-end | 1002 files in client/assets/characters/ | done | + +# Generation path (verified files) +image-gen (.claude/skills/image-gen/SKILL.md + scripts/image_connector.py, Gemini) → glb-gen (SKILL.md + tooling/db/trellis_connector.py + tooling/trellis-batch.sh + Blender postprocess scripts) → sprite-gen (SKILL.md + scripts/render.sh) → renderer/ (project.godot, render_scene.tscn, render_export.gd; D-019 camera, 3-light rig, 1024/256/64 chain) → client/assets/sprites/. renderer/output/ holds only .gdignore (intermediates gitignored). + +# Produced assets in client/assets (non-.import files) +audio 16 · characters 1002 · fonts 3 · icons 13 · planets 7 · sprites 9 (npc_generic ×4, wall_structural ×4, .gitkeep) · theme 1 · tilesets 1 (.gitkeep only) + +--- +Refinement (2026-06-16, /whats-next Si pass) — READY. This is a PLANNING task: break T-961 into per-class child stories. + +CLASS LIST (8 stories, parented under T-961, each gated on a one-page brief): station walls, rural walls, doors, floors, furniture/props, street furniture (lamp posts), signage/screens (TVs/billboards), rural structures (barns). +- CARS (9th class named in T-750): DEFERRED — Q-067 needs a phase decision (Phase-4 deliverable vs Phase-5+) before any car story. File a deferral note, not a story. +- Q-119 (generic-parent placeholder set as milestone-0): NOT a blocker — sequencing only. On each relevant story note that the generic parents (generic_wall/roof/facade/street + generic_door/floor/furniture; D-235 / wiki/economics/architecture_trait_catalog.toml:33-41) are milestone-0 IF Q-119 resolves "yes". + +BRIEF TEMPLATE: none exists. Create docs/assets/_templates/asset-brief.md (scope · visual reference · naming/resolution conventions · generation path · acceptance), or adopt docs/_templates/design.md. Each story states where its brief + catalog file live under docs/assets/visual/. +READ before filing: docs/assets/README.md, docs/design/{placeholder-art-spec-v01,visual-grammar-v01}.md, docs/workshops/art-direction-mood-board/workshop-outcomes.md, D-235/D-228/D-232, docs/architecture/character-asset-organization.md (the reference model). +NOTE: T-1050 (sibling) owns the catalog + naming conventions these stories reference — sequence T-1049 to consume T-1050''s conventions where they overlap. + +--- +HELD 2026-06-16 (separated from the cascade batch; needs re-scoping before resuming). +The asset pipeline this ticket assumes is sprite-centric (docs/assets/visual/sprites.md +"Entities", _templates/sprite.md, SPR/FUR/TILE/ICO/FX prefixes, the /sprite-gen +render-3D->2D-sprites step). Direction correction: we are NOT producing sprites — Trellis +produces 3D objects (.glb) displayed in-world; the only flattened 2D content is TEXTURES. +Re-scope this ticket around a 3D-model + texture catalog (not a sprite catalog) before +activating. See the sprite-vs-3D root-cause note (this session). + +Refinement (batch-6 Si pass, 2026-07-25) — SUPERSEDES the 2026-06-16 READY block +(sprite-era, invalidated by D-244) and the 2026-06-16 HELD note. T-1050 (catalog + +conventions.md + manifest.json) and T-1052 (palette.md style bible) are both DONE +(PR #211, merged 2026-07-25) — the conventions this ticket needed to consume now +exist and are read, not hypothetical. + +SCOPE, 3D REALITY: T-961 breaks into per-asset-class stories, each gated on a +one-page brief. A "written brief" is now a short doc that states, per class: +(1) scope — which manifest category (furniture|props) and which D-235 ObjectTag +axis if any; (2) visual reference — cites palette.md §3''s hue/grain/weathering +register for the relevant token(s), or palette.md §2 toon/PBR default for classes +with no D-235 token; (3) naming — conventions.md §1 model naming +(_.glb), §2 mask sidecar if tintable, §4 footprint_tiles if +multi-tile; (4) generation path — /image-gen concept → /glb-gen (Trellis) → +postprocess → promotion into client/assets/models/{furniture,props}/ + +manifest.json entry (conventions.md, models.md); (5) acceptance — N assets at +"planned" status registered in the manifest with a real manifest.json entry, +status flips as production proceeds (models.md''s shared status vocab). + +BRIEF TEMPLATE (create docs/assets/_templates/asset-brief.md): the five headers +above (scope / visual reference / naming / gen path / acceptance), one page, +adopting docs/_templates/design.md frontmatter. Do NOT reuse _templates/sprite.md +(SPR/FUR/TILE/ICO/FX prefixes) — explicitly retired by D-244/T-1050. + +CLASS LIST — station walls, rural walls, doors, floors, furniture/props already +have partial prior art (station walls: wall_structural GLB exists unrendered per +the 2026-06-12 audit matrix; doors: DoorSpec logical model exists, T-979/T-998; +floors: D-228 FloorMaterial axis exists) — each gets a real story + brief, citing +its relevant D-235 axis (wall/roof/facade/street) or, for doors/floors/furniture, +the non-ObjectTag conventions (mask sidecars, footprint_tiles). + +STREET FURNITURE / SIGNAGE-SCREENS / RURAL STRUCTURES (lamp posts, barns, +TVs, billboards — T-1051''s four classes): RECOMMEND FORMAL DEFERRAL, not real +briefs, this batch. Rationale: Phase 4''s deliverable is "deterministic +walkable-world data + per-layer Atlas maps + asset catalog" (cascade table) — +outside-in generation (D-166) needs building EXTERIOR templates +(D-235 wall/roof/facade/street axes) to make districts/blocks walkable and +Atlas-legible. Lamp posts (street furniture), TVs/billboards (signage/screens +placed as interior or exterior set-dressing), and barns (a rural building +*type*, not a wall/roof/facade axis token) are furniture-tier detail objects +that populate a walkable exterior once it exists — they read as Phase 6 +"detail coloring / room-level population" (cascade table) or, at best, late +Phase 4 polish, not blocking asset classes for getting the world walkable. +Zero repo hits exist for any of the four beyond epic text (T-1049''s own 2026-06-12 +audit matrix, unchanged) — there is no partial work to protect by deferring. +Treat identically to Q-067''s car deferral: file a deferral note on the story +(or fold into one placeholder story "T-1051 classes — deferred pending Phase 4 +exterior-generation completion"), do not write real briefs now. Distinguish from +cars only in degree, not in kind — Q-067 is a still-open decision question +(vehicle simulation is a bigger unresolved design surface); the T-1051 four are +simpler set-dressing whose deferral needs no open question, just a scope note, +because nothing about their eventual brief depends on an undecided mechanic. + +Q-119 (generic-parent placeholder set as milestone-0) is STILL OPEN (unresolved +as of this pass) — unchanged from the prior block: not a blocker, sequencing-only. +Each relevant story notes the generic_wall/roof/facade/street (+ generic +door/floor/furniture) parents as milestone-0 IF Q-119 resolves yes. + +READ before filing: docs/assets/visual/{conventions,palette,models,textures, +artwork}.md (T-1050/T-1052, merged), D-244, D-257, D-235 (ratified token list), +D-243 (footprint unit), docs/architecture/character-asset-organization.md +(mask/manifest precedent). + +VERDICT: READY (activation = batch 6; the T-1051 deferral recommendation above is +ratified at activation, then T-1051 is updated to match the ruling this ticket records).', 'backlog', 'high', NULL, 'visual', NULL, '2026-06-12 10:40:58', '2026-07-25 19:00:33.468', NULL, 'e7ab3b683997f5e8bf5e80ab8d270840', 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 ('06FBPTXPZ0ZTM1QQXD5K972CMM', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Rewrite docs/briefings/araminta.md around the Phase-4 asset pipeline', '(description follows in first append) + +Refinement (batch-6 Si pass, 2026-07-25) — READY. + +WHAT''S WRONG WITH THE CURRENT BRIEFING: frozen at the 2026-03-13 v0.2 pivot. +(1) Frames the role around "v0.2 Pivot — Key Changes" — v0.2 is formally dropped +("No scoping negotiations. Build the base systems fully"), so the framing section +is obsolete, not just outdated. (2) Assumes the sprite-era pipeline throughout +("Phase 1-3 placeholder art pipeline," "sprite replacements" in Phase 4) — +contradicted by D-244 (3D objects in-world, no per-object sprite layer) and D-149 +(live 3D characters). (3) Cites D-019 as simply "top-down art style" with no note +that D-148 superseded it for gameplay (palette.md §1 had to correct exactly this +conflation in its own stub — the briefing repeats the same error). (4) Zone visual +grammar / tile palette framing predates D-235''s ObjectTag vocabulary and D-232''s +trait-template catalog entirely. (5) Character customisation section (D-126, +Q-WTF-039/040) predates the shipped D-159–D-164 body-type + slot-compositing +system and the wardrobe/garment-token pipeline — describes a design question that +has since been built. (6) No mention of the Atlas at all (D-255 stepped ladder, +D-169/D-170 implant UI) despite araminta being a named co-designer of D-255''s +named-feature encoding/payload schema/lakes work. + +WHAT THE REWRITE MUST CONTAIN: +- Role framing: visual art direction for the Phase-4 asset pipeline + Atlas + map-art rendering (not "generator spike prerequisites"). +- palette.md (T-1052, merged) as the SINGLE LIVE STYLE AUTHORITY — point to it, + do not restate camera/color/material content (avoids a second drifting copy; + visual-grammar-v01.md / workshop-outcomes.md supersession is palette.md §5''s + to state, the briefing defers). +- conventions.md (T-1050, merged) for naming/mask/manifest mechanics — note the + category-first naming ruling (conventions.md §1) and the D-257 toon/PBR ruling + with glazing carve-out as HER standing rulings she''ll be asked to extend. +- D-235 ObjectTag vocabulary as the logical layer she renders a look on top of — + co-maintained with Miri per D-235''s own text. +- The character wardrobe pipeline (character-asset-organization.md) as the proven + reference model conventions.md imitates (mask sidecars, manifest schema). +- D-255 stepped Atlas as active/ongoing work she co-designed. +- Current open items: T-1049 (per-class asset stories, re-scoped this batch), the + D-257 glazing shader implementation (unassigned per palette.md §2.3), Q-119 + (generic-parent milestone question, hers to weigh in on per D-235 + co-maintenance). +- Drop: v0.2 pivot section, Q-WTF-039/040, tycoon-bookmark/apartment-wakeup + framing, and the v0.2-era decision list (D-114/D-117/D-119/D-122/D-128/D-131/ + D-135) — verify each against current decision status before dropping outright; + a few (D-033 entity color, D-126 customisation dimensions) may still have live + sub-parts even though the surrounding section is obsolete. + +ACCEPTANCE: rewritten briefing reflects the current Phase-4 landscape (D-235, +D-244, D-257, D-255), points to palette.md/conventions.md as live authorities +rather than duplicating content, drops v0.2-pivot and superseded-question +framing, lists araminta''s actual current/open tickets. + +SEQUENCING: independent of T-1049 (different files, no shared input) — both can +run in the same batch.', 'backlog', 'medium', NULL, 'visual', NULL, '2026-06-12 10:40:59', '2026-07-25 19:00:36.463', NULL, '6080da556144a938b435764dd8ed6a18', 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 ('06FSM3MQ5GFSPKKFJF6GGZN8CM', 'task', '06FBPPMZNNEV052DBYYY3A897C', 'Alpine relief capture — Quarter-vs-District differentiation on a tectonics:high body', 'Araminta''s PR #192 companion note, kept open by her PR #208 ruling (option b): the Quarter-vs-District relief-differentiation check was accepted for the T-1160 merge on a plains/river substitution (GJ380c world center -17723392,-10532864 — Quarter clearly shows finer higher-frequency texture than District), but plains texture and jagged tectonic relief are different visual regimes and the sharper case still needs its own capture. Task: identify the body pool with tectonics:high (or planet_class values mapping to TectonicClass::Volcanic/Active) and RECORD the pool in this ticket first — the PR #208 eyeball proved GJ380c/GJ820Bc are both tectonics:low via exhaustive two-Global color scans, don''t re-run that search blind — then capture quarter-relief vs district-relief at a true Alpine/MountainPass crop (MorphologyZone 13/14) at the same world center, matched crop, and have Araminta judge the differentiation. Reuse the pr208_driver fixed-center --step protocol (T-1157 harness inventory). Related: T-1160, PR #192, PR #208. + +POOL RECORDED (2026-07-25, read-only survey per this ticket''s own first step — +do not re-run the search blind). + +MAPPING MECHANISM: server/src/atlas/district_profile.rs:296-312 +derive_tectonic_class(): BodyParams::tectonic_activity authored override first +("volcanic"/"active"/"tidally_forced", NO column in current systems.db schema and +no authored overrides in the wiki TOMLs — so today the override path is dead), +else planet_class fallback: "volcanic" → Volcanic; "oceanic"/"ocean_world" → +Active; everything else → Stable. Sanity anchors agree with the PR #208 eyeball: +GJ380c (arid) and GJ820Bc (arid) both → Stable. + +THE FULL POOL (7 bodies): +- Volcanic: GJ0c (Venus, no heightmap), GJ0f-1 (Io, no heightmap), GJ4056b + (heightmap, airless, relief_energy_q=1.0), GJ660Ad (Seongho Prime, heightmap, + thin atmosphere, relief_energy_q=1.0) +- Active: GJ1002b (Caparica, heightmap, island world, standard atmosphere), + GJ103c (Vlieland, heightmap, island world, standard atmosphere), GJ447c + (Marevna, heightmap, ocean world) — all relief_energy_q=0.70 per D-239 §5. + +CAPTURE RECOMMENDATION (lead): for the true Alpine/MountainPass (MorphologyZone +13/14) crop this ticket wants, the Active terrestrial island worlds GJ1002b and +GJ103c are the primary candidates (coastal orogeny, standard climate — closest +analogue to the GJ380c plains/river substitution being replaced). The Volcanic +pair (GJ4056b, GJ660Ad) are alternates with maximum relief energy but +airless/thin atmospheres — shield/lava-field morphology, likely NOT +Alpine-classed; use only if the Active pair fails to produce MorphologyZone +13/14 crops. GJ0c/GJ0f-1 lack terrain_reference and cannot open in the Atlas.', 'ready', 'low', NULL, 'client', NULL, '2026-07-25 16:14:35.308', '2026-07-25 19:00:39.730', NULL, 'c56c7d2a7a030ee4e959478459ee3d12', 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);