diff --git a/docs/sprints/sprint-16/client.md b/docs/sprints/sprint-16/client.md new file mode 100644 index 000000000..5050645dd --- /dev/null +++ b/docs/sprints/sprint-16/client.md @@ -0,0 +1,98 @@ +# Sprint 16: Converse — Client Tasks + +**Goal:** Dialogue becomes a two-way exchange — players pick options and the server responds; NPC information-giving deepens through trust and trait-shaped delivery; the sprite pipeline gets its correct art-direction angle; and housekeeping fixes land across all three active teams. + +**Branch:** `client` +**Agents:** Stig (dev), Tyre (arch), Hoshe (QA) + +## Carry-over from Sprint 15 + +None. Sprint 15 was 100% complete (16/16 done). + +## New Tickets + +| # | Title | Blocked by | +|---|-------|------------| +| #543 | Optimize zone_id extraction O(N) → O(1) dictionary lookup | — | +| #540 | Adjust sprite camera to D-019 angle (~15-20° from vertical) | #541 (visual, must land first) | + +Use `db/connectors/ticket show ` for full details. + +## Key Decisions + +- `decisions/perception.md` — D-019 (top-down camera, angle amendment: ~15-20° from vertical, "the angle"), D-043 (visual style, functional warmth), D-049 (z-level rendering stack) +- `decisions/architecture.md` — D-066 (dual-scale grid: 0.5m sim, 1m visual, 2x retina), D-077 (zone temperature memory, server-tracked zone_id) + +## Open Questions to Resolve Early + +None blocking either ticket. + +## Notes + +### #543 — Optimize zone_id extraction O(N) → O(1) dictionary lookup + +**What exists:** `client/scripts/autoloads/game_state.gd` `apply_snapshot()` function. The current zone_id extraction (lines 243-252) does a linear scan of `visible_tiles` on every snapshot to find the tile whose coordinates match the player position: + +```gdscript +current_zone_id = "" +var _px := int(player_position.x) +var _py := int(player_position.y) +for _ztile in visible_tiles: + if _ztile is Dictionary and _ztile.get("x") == _px and _ztile.get("y") == _py: + current_zone_id = _ztile.get("zone_id", "") + break +``` + +Within the same `apply_snapshot()` call (lines 256-268), `visible_tiles` is already iterated to build `visible_positions` (a `Dictionary` of `Vector2i → true`). The optimization is to build a second dictionary `_tile_by_coord: Dictionary` (mapping `Vector2i → tile Dictionary`) in that same existing loop, then replace the O(N) zone_id scan with an O(1) lookup. + +**What to deliver:** + +1. Declare `_tile_by_coord` as a local variable in `apply_snapshot()` (or as a member var if AudioManager or others need tile data by coord) +2. In the existing `visible_tiles` iteration loop (lines 256-268), add: `_tile_by_coord[pos] = vtile` +3. Replace the O(N) zone_id scan (lines 243-252) with: + ```gdscript + var player_pos_key := Vector2i(int(player_position.x), int(player_position.y)) + var player_tile = _tile_by_coord.get(player_pos_key, null) + current_zone_id = player_tile.get("zone_id", "") if player_tile else "" + ``` +4. Remove the old loop block (lines 249-252) + +**Gotcha:** The zone_id scan (lines 243-252) currently runs BEFORE the `visible_tiles` iteration that builds `visible_positions` (lines 256-268). You need to reorder: build `_tile_by_coord` in the main tile loop first, then do the zone_id lookup. The comment on line 244 ("O(1) via visible_positions dict would be ideal") confirms this was known and flagged by Tyre in a PR review — this ticket resolves it. + +**Net-zero complexity:** The tile loop is already there. This adds one dict-set per tile in the existing loop, then removes the separate zone_id scan loop entirely. No behavioral change; the same `current_zone_id` value is produced. + +**Test:** Verify `current_zone_id` is populated correctly after the refactor by checking the existing client P-tests or adding a unit test in gdUnit4. + +### #540 — Adjust sprite camera to D-019 angle (~15-20° from vertical) + +**Blocked by #541 (visual).** Do not start until Araminta's 3D sprite render pipeline is set up and has produced at least a test sprite at the new angle. + +**What exists:** The Godot client renders entity, object, and wall sprites. Current sprites are rendered at 90° (pure top-down). D-019 amendment specifies "the angle": Camera3D at -72.5° from horizontal (= ~17.5° from vertical, the midpoint of the 15-20° range). This matches Rimworld's orthographic-with-art-tilt approach. The Godot gameplay camera stays orthographic — the tilt is entirely an art convention baked into sprites. + +**What to deliver:** + +Once #541 delivers the offline render pipeline and a set of test sprites at -72.5°: +1. Update `client/scripts/rendering/entity_renderer.gd` to reference the new sprite assets (path may change per Araminta's pipeline output structure) +2. Verify `client/scripts/rendering/tile_renderer.gd` and `world_renderer.gd` — wall/object sprites from #541 should slot in at the same visual tile size (64×64px runtime per D-043 resolution chain) +3. Confirm the z-sorting logic in `entity_renderer.gd` still produces correct results: entities south-facing should overlap objects on the same row correctly with the tilted perspective +4. Check that the fog shader in `fog_shader.gd` is unaffected — fog is screen-space and driven by the LOS mask, not by sprite perspective (D-019: "tile grid remains square/orthogonal, vision cone math remains pure 2D") + +**Key constraint from D-066:** Entity sprites render across a 2×2 sim tile footprint. With the tilt, the south-facing face of entities/objects is now visible — confirm the sprite footprint is still contained within the 2×2 sim tile bounding box so interaction range (2 sim tiles) remains accurate. + +**What NOT to change:** The `client/scripts/rendering/fog_shader.gd`, fog vision cone shape, or any LOS computation. Those are 2D sim-space and are unaffected by the art-direction angle. + +## Dependency Chain + +``` +#543 (zone_id O(1)) → standalone, no dependencies + +#541 (visual: 3D render pipeline) → #540 (sprite camera angle) +``` + +## PR Workflow + +When ready to submit, create a PR with `tea` CLI. **All flags are required** to avoid TTY prompts (see CLAUDE.md "Gitea access" section): + +```bash +tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(client): sprint 16 zone lookup + sprite angle" --description "body" --base main --head client +``` diff --git a/docs/sprints/sprint-16/copy.md b/docs/sprints/sprint-16/copy.md new file mode 100644 index 000000000..a86f70bcb --- /dev/null +++ b/docs/sprints/sprint-16/copy.md @@ -0,0 +1,90 @@ +# Sprint 16: Converse — Copy Tasks + +**Goal:** Dialogue becomes a two-way exchange — players pick options and the server responds; NPC information-giving deepens through trust and trait-shaped delivery; the sprite pipeline gets its correct art-direction angle; and housekeeping fixes land across all three active teams. + +**Branch:** `copy` +**Agents:** Mellanie (author), Paula (narrative), Gestalt (systems) + +## Carry-over from Sprint 15 + +None. Sprint 15 was 100% complete (16/16 done). + +## New Tickets + +| # | Title | Blocked by | +|---|-------|------------| +| #542 | Migrate line IDs from location-scoped to NPC-scoped namespace | — | + +Use `db/connectors/ticket show 542` for full details. + +## Key Decisions + +- `decisions/content.md` — D-035 (tag taxonomy; Sprint 15 amendment: line ID namespace change from location-scoped to NPC-scoped), D-028 (dialogue architecture, line pool format) +- `decisions/process.md` — D-022 (process workflow) + +## Notes + +### #542 — Migrate line IDs from location-scoped to NPC-scoped namespace + +**What exists:** All dialogue and monologue YAML files under `content/campaigns/main/systems/krenn/stations/sova/districts/transit/`. There are approximately 20 dialogue pool files and a set of monologue pool files. The current line ID scheme uses the location as prefix: + +- Old: `the-terminal_d_039` (all NPCs at The Terminal share one ID sequence) +- New: `kael-davan_d_001`, `dock-worker_d_001` (each NPC has an independent sequence) + +The D-035 Sprint 15 amendment confirmed this change. The collision that prompted it (`the-terminal_d_039` appeared in multiple NPC files) demonstrates the old scheme does not scale to procedurally generated populations. + +**Dialogue pool files to update (location + NPC name):** +- `dialogue/maintenance-corridors/kael-davan.yaml` +- `dialogue/maintenance-corridors/ring-operative.yaml` +- `dialogue/maintenance-corridors/transit-worker.yaml` +- `dialogue/the-last-shift/bar-owner.yaml` +- `dialogue/the-last-shift/bar-regular.yaml` +- `dialogue/the-last-shift/bartender.yaml` +- `dialogue/the-last-shift/day-worker.yaml` +- `dialogue/the-last-shift/kael-davan.yaml` +- `dialogue/the-last-shift/pc-detective.yaml` +- `dialogue/the-last-shift/pc-smuggler.yaml` +- `dialogue/the-last-shift/sera-venn.yaml` +- `dialogue/the-terminal/courier.yaml` +- `dialogue/the-terminal/dock-worker.yaml` +- `dialogue/the-terminal/kael-davan.yaml` +- `dialogue/the-terminal/maintenance-tech.yaml` +- `dialogue/the-terminal/new-hire.yaml` +- `dialogue/the-terminal/pc-detective.yaml` +- `dialogue/the-terminal/pc-smuggler.yaml` +- `dialogue/the-terminal/scheduler.yaml` +- `dialogue/the-terminal/shift-supervisor.yaml` + +**Monologue files:** Check all `monologue/detective/*.yaml` and `monologue/smuggler/*.yaml` (if present) for any `m_` prefixed line IDs that use the old location-slug convention. + +**ID scheme rules:** + +For each NPC file, assign a fresh sequence starting at `_001`: +- Dialogue lines: `{npc-slug}_d_{###}` — e.g., `kael-davan_d_001` +- Monologue lines: `{npc-slug}_m_s_{###}` (smuggler) or `{npc-slug}_m_d_{###}` (detective) per D-032 + +The npc-slug is the NPC's name in kebab-case: `kael-davan`, `dock-worker`, `shift-supervisor`, `pc-smuggler`, `pc-detective`, etc. + +Each file's IDs restart at `_001` — do not carry a running counter across files. The 999-line ceiling is per-NPC, not per-location. + +**Schema:** The `dialogue-pool.schema.json` regex pattern already accepts the new format (`^[a-z][a-z0-9-]*`). Only the `description` field in the schema needs updating to reference the new convention. No code changes required — line IDs are opaque strings to the server. + +**Authoring docs:** Update `docs/workshops/content-gap-analysis_v0_1/` or any voice guide that references the old `{location_slug}` convention. Check `docs/briefings/mellanie.md` and related copy briefings for any hardcoded examples. + +**Golden tests:** Run `make pre-pr` after all renames to confirm no golden test snapshots reference old IDs. The content cross-reference validation (CI ticket #464, done) will catch any mismatches between schema and file content. + +**This is a mechanical rename with zero content changes.** Do not rewrite lines, adjust trust tiers, or add new content in this ticket. Pure ID substitution only. + +## Dependency Chain + +``` +#542 (line ID namespace migration) → standalone, no dependencies +``` + +## PR Workflow + +When ready to submit, create a PR with `tea` CLI. **All flags are required** to avoid TTY prompts (see CLAUDE.md "Gitea access" section): + +```bash +tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(content): migrate line IDs to NPC-scoped namespace (D-035)" --description "body" --base main --head copy +``` diff --git a/docs/sprints/sprint-16/joint.md b/docs/sprints/sprint-16/joint.md new file mode 100644 index 000000000..cd3d7872b --- /dev/null +++ b/docs/sprints/sprint-16/joint.md @@ -0,0 +1,73 @@ +# Sprint 16: Converse — Joint Coordination + +**Goal:** Dialogue becomes a two-way exchange — players pick options and the server responds; NPC information-giving deepens through trust and trait-shaped delivery; the sprite pipeline gets its correct art-direction angle; and housekeeping fixes land across all three active teams. + +## Pre-Sprint + +No blocking decisions or schema work required before implementation starts. All relevant decisions are confirmed (D-019, D-028, D-035, D-075, D-077). Sprint 16 can begin immediately. + +| Item | Owner | Status | +|------|-------|--------| +| D-035 line ID scheme (NPC-scoped) | confirmed | done | +| D-075 layered confidence gate on trust | confirmed | done | +| D-019 camera angle amendment | confirmed | done | +| D-077 zone_id on VisibleTile | confirmed, implemented | done | + +## Ticket Overview + +| # | Title | Team | Blocked by | +|---|-------|------|------------| +| #538 | Arch: Move dialogue systems BridgePlugin → NpcPlugin | server | — | +| #539 | Server: implement DialogueResponse verb handler | server | #538 | +| #171 | Layer 3: Trust-gated gossip | server | — | +| #338 | Line variety tracker — prevent repeat dialogue | server | — | +| #543 | Optimize zone_id extraction O(N) → O(1) | client | — | +| #540 | Adjust sprite camera to D-019 angle | client | #541 | +| #542 | Migrate line IDs to NPC-scoped namespace | copy | — | +| #541 | 3D sprite render pipeline setup | visual | — | + +## Cross-team Dependencies + +**#541 (visual) → #540 (client):** Araminta must deliver at minimum a test entity sprite and a wall sprite at the -72.5° angle before Stig can validate the import path and z-sorting behavior. Early delivery of test sprites (even before the full pipeline doc is written) unblocks client work. Coordinate via the `visual` → `client` handoff — Araminta commits test sprites to `client/assets/sprites/` on the visual branch; Stig pulls and verifies fit in `entity_renderer.gd`. + +**#538 (server) → #539 (server):** Sequential within the server team. #538 refactors registration; #539 adds a new system in the correct location. Dudley should complete and merge #538 before starting #539. + +**#542 (copy) → server golden tests:** After #542 renames all line IDs, the server golden test fixtures in `server/tests/` may reference old line IDs (e.g., `the-terminal_d_039`). Run `make pre-pr` (which includes the content cross-reference validation and fixture staleness check) after #542 merges. If golden tests fail, the server team updates fixtures with `make golden-update`. + +## Sprint Completion Proof + +The sprint is complete when all of the following are observable: + +1. **DialogueResponse closes the loop:** Player opens dialogue with any NPC, selects response option 1 or 2 (non-confrontation), and the NPC delivers a contextually appropriate follow-up line. No silent no-op on option selection. Observable in-game or via the test client replay. + +2. **Trust-gated gossip unlocks progressively:** A player with `KnowsOf` confidence on a target NPC receives different (more forthcoming) dialogue lines than a player with `Suspects` confidence. Observable via the line previewer CLI or a gauntlet test room. The same NPC gives Surface-tier lines on first contact and Real-tier lines after investigation. + +3. **No line repeats within 1 game-hour:** The player can initiate dialogue with the same NPC 5+ times in quick succession and observe that no dialogue line_id repeats. Observable via the WRONG button capture or server logs. + +4. **Zone_id lookup is O(1):** `game_state.gd` `apply_snapshot()` no longer contains the linear tile scan for zone_id (lines 249-252 removed). `current_zone_id` populates correctly. Confirmed by code review + existing client tests passing. + +5. **Sprite camera angle:** At least one entity sprite and one wall sprite are rendered at -72.5° from horizontal and display correctly in the Godot client without z-sorting artifacts. Observable in the rendering scene. + +6. **Line ID migration complete:** `make pre-pr` passes with no content validation errors. No YAML file under `content/` contains line IDs with the old `{location_slug}_` prefix. Observable via CI. + +## Test Plan Alignment (D-030) + +Sprint 16 is in the Sprint 3-4 integration phase per D-030 test priorities. + +- **#539 (DialogueResponse handler):** New system requires an integration test: player sends `DialogueResponse { response_id }`, assert `DialogueResponseBuffer` is populated with a follow-up line. Use existing test harness pattern from `dialogue.rs` game_loop tests. +- **#171 (Trust-gated gossip):** Requires a unit test: initialize player KG with `KnowsDetails` on target, run `process_talk_interaction`, assert a `Secret`-tier line is selected. Mirror with `Suspects` confidence, assert only `Surface`-tier lines are selected. +- **#338 (Line variety tracker):** Requires a regression test: send `Talk` 10 times to same NPC within 600 ticks, assert no line_id appears twice. +- **#543 (O(1) zone lookup):** Code-change-only; existing client tests cover `apply_snapshot()` behavior. Add assertion that `current_zone_id` is non-empty when the player is on a zone-tagged tile. +- **#542 (line ID rename):** Covered by `make pre-pr` content cross-reference validation (CI #464). + +## PR Merge Order Recommendation + +To minimize merge conflicts and test failures: + +1. `copy` #542 — no code dependencies, merge first to unblock golden test validation +2. `server` #538 — plugin refactor, isolated change +3. `server` #171 and #338 — parallel, both isolated from #539 +4. `server` #539 — depends on #538 being merged +5. `visual` #541 — can merge any time, gates #540 +6. `client` #543 — standalone, merge any time +7. `client` #540 — merge after #541 lands diff --git a/docs/sprints/sprint-16/server.md b/docs/sprints/sprint-16/server.md new file mode 100644 index 000000000..5ce0cfe45 --- /dev/null +++ b/docs/sprints/sprint-16/server.md @@ -0,0 +1,100 @@ +# Sprint 16: Converse — Server Tasks + +**Goal:** Dialogue becomes a two-way exchange — players pick options and the server responds; NPC information-giving deepens through trust and trait-shaped delivery; the sprite pipeline gets its correct art-direction angle; and housekeeping fixes land across all three active teams. + +**Branch:** `server` +**Agents:** Dudley (simulation), Tyre (arch), Hoshe (QA) + +## Carry-over from Sprint 15 + +None. Sprint 15 was 100% complete (16/16 done). + +## New Tickets + +| # | Title | Blocked by | +|---|-------|------------| +| #538 | Arch: Move dialogue systems from BridgePlugin to NpcPlugin | — | +| #539 | Server: implement DialogueResponse verb handler | #538 | +| #171 | Layer 3: Trust-gated gossip | — | +| #338 | Line variety tracker — prevent repeat dialogue | — (unblocked: #305 done) | + +Use `db/connectors/ticket show ` for full details. + +## Key Decisions + +- `decisions/content.md` — D-028 (dialogue architecture, four relational layers), D-035 (tag taxonomy, trust/access tiers), D-075 (layered confidence gate on trust tier) +- `decisions/architecture.md` — D-041 (knowledge graph data model, KnowledgeConfidence hierarchy), D-010 (no baking player identity into game loop) + +## Notes + +### #538 — Arch: Move dialogue systems from BridgePlugin to NpcPlugin + +**What exists:** `server/src/bridge/mod.rs` (lines 181-187) registers `process_talk_interaction`, `process_walk_away`, and `process_confrontation_response` inside `BridgePlugin::build()`. These systems depend on NPC-layer resources (`TrustEventQueue`, `KnowledgeGraph`, `KnowledgeEventQueue`) but live in the bridge module, which is supposed to own only wire protocol concerns. + +**What to deliver:** Move the three `.add_systems()` calls from `BridgePlugin::build()` to `NpcPlugin::build()` in `server/src/npc/mod.rs`. The function implementations in `server/src/simulation/dialogue.rs` do not move — only their registration site changes. The scheduling constraints (`.after(process_player_input)`, `.before(compute_observer_snapshot)`) must be preserved exactly. `NpcPlugin` already has access to `TrustEventQueue` and other NPC resources (it `init_resource`s them), so no new resource registration is needed. + +**Gotcha:** The `game_loop` integration tests in `dialogue.rs` that manually set up `TrustEventQueue` were the signal that this was misplaced. After the move, those tests should no longer need the manual init. Verify no test breaks. + +**Integration point for #539:** This must merge before #539 starts, since #539 adds a new dialogue system that should register in `NpcPlugin` from the start. + +### #539 — Server: implement DialogueResponse verb handler + +**What exists:** `process_talk_interaction` in `server/src/simulation/dialogue.rs` handles the initial `Talk` verb. It selects a line via the 4-layer pipeline and writes to `DialogueResponseBuffer`. The client sends `DialogueResponse { response_id }` when the player picks an option, but no server system consumes it. `PlayerAction::DialogueResponse` is already defined in the bridge types. + +**What to deliver:** A new system `process_dialogue_response` in `server/src/simulation/dialogue.rs` that: +1. Reads `DialogueResponse` actions from the player input queue +2. Looks up the NPC's `DialogueProfile` (location, role) and active `KnowledgeGraph` state +3. Runs the same 4-layer filtering pipeline (`query_dialogue` on `LinePoolIndexResource`) to select a follow-up line based on the response_id context +4. Writes the result to `DialogueResponseBuffer` for snapshot inclusion +5. For conversations that have ended (no follow-up lines), clears `ActiveDialogue` + +**Key design constraints from D-028 + D-062:** Dialogue options the player hasn't unlocked are invisible — so `response_id` will only arrive for options that were legitimately sent. No validation needed beyond confirming the response_id maps to a known dialogue context. The server does not need to validate "was this a valid choice?" — if the client sent it, it was shown. + +**Integration with #171:** If trust-gated gossip (#171) is complete in the same sprint, this system should also route `Real`/`Secret` tier responses through the trust-gated pipeline. If #171 lands after, stub with `Surface`-only for now. + +**Register in NpcPlugin** (after #538 lands). + +### #171 — Layer 3: Trust-gated gossip + +**What exists:** The trust tier gate is already fully implemented. `relationship_to_trust()` in `server/src/simulation/dialogue.rs` (lines 199-216) takes `RelationshipState` and `KnowledgeConfidence` and returns `TrustTier` per D-075. The `LinePoolIndexResource` query pipeline in `content/mod.rs` accepts a `TrustTier` and filters lines accordingly. The content YAML files already carry `trust: real` and `trust: secret` tags on authored lines. + +**What to deliver:** Three disclosure tiers per NPC role (surface / real / secret). The engine is ready — the gap is that `process_talk_interaction` may not be passing the correct `KnowledgeConfidence` from the observer's `KnowledgeGraph` to `relationship_to_trust()`. Verify in `process_talk_interaction` that: +1. The observer's `KnowledgeGraph` is queried for the NPC's `StableId` +2. `confidence_of(&target_sid)` is passed as the second arg to `relationship_to_trust()` +3. The resulting `TrustTier` filters lines in `query_dialogue` + +If this path is already wired (check near line 345 in `dialogue.rs`), the ticket may be a content validation: confirm that authored lines with `trust: real` and `trust: secret` actually surface for a player with `KnowsOf+` / `KnowsDetails+` knowledge. Write a test that initializes a player KG with `KnowsDetails` on a target NPC, runs `process_talk_interaction`, and asserts a `Secret`-tier line was selected. + +**Progressive revelation:** The same NPC gives different answers on first meeting (Surface only) vs. after investigation (Real/Secret unlocked). This is not a UI change — it is already handled by D-062's invisible locks. The engine must simply populate `DialogueResponseBuffer` with the tier-appropriate line. + +### #338 — Line variety tracker — prevent repeat dialogue + +**What exists:** `DialogueCooldownTracker` is already implemented as a `Component` in `server/src/simulation/dialogue.rs` (lines 78-105). It tracks `line_id → tick_used` in a `BTreeMap` with a 600-tick (1 game-hour) cooldown. `record()`, `is_on_cooldown()`, and `prune()` methods exist. + +**What to deliver:** Wire the tracker into `process_talk_interaction`: +1. Query `DialogueCooldownTracker` on the player entity (it may already be queried — check the system signature near line 345) +2. After the 4-layer pipeline returns candidates, filter out lines where `tracker.is_on_cooldown(line_id, current_tick)` returns true +3. Call `tracker.record(selected_line_id, current_tick)` after selection +4. Call `tracker.prune(current_tick)` each time to prevent unbounded growth + +If the tracker is already being queried but not used to filter, this is a 10-line change. If it is not queried at all, add it to the system signature and wire it in. Write a test that sends `Talk` repeatedly to the same NPC and asserts no line_id appears twice within `LINE_COOLDOWN_TICKS`. + +## Dependency Chain + +``` +#538 (BridgePlugin → NpcPlugin refactor) + → #539 (DialogueResponse verb handler) + +#171 (Layer 3: Trust-gated gossip) → standalone, parallel track + [may inform #539 if both land same sprint] + +#338 (Line variety tracker wiring) → standalone, parallel track +``` + +## PR Workflow + +When ready to submit, create a PR with `tea` CLI. **All flags are required** to avoid TTY prompts (see CLAUDE.md "Gitea access" section): + +```bash +tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(simulation): sprint 16 dialogue server" --description "body" --base main --head server +``` diff --git a/docs/sprints/sprint-16/visual.md b/docs/sprints/sprint-16/visual.md new file mode 100644 index 000000000..20575dd22 --- /dev/null +++ b/docs/sprints/sprint-16/visual.md @@ -0,0 +1,69 @@ +# Sprint 16: Converse — Visual Tasks + +**Goal:** Dialogue becomes a two-way exchange — players pick options and the server responds; NPC information-giving deepens through trust and trait-shaped delivery; the sprite pipeline gets its correct art-direction angle; and housekeeping fixes land across all three active teams. + +**Branch:** `visual` +**Agents:** Araminta (art direction) + +## Carry-over from Sprint 15 + +None. Sprint 15 was 100% complete (16/16 done). + +## New Tickets + +| # | Title | Blocked by | +|---|-------|------------| +| #541 | 3D sprite render pipeline setup | — | + +Use `db/connectors/ticket show 541` for full details. + +## Key Decisions + +- `decisions/perception.md` — D-019 (camera angle amendment: -72.5° from horizontal = ~17.5° from vertical, "the angle"; Camera3D in the offline 3D pipeline produces sprites at this angle), D-043 (visual style: functional warmth, clean 2D with bold silhouettes), D-044 (visual hierarchy: entity > object > structure, resolution chain 1024→256→64), D-049 (z-level rendering stack, 8 layers) +- `decisions/architecture.md` — D-066 (dual-scale grid: 64×64px visual tiles on 1m grid; entity sprites: 24×32px footprint within 64×64px visual tile) + +## Notes + +### #541 — 3D sprite render pipeline setup + +**What to deliver:** Establish the offline 3D sprite render pipeline used to produce all entity, object, and wall sprites for the game. This pipeline is a Blender (or equivalent) render setup, not a runtime system. + +**Camera spec (D-019 amendment):** +- Camera3D positioned at **-72.5° from horizontal** (= 17.5° from vertical, midpoint of 15-20° range) +- Orthographic projection (not perspective) to match the Godot gameplay camera model +- This is "the angle" — all sprites for v0.1 are rendered at this angle + +**Output spec (D-043, D-044):** +- Source resolution: 1024×1024px (highest fidelity, for outline processing) +- Working resolution: 256×256px (outlines applied at 4-8px, dark blue-grey `#333340`) +- Runtime resolution: 64×64px (bilinear interpolation from 256×256) +- Entity sprites: 24×32px footprint within the 64×64px visual tile (D-044) +- Sprites are shape templates — no baked shadows, no baked lighting, no baked mood (lighting is applied at runtime by Godot's PointLight2D pipeline) + +**Lighting rig:** +- Three-point studio rig (key, fill, rim) to produce clean silhouettes without baked directional shadows +- Do NOT bake lighting direction into sprites — the runtime Godot lighting system (PointLight2D per fixture) provides all scene lighting +- Keep surface colors flat and readable; the 3D render should produce crisp outlines and readable silhouettes, not mood-lit final frames + +**Export process:** +- Document the render settings (resolution, camera angle, lighting rig) so any team member can re-render consistent sprites +- Export at least one entity test sprite (generic NPC silhouette) and one structural test sprite (wall segment) to validate the pipeline before full production +- Store render source files in the `visual` branch; export the runtime 64×64 sprites to `client/assets/sprites/` (or the established asset path) for the client team + +**Blocks #540 (client):** The client team cannot implement the camera angle adjustment (#540) until this pipeline produces correctly-angled sprites. Deliver at minimum a test entity sprite and one wall sprite so the client team can begin #540 integration work. + +**Key constraint from D-066:** Entity sprites render across a 2×2 sim tile footprint. The 24×32px entity footprint within a 64×64px visual tile must be respected — test sprites should fit this bounding box so the client's z-sorting logic (y-based sort) remains accurate. + +## Dependency Chain + +``` +#541 (3D render pipeline setup) → #540 (client: sprite camera angle) +``` + +## PR Workflow + +When ready to submit, create a PR with `tea` CLI. **All flags are required** to avoid TTY prompts (see CLAUDE.md "Gitea access" section): + +```bash +tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(assets): 3D sprite render pipeline setup (D-019 angle)" --description "body" --base main --head visual +```