diff --git a/client/assets/sprites/npc_generic_east_64.png.import b/client/assets/sprites/npc_generic_east_64.png.import new file mode 100644 index 000000000..0293c1485 --- /dev/null +++ b/client/assets/sprites/npc_generic_east_64.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://850hx6cd5kx7" +path="res://.godot/imported/npc_generic_east_64.png-bf22e76d70922112e99bf747d85a0a04.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/sprites/npc_generic_east_64.png" +dest_files=["res://.godot/imported/npc_generic_east_64.png-bf22e76d70922112e99bf747d85a0a04.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/client/assets/sprites/npc_generic_north_64.png.import b/client/assets/sprites/npc_generic_north_64.png.import new file mode 100644 index 000000000..6fcdb1f4f --- /dev/null +++ b/client/assets/sprites/npc_generic_north_64.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bx6s0yglmpt2l" +path="res://.godot/imported/npc_generic_north_64.png-296e233a60c8b9efed025a82a69614df.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/sprites/npc_generic_north_64.png" +dest_files=["res://.godot/imported/npc_generic_north_64.png-296e233a60c8b9efed025a82a69614df.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/client/assets/sprites/npc_generic_south_64.png.import b/client/assets/sprites/npc_generic_south_64.png.import new file mode 100644 index 000000000..173cd3aef --- /dev/null +++ b/client/assets/sprites/npc_generic_south_64.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dpsuq148mksls" +path="res://.godot/imported/npc_generic_south_64.png-c121e02d806f6dcc3ed440484827c258.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/sprites/npc_generic_south_64.png" +dest_files=["res://.godot/imported/npc_generic_south_64.png-c121e02d806f6dcc3ed440484827c258.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/client/assets/sprites/npc_generic_west_64.png.import b/client/assets/sprites/npc_generic_west_64.png.import new file mode 100644 index 000000000..40e36f1d6 --- /dev/null +++ b/client/assets/sprites/npc_generic_west_64.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dgt84hobbdumx" +path="res://.godot/imported/npc_generic_west_64.png-9ee237384537f7357d37802b6cfae559.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/sprites/npc_generic_west_64.png" +dest_files=["res://.godot/imported/npc_generic_west_64.png-9ee237384537f7357d37802b6cfae559.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/client/assets/sprites/wall_structural_east_64.png.import b/client/assets/sprites/wall_structural_east_64.png.import new file mode 100644 index 000000000..be5d4b837 --- /dev/null +++ b/client/assets/sprites/wall_structural_east_64.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bcmer1bsugk8b" +path="res://.godot/imported/wall_structural_east_64.png-f868b267dcc1824e2b0fe213e49b4996.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/sprites/wall_structural_east_64.png" +dest_files=["res://.godot/imported/wall_structural_east_64.png-f868b267dcc1824e2b0fe213e49b4996.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/client/assets/sprites/wall_structural_north_64.png.import b/client/assets/sprites/wall_structural_north_64.png.import new file mode 100644 index 000000000..73fb8d1d6 --- /dev/null +++ b/client/assets/sprites/wall_structural_north_64.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dd5j0q6cu674m" +path="res://.godot/imported/wall_structural_north_64.png-f7eceb6d561e7e07b3bac2e28c659628.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/sprites/wall_structural_north_64.png" +dest_files=["res://.godot/imported/wall_structural_north_64.png-f7eceb6d561e7e07b3bac2e28c659628.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/client/assets/sprites/wall_structural_south_64.png.import b/client/assets/sprites/wall_structural_south_64.png.import new file mode 100644 index 000000000..c203de307 --- /dev/null +++ b/client/assets/sprites/wall_structural_south_64.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cctlhvaolom3x" +path="res://.godot/imported/wall_structural_south_64.png-c34925f9a5a3f6b5968e73e51953367e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/sprites/wall_structural_south_64.png" +dest_files=["res://.godot/imported/wall_structural_south_64.png-c34925f9a5a3f6b5968e73e51953367e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/client/assets/sprites/wall_structural_west_64.png.import b/client/assets/sprites/wall_structural_west_64.png.import new file mode 100644 index 000000000..6236399ed --- /dev/null +++ b/client/assets/sprites/wall_structural_west_64.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ughjt5s8la2p" +path="res://.godot/imported/wall_structural_west_64.png-7c39e0c692d32348190875c4ad99b227.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/sprites/wall_structural_west_64.png" +dest_files=["res://.godot/imported/wall_structural_west_64.png-7c39e0c692d32348190875c4ad99b227.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/client/scripts/autoloads/session_manager.gd.uid b/client/scripts/autoloads/session_manager.gd.uid new file mode 100644 index 000000000..65625d9ba --- /dev/null +++ b/client/scripts/autoloads/session_manager.gd.uid @@ -0,0 +1 @@ +uid://b357ok64jc8vp diff --git a/client/scripts/protocol/test_harness.gd.uid b/client/scripts/protocol/test_harness.gd.uid new file mode 100644 index 000000000..e9589da35 --- /dev/null +++ b/client/scripts/protocol/test_harness.gd.uid @@ -0,0 +1 @@ +uid://2p33ypi2y2kv diff --git a/client/scripts/snapshot_event_router.gd.uid b/client/scripts/snapshot_event_router.gd.uid new file mode 100644 index 000000000..5cf18861a --- /dev/null +++ b/client/scripts/snapshot_event_router.gd.uid @@ -0,0 +1 @@ +uid://drppri4b46v80 diff --git a/client/scripts/util/yaml_parser.gd.uid b/client/scripts/util/yaml_parser.gd.uid new file mode 100644 index 000000000..bc99d1122 --- /dev/null +++ b/client/scripts/util/yaml_parser.gd.uid @@ -0,0 +1 @@ +uid://cix55xks85vl8 diff --git a/client/tests/run_gdunit4.gd.uid b/client/tests/run_gdunit4.gd.uid new file mode 100644 index 000000000..2afd0be22 --- /dev/null +++ b/client/tests/run_gdunit4.gd.uid @@ -0,0 +1 @@ +uid://ypniv2crdp8c diff --git a/client/tests/test_debug_overlay_sprint19.gd.uid b/client/tests/test_debug_overlay_sprint19.gd.uid new file mode 100644 index 000000000..45339eedc --- /dev/null +++ b/client/tests/test_debug_overlay_sprint19.gd.uid @@ -0,0 +1 @@ +uid://bdsgybncfyu52 diff --git a/client/tests/test_dialogue_sprint18.gd.uid b/client/tests/test_dialogue_sprint18.gd.uid new file mode 100644 index 000000000..86b65385f --- /dev/null +++ b/client/tests/test_dialogue_sprint18.gd.uid @@ -0,0 +1 @@ +uid://b46mtkralmq14 diff --git a/client/tests/test_dialogue_sprint20.gd.uid b/client/tests/test_dialogue_sprint20.gd.uid new file mode 100644 index 000000000..7b280ebe1 --- /dev/null +++ b/client/tests/test_dialogue_sprint20.gd.uid @@ -0,0 +1 @@ +uid://bh8tiio43gveq diff --git a/client/tests/test_etalk_overlay_sprint17.gd.uid b/client/tests/test_etalk_overlay_sprint17.gd.uid new file mode 100644 index 000000000..aba387a11 --- /dev/null +++ b/client/tests/test_etalk_overlay_sprint17.gd.uid @@ -0,0 +1 @@ +uid://hdoafqdfr6sq diff --git a/client/tests/test_examine_display_sprint18.gd.uid b/client/tests/test_examine_display_sprint18.gd.uid new file mode 100644 index 000000000..77fb47651 --- /dev/null +++ b/client/tests/test_examine_display_sprint18.gd.uid @@ -0,0 +1 @@ +uid://c8e2watwphu41 diff --git a/client/tests/test_game_state.gd.uid b/client/tests/test_game_state.gd.uid new file mode 100644 index 000000000..4caf26f36 --- /dev/null +++ b/client/tests/test_game_state.gd.uid @@ -0,0 +1 @@ +uid://dvt7ytxff0id diff --git a/client/tests/test_game_state_sprint20.gd.uid b/client/tests/test_game_state_sprint20.gd.uid new file mode 100644 index 000000000..d8379ec44 --- /dev/null +++ b/client/tests/test_game_state_sprint20.gd.uid @@ -0,0 +1 @@ +uid://c6lle6hfc1cf6 diff --git a/client/tests/test_ipc_fixtures.gd.uid b/client/tests/test_ipc_fixtures.gd.uid new file mode 100644 index 000000000..e4a0e7adf --- /dev/null +++ b/client/tests/test_ipc_fixtures.gd.uid @@ -0,0 +1 @@ +uid://cp6n6uo6pfnee diff --git a/client/tests/test_journal_sprint18.gd.uid b/client/tests/test_journal_sprint18.gd.uid new file mode 100644 index 000000000..f4bb0f116 --- /dev/null +++ b/client/tests/test_journal_sprint18.gd.uid @@ -0,0 +1 @@ +uid://cao5jf5h36img diff --git a/client/tests/test_minimap_sprint18.gd.uid b/client/tests/test_minimap_sprint18.gd.uid new file mode 100644 index 000000000..3e8b699a8 --- /dev/null +++ b/client/tests/test_minimap_sprint18.gd.uid @@ -0,0 +1 @@ +uid://dm46ip672i3jc diff --git a/client/tests/test_save_load_flow_sprint21.gd.uid b/client/tests/test_save_load_flow_sprint21.gd.uid new file mode 100644 index 000000000..45cd32037 --- /dev/null +++ b/client/tests/test_save_load_flow_sprint21.gd.uid @@ -0,0 +1 @@ +uid://bd42lh2licl65 diff --git a/client/tests/test_session_manager_sprint19.gd.uid b/client/tests/test_session_manager_sprint19.gd.uid new file mode 100644 index 000000000..18eb168e1 --- /dev/null +++ b/client/tests/test_session_manager_sprint19.gd.uid @@ -0,0 +1 @@ +uid://c7lnnr2apbyqw diff --git a/client/tests/test_snapshot_event_router.gd.uid b/client/tests/test_snapshot_event_router.gd.uid new file mode 100644 index 000000000..52ad471d1 --- /dev/null +++ b/client/tests/test_snapshot_event_router.gd.uid @@ -0,0 +1 @@ +uid://dmd5ka0xckcg5 diff --git a/client/tests/test_snapshot_zone_id.gd.uid b/client/tests/test_snapshot_zone_id.gd.uid new file mode 100644 index 000000000..4b150a340 --- /dev/null +++ b/client/tests/test_snapshot_zone_id.gd.uid @@ -0,0 +1 @@ +uid://c84lxgbhgf3bw diff --git a/client/tests/test_sprite_integration.gd.uid b/client/tests/test_sprite_integration.gd.uid new file mode 100644 index 000000000..d9f5f7931 --- /dev/null +++ b/client/tests/test_sprite_integration.gd.uid @@ -0,0 +1 @@ +uid://b8snipm64g2b2 diff --git a/client/tests/test_time_display_sprint17.gd.uid b/client/tests/test_time_display_sprint17.gd.uid new file mode 100644 index 000000000..24ed2539a --- /dev/null +++ b/client/tests/test_time_display_sprint17.gd.uid @@ -0,0 +1 @@ +uid://c3rq8w40d20yw diff --git a/client/tests/test_yaml_parser.gd.uid b/client/tests/test_yaml_parser.gd.uid new file mode 100644 index 000000000..a7c73e961 --- /dev/null +++ b/client/tests/test_yaml_parser.gd.uid @@ -0,0 +1 @@ +uid://cnkfuyqbrcr3v diff --git a/client/tests/util/scene_helper.gd.uid b/client/tests/util/scene_helper.gd.uid new file mode 100644 index 000000000..91e0a4362 --- /dev/null +++ b/client/tests/util/scene_helper.gd.uid @@ -0,0 +1 @@ +uid://dguvww6h7nxyr diff --git a/client/ui/examine_display.gd.uid b/client/ui/examine_display.gd.uid new file mode 100644 index 000000000..7ace632e0 --- /dev/null +++ b/client/ui/examine_display.gd.uid @@ -0,0 +1 @@ +uid://wqekxebyn6to diff --git a/client/ui/journal_panel.gd.uid b/client/ui/journal_panel.gd.uid new file mode 100644 index 000000000..e223446a6 --- /dev/null +++ b/client/ui/journal_panel.gd.uid @@ -0,0 +1 @@ +uid://2hd0mfw5x245 diff --git a/client/ui/main_menu.gd.uid b/client/ui/main_menu.gd.uid new file mode 100644 index 000000000..e8143c986 --- /dev/null +++ b/client/ui/main_menu.gd.uid @@ -0,0 +1 @@ +uid://8jhxbtbelw0y diff --git a/client/ui/time_display.gd.uid b/client/ui/time_display.gd.uid new file mode 100644 index 000000000..8b9339bd0 --- /dev/null +++ b/client/ui/time_display.gd.uid @@ -0,0 +1 @@ +uid://dw1caicc2s7qf diff --git a/docs/sprints/sprint-22/ci.md b/docs/sprints/sprint-22/ci.md new file mode 100644 index 000000000..31e76a296 --- /dev/null +++ b/docs/sprints/sprint-22/ci.md @@ -0,0 +1,44 @@ +# Sprint 22: Wire — CI Tasks + +**Goal:** Fix the fog regression, wire the authored Sova content into a production population and triangle set, and activate the contamination pacing layer that gives the vertical slice its 30-minute shape. + +**Branch:** `ci` +**Agents:** Justine (build/deploy) + +## New Tickets + +| # | Title | Blocked by | +|---|-------|------------| +| #568 | Remove db/connectors symlink (Sprint 22) | — | + +Use `tooling/db/ticket show ` for full details. + +## Key Decisions + +No decision files are directly relevant. This is housekeeping: the symlink was marked "remove after Sprint 22" when it was added in PR #274 (#274 ticket: #274). + +## Notes + +**#568 — Remove db/connectors symlink** +- The `db/connectors → tooling/db/` backwards-compat symlink was added in #274 to avoid breaking references during the connector migration. The PR note says "remove after Sprint 22." +- **Before removing the symlink:** + 1. Grep all worktree branches for `db/connectors/` references and update them. The main branch is the reference — check `CLAUDE.md`, any skill files, and briefing templates that may reference the old path. + 2. Check `whatsinagame/` templates (kit templates) that use `db/connectors/` as a canonical path — these need their own path config updated. + 3. Run `grep -r "db/connectors" .` from the repo root before removing. +- **Removal:** `git rm db/connectors` (if tracked) or `rm db/connectors` (if untracked symlink). The target directory `tooling/db/` stays — only the symlink goes. +- **After removal:** Run `make ci` to confirm nothing is broken by the removal. The `make` targets all use `tooling/db/` paths directly, so CI should be unaffected. +- **Acceptance:** `make ci` passes. `ls db/` no longer shows a `connectors` entry. No `db/connectors/` references remain in the repo (verified by grep). + +## Dependency Chain + +``` +#568 (symlink removal) — standalone +``` + +## PR Workflow + +When ready to submit, create a PR with `tea` CLI: + +```bash +tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "chore(ci): remove db/connectors backwards-compat symlink" --description "body" --base main --head ci +``` diff --git a/docs/sprints/sprint-22/client.md b/docs/sprints/sprint-22/client.md new file mode 100644 index 000000000..1efc2cd7f --- /dev/null +++ b/docs/sprints/sprint-22/client.md @@ -0,0 +1,78 @@ +# Sprint 22: Wire — Client Tasks + +**Goal:** Fix the fog regression, wire the authored Sova content into a production population and triangle set, and activate the contamination pacing layer that gives the vertical slice its 30-minute shape. + +**Branch:** `client` +**Agents:** Stig (UI dev), Tyre (arch), Hoshe (QA) + +## New Tickets + +| # | Title | Blocked by | +|---|-------|------------| +| #569 | Fix fog system: blocky edges and zero visibility in explored areas | — | +| #175 | Entanglement ratio configuration | — | +| #178 | Seed-based variation | #175 | + +Use `tooling/db/ticket show ` for full details on any ticket. + +## Key Decisions + +- `decisions/perception.md` — D-011 (fog of perception, non-negotiable), D-015 (vision cone, 120° forward arc, physiological basis), D-059 (fog shader spec: 3 visual states, smooth gradients, explored-tile overlay), D-066 (dual-scale grid: gradient edge = 6-8 sim tiles) +- `decisions/content.md` — D-029 (population entanglement ratio 30/50/20, variable per seed) +- `decisions/architecture.md` — D-010 (deterministic simulation, seed-based repeatability), D-020 (client is a dumb renderer — reads ObserverSnapshot, sends PlayerInput) + +## Notes + +**#569 — Fix fog system: blocky edges and zero visibility in explored areas** +- This ticket carries over uncommitted changes already on `main`. The server-side changes (vision_cone.rs simplification to Forward-only, test updates) passed CI and are green. The client-side changes (fog.gdshader, fog_state.gd) are the failing part — two persistent visual regressions: + 1. **Blocky stair-stepped edges** at the vision cone boundary — tile-sized steps instead of a smooth gradient. + 2. **Zero visibility in explored areas** — previously seen tiles appear near-black instead of showing art through a light overlay. +- **Spec (D-059 + D-015 + D-066):** Three visual states: + - **Forward cone (clear):** Nearly transparent, 6-8 sim tile soft gradient at the cone boundary. No tile-stepping. + - **Explored (light fog):** ~25-30% opacity overlay. ALL underlying art, sprites, furniture, NPCs visible. Subtle animated Perlin noise (8-10s cycle). Exploration memory is permanent — once seen, always shown in this state. + - **Unexplored:** Solid near-black `#12141a`. No art, no information. +- **Files to work in:** + - `client/shaders/fog.gdshader` — the Gaussian blur kernel (7x7, sigma 2.0) is already there. The issue is likely in the `smoothstep` clarity ramp or the `exp_fade` blend. The `fog_alpha` at 0.28 may be too low for explored tiles when `vis = 0`. + - `client/scripts/autoloads/fog_state.gd` — grow-only bounds and exploration preservation are in place. Check that `EXP_EXPLORED = 128` is being written correctly for tiles that leave the forward cone. +- **Diagnostic approach:** Add a temporary debug mode that renders the `exploration_tex` raw (bypassing fog) to verify the texture is being populated correctly before debugging the shader blend. If explored tiles are showing `EXP_UNEXPLORED = 0` when they should be `EXP_EXPLORED = 128`, the bug is in `fog_state.gd`. If they're correct in the texture but invisible in the render, the bug is in the shader. +- **Smooth gradient:** The 7x7 Gaussian is in `sample_visibility()`. The `smoothstep(0.0, 0.85, vis)` ramp controls the gradient width. Try `smoothstep(0.1, 0.9, vis)` for a wider soft zone. The D-066 spec says 6-8 sim tiles — at tile_size pixels per tile, the blur kernel needs to spread across that many tiles in UV space. +- **Acceptance (all must pass):** + - Forward cone edge has a smooth 6-8 tile gradient — no visible tile-stepping at any camera zoom. + - Explored tiles outside the cone show underlying art at approximately 25-30% fog overlay. + - Explored tiles never revert to unexplored black when the player turns or moves. + - Unexplored tiles are solid `#12141a`. + - Transitions between all three states are smooth. + - Server tests remain green (`cargo test -p server`). + - Visual QA sign-off from Araminta before PR merge (she owns #563 which depends on this landing first). + +**#175 — Entanglement ratio configuration** +- Server-side resource. The 30/50/20 flat/mundane/intrigue split (D-029) needs to be a configurable resource that `#176` (NPC pool generation on the server team) can read. +- This ticket is assigned to the client team because the configuration surface is a client-facing concern — the seed screen / character selection will eventually expose this to the player, and the config needs to be representable in the ObserverSnapshot or startup protocol. +- For v0.1: implement `EntanglementConfig` as a `Resource` in the server (coordinate with Dudley on the struct definition — server team's #176 will consume it). The client side of this ticket is: add a `world_seed: u64` field to the session startup message (if not already present in the IPC handshake), so the server can seed `SimRng` deterministically from it. +- Check `server/src/bridge/types.rs` for the existing startup/handshake message structure. The seed should flow: character selection screen (client) → startup IPC message → server reads seed → `SimRng::from_seed(seed)` → `EntanglementConfig` sampled from seeded RNG. +- Coordinate with server team: they own the `EntanglementConfig` struct and its consumption in #176. Client team owns the seed-carrying protocol field and any UI stub. +- Acceptance: the startup IPC message carries a `world_seed` field; server logs confirm it is received and used to seed `SimRng`. + +**#178 — Seed-based variation** +- Depends on #175. Per D-029: entanglement rate varies per seed to prevent player metagaming calibration across playthroughs. +- The variation is seeded in `EntanglementConfig` at startup: `flat_ratio`, `mundane_ratio`, `intrigue_ratio` are not hardcoded 30/50/20 but sampled from the seeded RNG within a range (e.g., flat: 25-35%, mundane: 45-55%, intrigue: 15-25%). The exact ranges should keep the ratios summing to 100% and maintain the intent of D-029 (majority unentangled, minority intrigue). +- This ticket: implement the seeded sampling of `EntanglementConfig` values from `SimRng`. Wire it so two game starts with the same `world_seed` produce identical `EntanglementConfig`, and different seeds produce observably different ratios. +- Acceptance: a test asserts that `EntanglementConfig::from_rng(seed_A) != EntanglementConfig::from_rng(seed_B)` for at least 90% of random seed pairs, AND `EntanglementConfig::from_rng(seed_A) == EntanglementConfig::from_rng(seed_A)` (deterministic). + +## Dependency Chain + +``` +#569 (fog fix) — standalone, start immediately + [Araminta's #563 blocks on this — do not merge #563 until #569 is in review] + +#175 (entanglement ratio config) — standalone, coordinate with server team on struct definition + → #178 (seed-based variation) +``` + +## PR Workflow + +When ready to submit, create a PR with `tea` CLI: + +```bash +tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "fix(client): Sprint 22 — fog rendering and seed config" --description "body" --base main --head client +``` diff --git a/docs/sprints/sprint-22/joint.md b/docs/sprints/sprint-22/joint.md new file mode 100644 index 000000000..4f9ae36a2 --- /dev/null +++ b/docs/sprints/sprint-22/joint.md @@ -0,0 +1,83 @@ +# Sprint 22: Wire — Joint / Integration + +**Goal:** Fix the fog regression, wire the authored Sova content into a production population and triangle set, and activate the contamination pacing layer that gives the vertical slice its 30-minute shape. + +**Agents:** All implementation agents (Dudley, Tyre, Hoshe, Stig, Araminta, Justine) + +--- + +## Pre-Sprint Actions + +These must happen at sprint start, before implementation tickets begin: + +| Action | Owner | Blocks | +|--------|-------|--------| +| Resolve Q-017 (triangle pressure threshold) | Gestalt + Paula (planning) | #254 | +| Confirm struct name for `EntanglementConfig` | Dudley + Stig sync | #175, #176 | +| Confirm `world_seed` field in startup IPC message | Tyre | #175, #178 | + +If Q-017 is not resolved within the first two days of the sprint, implement #254 with a `CONTAMINATION_DELAY_TICKS = 1800` timer fallback and a `CONFRONTATION_THRESHOLD = 75` constant placeholder. Do not block the sprint on the discussion. + +--- + +## Cross-Team Dependencies + +| Dependency | Direction | Notes | +|------------|-----------|-------| +| `EntanglementConfig` struct definition | Server (#176) → Client (#175) | Server defines, client adds seed-carrying protocol field | +| `world_seed` in startup IPC message | Client (#175) → Server (#176) | Client sends seed; server uses it to seed `SimRng` and sample `EntanglementConfig` | +| Fog architecture fixed | Client (#569) → Visual (#563) | Araminta must not touch `fog.gdshader` until Stig's #569 is in review | + +--- + +## Sprint Completion Proof + +The sprint is done when all of the following are observable: + +1. **Fog renders correctly.** Boot the client, walk around the Gauntlet. The forward cone has a smooth gradient edge (no tile-stepping). Tiles left behind the player show underlying art at ~25-30% fog overlay. Never-visited tiles are solid near-black. Araminta's zone temperature tints are visible in previously-explored areas. + +2. **Production world boots.** Start the server without the `gauntlet` feature (or with a `--production` flag). The server logs show 17+ NPC entities spawned from `content/campaigns/main/`. No panic. No assertion failures. + +3. **Triangles exist.** After production boot, a debug query (or test) confirms 5 `TriangleState` entities: 3 flagged as active forks, 2 as passive tensions. All have valid NPC role assignments. + +4. **Contamination fires.** A test running 1801+ ticks asserts `ContaminationActive` is set and at least one `TriangleState::pressure > 0`. + +5. **Seed determinism holds.** Two server boots with the same `world_seed` produce identical `EntanglementConfig` values. Different seeds produce different values. + +6. **CI green.** `make ci` passes on all branches. No regressions. + +--- + +## Test Plan Alignment (D-030) + +Sprint 22 is in D-030 Phase 2 (sprint 3-4 scope: integration tests + information boundary negative tests). The new tests in this sprint: + +- **#176:** Integration test — production boot spawns 17+ NPCs. (Phase 2 integration test) +- **#188:** Assertion test — 5 triangle entities with correct classifications post-boot. (Phase 2 integration test) +- **#254:** Timer test — 1801-tick simulation asserts `ContaminationActive`. (Phase 2 integration test) +- **#509:** Fuzzy test — 50-seed map invariant sweep. (Phase 2, borderline Phase 3) +- **#178:** Determinism test — same seed = same `EntanglementConfig`. (Phase 1 determinism, retroactive) + +Hoshe owns acceptance test definition for #176, #188, #254. Coordinate with Dudley on world setup for integration tests — reuse the `AppBuilder` pattern from existing integration tests in `server/tests/`. + +--- + +## Open Questions + +| ID | Question | Blocks | Action | +|----|----------|--------|--------| +| Q-017 | Triangle pressure threshold | #254 | Resolve at sprint start. Fallback: timer + constant if unresolved in 2 days. | +| Q-020 | Multi-entity collision resolution | none this sprint | Tracked, not blocking. | + +--- + +## PR Merge Order + +To avoid conflicts on shared files (`fog.gdshader`, `bridge/types.rs`): + +1. Server PRs (#565, #567, #509, #512) — merge first, no client/visual conflicts. +2. Server PR (#176 → #188 → #254 chain) — merge when chain is complete. +3. Client PR (#569 fog fix) — merge after server PRs are in. +4. Client PR (#175 + #178 seed config) — can merge alongside #569. +5. Visual PR (#563 fog tuning) — merge last, after client #569 is in main. +6. CI PR (#568 symlink removal) — merge anytime, no conflicts. diff --git a/docs/sprints/sprint-22/planning.md b/docs/sprints/sprint-22/planning.md new file mode 100644 index 000000000..236019a3f --- /dev/null +++ b/docs/sprints/sprint-22/planning.md @@ -0,0 +1,51 @@ +# Sprint 22: Wire — Planning Tasks + +**Goal:** Fix the fog regression, wire the authored Sova content into a production population and triangle set, and activate the contamination pacing layer that gives the vertical slice its 30-minute shape. + +**Branch:** `planning` (documentation only — no code branch) +**Agents:** Gestalt (systems), Paula (narrative), SI (project manager) + +## New Tickets + +| # | Title | Blocked by | +|---|-------|------------| +| #566 | Document MobileChunk Idle state as stationary installation primitive | — | +| #162 | Storyteller module activation — scope and assign | — | + +Use `tooling/db/ticket show ` for full details. + +## Notes + +**#566 — Document MobileChunk Idle state as stationary installation primitive** +- Decision D-111 (pending formal filing): the MobileChunk `Idle` movement state is the canonical primitive for player-owned stationary installations — space stations, orbital platforms, parked vessels used as bases. No code change. +- Deliverable: add a note to `decisions/architecture.md` under the D-108 (MobileChunk Specification) entry. Text: "The `Idle` movement state is the canonical primitive for player-owned stationary installations (space stations, orbital platforms, parked vessels as permanent bases). A MobileChunk in `Idle` with no `scheduled_departure` is architecturally equivalent to a static chunk from the simulation's perspective — it participates in the same tile/zone system. This design prevents future over-engineering of a separate 'player installation' system." +- Also update any relevant briefing files if MobileChunk is referenced in a way that omits this distinction. +- Acceptance: `decisions/architecture.md` updated. No compiler output needed. + +**#162 — Storyteller module activation — scope and assign for Sprint 23** +- This ticket is currently unassigned with an underdeveloped description. Sprint 22 planning task: scope it for Sprint 23 implementation. +- The StorytellerPlugin in `server/src/storyteller/mod.rs` will have a contamination stub after #254 lands (Sprint 22). Sprint 23 needs to extend it into the full proximity/engagement-based activation model from D-023. +- D-023: "The storyteller activates Tier 1 modules based on player proximity and engagement, not timers." This is the post-contamination layer: after contamination fires, the storyteller decides which triangle escalates, when, and how — based on what the player has engaged with. +- Sprint 22 planning deliverable: update ticket #162's description with the concrete Sprint 23 scope: + 1. `StorytellerPlugin` reads `ContaminationActive` (from #254) to gate activation. + 2. Proximity query: identify Active-tier NPCs within the player's last-N-tick path. + 3. Engagement scoring: weight NPCs by conversation count, observation time, and monologue triggers. + 4. Module selection: among active `TriangleState` forks, activate the fork containing the highest-engagement NPC. + 5. Activation event: emit `TriangleActivated { triangle_id }` for downstream systems (tells, NPC behavior shifts, monologue hooks). +- Assign #162 to the server team for Sprint 23. +- Acceptance: ticket #162 description updated, team assigned to `server`, priority confirmed as `high`. + +## Dependency Chain + +``` +#566 (MobileChunk Idle doc) — standalone documentation +#162 (storyteller scope) — standalone planning, produces Sprint 23 server ticket +``` + +## PR Workflow + +Planning outputs land on main directly (decisions/*.md updates) or via a planning branch PR: + +```bash +tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "docs(decisions): Sprint 22 planning — D-111 MobileChunk Idle doc, #162 scope" --description "body" --base main --head planning +``` diff --git a/docs/sprints/sprint-22/server.md b/docs/sprints/sprint-22/server.md new file mode 100644 index 000000000..b7a6deb5b --- /dev/null +++ b/docs/sprints/sprint-22/server.md @@ -0,0 +1,106 @@ +# Sprint 22: Wire — Server Tasks + +**Goal:** Fix the fog regression, wire the authored Sova content into a production population and triangle set, and activate the contamination pacing layer that gives the vertical slice its 30-minute shape. + +**Branch:** `server` +**Agents:** Dudley (simulation dev), Tyre (arch), Hoshe (QA) + +## New Tickets + +| # | Title | Blocked by | +|---|-------|------------| +| #565 | Fix signed z-level addressing: base_z u8 → i8 | — | +| #567 | Add modifications: Vec stub on chunk and MobileChunk | — | +| #176 | NPC pool generation | — | +| #188 | Triangle instantiation from authored content | #176 | +| #254 | Contamination activation mechanic | #188, Q-017 | +| #509 | Fuzzy tests for dynamic/procedural maps | — | +| #512 | Zone Gate room implementation | — | + +Use `tooling/db/ticket show ` for full details on any ticket. + +## Key Decisions + +- `decisions/architecture.md` — D-010 (deterministic simulation, BTreeMap over HashMap), D-026 (simulation tiers), D-041 (knowledge graph data model) +- `decisions/content.md` — D-023 (three-tier content model), D-024 (10-axis NPC model), D-025 (social site as atomic template unit), D-028 (dialogue tagged pools), D-029 (population entanglement ratio 30/50/20), D-034 (THE FRIEND pattern), D-036 (Sova Transit District setting) +- `decisions/scope.md` — D-027 (vertical slice success criteria), D-087 (3 active forks, 2 passive tensions), D-089 (self-contained triangle forks, no cross-triangle cascade) + +## Open Questions to Resolve Early + +- **Q-017: Triangle pressure threshold** — What events increase pressure? What numeric threshold triggers `confrontation` situation? Assigned to Gestalt and Paula. **Resolve before #254 starts** — the contamination mechanic needs a pressure model. If Q-017 is unresolved by the time #188 lands, implement #254 with a timer-based fallback (tick count, e.g., 1800 ticks = 30 game-minutes) and leave the pressure threshold as a named constant for Q-017 to fill in. + +## Notes + +**#565 — Fix signed z-level addressing: base_z u8 → i8** +- Decision D-110 (pending formal filing): change `base_z` from `u8` to `i8` in `MultiBlockReservation`, `FloorZone.z_level`, and all structs that reference a z-level *position* (not a count). `z_levels` counts remain `u8`. +- Mechanically a cascading type change — no logic changes, only type propagation through ~20 structs. +- Grep starting points: `server/src/simulation/spatial.rs`, `server/src/simulation/zone.rs`, and any struct with a field named `z_level` or `base_z`. +- Add a doc comment on `DistrictSkeleton` (if present) clarifying the distinction: `base_z: i8` (lowest floor, can be negative for sub-basement) vs `z_levels: u8` (count, always positive). +- Acceptance: `cargo test -p server` green, `clippy` clean. + +**#567 — Add modifications: Vec stub on chunk and MobileChunk** +- Decision D-111/D-112 (pending formal filing): DLC scope stub. Entry point for future player construction system. +- Add `modifications: Vec` (initially an empty `Vec`) to both static chunk entities and `MobileChunk` entities. +- Define `Modification` enum/struct in a new `server/src/simulation/modification.rs` with at minimum: `position: TilePosition`, `modification_type: ModificationType` (enum stub), `placed_at_tick: u64`. +- Wire into `SaveStateV1` (`server/src/simulation/save_state.rs`) so player-modified spaces round-trip through save/load. The field can be `Vec` with `#[serde(default)]` — empty for now, but the save slot exists. +- No construction logic ships. This is a data model stub only. +- Acceptance: a round-trip save/load test asserts the `modifications` field serializes and deserializes without data loss (even when empty). + +**#176 — NPC pool generation** +- This is the key production startup ticket. The Gauntlet uses hand-crafted `setup_gauntlet()`. The production path needs an equivalent that loads from `content/` and spawns the Sova NPC pool. +- The authored content already exists: 23 NPC YAML files at `content/campaigns/main/systems/krenn/stations/sova/districts/transit/npcs/`. The content loader (`server/src/content/loader.rs`) already loads `DistrictContent`. The spawn module (`server/src/content/spawn.rs`) already has `spawn_district_npcs()` and `resolve_cross_references()`. +- The gap: there is no production startup path that calls these. The server boots into Gauntlet (`setup_gauntlet`) or does nothing (without the `gauntlet` feature). Add a production startup system that: (1) loads `content/campaigns/main/campaign.yaml`, (2) resolves the Sova district, (3) calls `spawn_district_npcs()` + `resolve_cross_references()`, (4) applies the 30/50/20 entanglement ratio from D-029 to mark NPCs as flat/mundane/intrigue-entangled via a new `EntanglementTag` component. +- Entanglement ratio: seed the assignment via `SimRng` (D-010 determinism). The 17 authored Sova NPCs are pre-assigned by content (YAML field `triangle_membership`). The entanglement ratio applies to any procedurally generated padding NPCs. +- Acceptance: integration test (non-gauntlet boot) asserts 17+ NPCs spawn with correct `StableId` assignments and at least one `EntanglementTag::Intrigue` entity. + +**#188 — Triangle instantiation from authored content** +- Depends on #176. The 5 triangle YAML files exist at `content/campaigns/main/systems/krenn/stations/sova/districts/transit/triangles/` (hub-power, bar-tensions, informant-question, worried-knowledge, worried-partner). +- The instantiation engine (`server/src/content/instantiation.rs`) already handles `FullTemplateDef` → `TriangleState`. This ticket wires the *authored Sova triangles* (loaded from YAML) into that engine, rather than template-generated triangles. +- Per D-087: 3 active forks (T1, T2, T4 = hub-power, bar-tensions, informant-question) and 2 passive tensions (T3, T5 = worried-knowledge, worried-partner). The `TriangleState` component must reflect this classification. +- Per D-089: self-contained forks. No cross-triangle cascade. Each `TriangleState` escalates independently. +- Cross-template triangle (D-024): bar-tensions links NPCs across the logistics-hub and bar templates. Ensure `TemplateReferenceMap` entries are populated for cross-template role assignments. +- Acceptance: after production startup, `query()` returns 5 triangle entities: 3 tagged `Active` (forks), 2 tagged `Passive` (tensions). All 5 have valid role assignments to existing NPC entities. + +**#254 — Contamination activation mechanic** +- Blocked by #188. Blocked by Q-017 resolution (or timer fallback — see Open Questions above). +- The `StorytellerPlugin` in `server/src/storyteller/mod.rs` is currently a stub. This ticket adds the v0.1 contamination system. +- Minimum viable: a Bevy system that fires after a configurable tick threshold (`CONTAMINATION_DELAY_TICKS`, default 1800 = 30 game-minutes at 10 tps). On firing: (1) set a `ContaminationActive` resource to true, (2) push pressure on all Active triangles (increment `TriangleState::pressure` by a configured delta), (3) emit a `ContaminationEvent` that the monologue system can hook for "the air feels different" NPC behavioural shifts. +- This gives the session its shape: 30 minutes of daily life, then escalation begins. The contamination is not an event the player sees directly — it's the upstream cause of NPC deviations, tell escalation, and eventually confrontation-threshold crossings. +- If Q-017 resolves before this ticket ships: wire the actual pressure threshold from that resolution. If not: the `CONFRONTATION_THRESHOLD` constant is the hook point, set to a reasonable default (e.g., 75 on a 0-100 scale). +- Acceptance: integration test runs 1801 ticks, asserts `ContaminationActive` resource is set and all Active `TriangleState` entities have `pressure > 0`. + +**#509 — Fuzzy tests for dynamic/procedural maps** +- Part of QA epic #455. Randomized testing of procedurally generated maps against structural invariants. +- Invariants to assert: (1) all walkable tiles are reachable from the player start position (connectivity), (2) no entity spawns outside the map bounds, (3) every door entity has at least one adjacent walkable tile on each side, (4) tile count within expected range (±20% of configured density). +- Use `SimRng` with varying seeds (e.g., 50 seeds, 1ms timeout each). `proptest` or a manual seed loop both acceptable. +- Acceptance: `cargo test -p server -- fuzzy_map` passes with at least 50 seed variations, each asserting all 4 invariants. + +**#512 — Zone Gate room implementation** +- Part of QA epic #455. Gauntlet room for testing zone transitions. +- Add to `server/src/test_world/rooms/` following the existing room pattern (see `shift_change.rs` for reference). +- The room needs: two distinct zones (e.g., `ZoneId::Terminal` and `ZoneId::Corridor`), a walkable path crossing the zone boundary, a door entity on the boundary, and a reset plate. +- Next available StableId range starts at 75 (see `test_world/mod.rs` for the allocation table). Update the table comment. +- Acceptance: golden file test for this room passes; zone-crossing event fires when player entity crosses the boundary tile. + +## Dependency Chain + +``` +#565 (z-level type fix) — standalone +#567 (modifications stub) — standalone +#509 (fuzzy map tests) — standalone +#512 (zone gate room) — standalone + +#176 (NPC pool generation) + → #188 (triangle instantiation from authored content) + → #254 (contamination activation) [also blocked by Q-017 / timer fallback] +``` + +Parallel tracks: #565, #567, #509, #512 can all start immediately. #176 is the chain head — start it first after those are underway. + +## PR Workflow + +When ready to submit, create a PR with `tea` CLI: + +```bash +tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(simulation): Sprint 22 — NPC pool, triangles, contamination" --description "body" --base main --head server +``` diff --git a/docs/sprints/sprint-22/visual.md b/docs/sprints/sprint-22/visual.md new file mode 100644 index 000000000..e48c2042a --- /dev/null +++ b/docs/sprints/sprint-22/visual.md @@ -0,0 +1,68 @@ +# Sprint 22: Wire — Visual Tasks + +**Goal:** Fix the fog regression, wire the authored Sova content into a production population and triangle set, and activate the contamination pacing layer that gives the vertical slice its 30-minute shape. + +**Branch:** `visual` +**Agents:** Araminta (art direction) + +## New Tickets + +| # | Title | Blocked by | +|---|-------|------------| +| #563 | Fog shader too opaque — tune alpha for semi-transparent layers per D-059 | #569 (client) | + +Use `tooling/db/ticket show ` for full details. + +## Key Decisions + +- `decisions/perception.md` — D-059 (fog shader spec: light fog = desaturated 40-50%, brightness -30%; deep fog = near-monochrome with ~10% zone temperature tint; fog breathes with Perlin noise), D-011 (fog of perception is non-negotiable — world must be visible through fog, not hidden behind it), D-033 (entity color = relationship to player, must remain visible through fog overlay) +- `decisions/architecture.md` — D-066 (dual-scale grid, sim tiles vs visual tiles — fog gradient tuning is in visual-tile terms) +- `decisions/perception.md` — D-046 (three-reference lighting: warm amber = social/inhabited, cool white = institutional, mixed = transitional — zone temperature tint maps to this) + +## Open Questions to Resolve Early + +None blocking #563. But coordinate with Stig (client team) — **do not start #563 until #569 (fog architecture fix) is in client review.** The shader file is the same artifact. Starting early will cause a merge conflict. + +## Notes + +**#563 — Fog shader too opaque — tune alpha for semi-transparent layers per D-059** +- **Sequencing:** This ticket is intentionally blocked by client #569. Stig fixes the broken gradient and explored-tile architecture. Once #569 is in review and the shader is structurally correct, Araminta takes the final tuning pass. +- **The problem (from ticket):** Current alphas are too high — light fog (layer 2) at 0.25-0.55 and deep fog (layer 3) at 0.78-0.90. These values make fog feel like a wall, not a perceptual filter. D-059 specifies the world should be visible through the fog — desaturated and dimmed, not hidden. +- **Target behavior per D-059:** + - **Clear (forward cone):** Nearly transparent. Fog alpha ≈ 0.0 in the cone center. Soft gradient at the edge. + - **Explored (light fog):** World content fully visible through desaturation and dimming. Fog alpha in the range 0.25-0.35 — enough to signal "not current attention" without obscuring art. The 8-10s Perlin noise breathe animates within this range (±0.04). Entity D-033 colors visible but slightly muted. + - **Previously explored (deep fog):** World visible but near-monochrome. Fog alpha 0.55-0.70. Zone temperature tint applied as a color shift, NOT an additional opaque overlay. Bar zone: warm dark tint. Hub zone: cool dark tint. Corridor: neutral dark. The 15-20s Perlin breathe cycle is more pronounced here. + - **Unexplored:** Solid `#12141a`. Alpha 1.0. Correct as-is. +- **Files:** + - `client/shaders/fog.gdshader` — primary. Tune the `fog_alpha` constant and the `DARK_OVERLAY` color. The zone temperature tint is currently not implemented — add it as a sampled `zone_tint_tex` lookup (the uniform already exists in the shader; wire the zone palette into it from `fog_state.gd`). + - `client/scripts/autoloads/fog_state.gd` — may need zone tint texture population if the uniform is not yet wired. + - `docs/architecture/fog-shader-spec.md` — update if the approach changes from the current spec. +- **Zone temperature palette (D-059 + D-046):** + - Terminal/hub: `#1a1f2e` (cool blue-dark) + - Last Shift/bar: `#2a1f15` (warm amber-dark) + - Maintenance corridors: `#1a1a1a` (neutral dark) +- **Artistic test — the "Hopper test" (D-046):** After walking out of The Last Shift, the bar should still appear warm and inviting in deep fog — "the warm light is indifferent, not ironic." If the deep fog makes the bar's zone temperature tint readable as warm-dark against the surrounding cool-dark corridors, the tint is working. If everything looks the same dark, the tint needs more saturation. +- **Do not:** Apply D-033 relationship color tinting to the fog itself. Fog is a perceptual layer, not a relationship layer. Entity colors are unaffected by the fog shader — they render through it. +- **Acceptance:** + - Light fog (explored): underlying art, sprites, furniture clearly visible at approximately 25-35% fog overlay. + - Deep fog (previously explored): world visible but near-monochrome, zone temperature tint distinguishable between bar/hub/corridor. + - Unexplored: solid near-black, unchanged. + - Fog "breathes" — animated Perlin noise visible at both explored and previously-explored states. + - No hard edges between fog states — all transitions are smooth. + - Sign off from Stig (client team, who owns the underlying architecture) before merging. + +## Dependency Chain + +``` +#569 (client fog architecture fix) → #563 (visual fog alpha tuning) +``` + +Araminta should not begin shader work until Stig's #569 is in review on the client branch. + +## PR Workflow + +When ready to submit, create a PR with `tea` CLI: + +```bash +tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "fix(assets): Sprint 22 — fog shader alpha and zone temperature tint" --description "body" --base main --head visual +```