fix(client): Sprint 22 — fog rendering fix and seed config #78

Closed
jpmschweitzer wants to merge 0 commits from client into main
Owner

Summary

Sprint 22 client team deliverables:

  • #569 — Fix fog system: blocky edges and zero visibility in explored areas

    • Root cause: update_from_state() used visible_tiles (empty in live server mode) instead of visible_positions for bounds calculation
    • Shader fix: removed guard that cut off Gaussian gradient at unexplored boundaries, doubled blur step for D-066 compliant 6-8 tile gradient
    • Added debug_exploration mode for diagnostic rendering
    • 19 acceptance tests
  • #175 — Entanglement ratio configuration (world_seed IPC)

    • StartupMessage struct with world_seed: u64 added to bridge types
    • Full protocol flow: client SessionManager → GameState.world_seed → SimBridge sends after handshake → server seeds SimRng
    • Protocol changes across local.rs, tcp.rs, sim_bridge.gd, protocol.gd
  • #178 — Seed-based variation

    • EntanglementConfig with from_rng() / from_seed() in server/src/content/entanglement.rs
    • flat ∈ [25,35]%, intrigue ∈ [15,25]%, mundane derived as remainder (D-029)
    • 10 Rust tests: determinism, variation ≥90%, bounds, sum invariant

Test results

  • 1061 server tests pass (including 10 new EntanglementConfig + 3 StartupMessage tests)
  • 19 new GDScript fog tests + 9 entanglement stubs + 2 encode tests

Known items

  • Visual QA sign-off from Araminta needed for fog (#569) before merging — headless verification insufficient
  • Mundane ratio spec note: D-029 says [45,55]% but independent sampling gives [40,60]% — documented with TODO

Decisions referenced

D-059 (fog shader spec), D-015 (vision cone), D-066 (dual-scale grid), D-029 (entanglement ratios), D-010 (deterministic simulation), D-020 (dumb renderer)

## Summary Sprint 22 client team deliverables: - **#569 — Fix fog system: blocky edges and zero visibility in explored areas** - Root cause: `update_from_state()` used `visible_tiles` (empty in live server mode) instead of `visible_positions` for bounds calculation - Shader fix: removed guard that cut off Gaussian gradient at unexplored boundaries, doubled blur step for D-066 compliant 6-8 tile gradient - Added `debug_exploration` mode for diagnostic rendering - 19 acceptance tests - **#175 — Entanglement ratio configuration (world_seed IPC)** - `StartupMessage` struct with `world_seed: u64` added to bridge types - Full protocol flow: client SessionManager → GameState.world_seed → SimBridge sends after handshake → server seeds SimRng - Protocol changes across local.rs, tcp.rs, sim_bridge.gd, protocol.gd - **#178 — Seed-based variation** - `EntanglementConfig` with `from_rng()` / `from_seed()` in server/src/content/entanglement.rs - flat ∈ [25,35]%, intrigue ∈ [15,25]%, mundane derived as remainder (D-029) - 10 Rust tests: determinism, variation ≥90%, bounds, sum invariant ## Test results - 1061 server tests pass (including 10 new EntanglementConfig + 3 StartupMessage tests) - 19 new GDScript fog tests + 9 entanglement stubs + 2 encode tests ## Known items - Visual QA sign-off from Araminta needed for fog (#569) before merging — headless verification insufficient - Mundane ratio spec note: D-029 says [45,55]% but independent sampling gives [40,60]% — documented with TODO ## Decisions referenced D-059 (fog shader spec), D-015 (vision cone), D-066 (dual-scale grid), D-029 (entanglement ratios), D-010 (deterministic simulation), D-020 (dumb renderer)
jpmschweitzer added 3 commits 2026-02-28 16:28:46 +01:00
Root cause: update_from_state() used visible_tiles (always empty in live
server mode) instead of visible_positions for bounds calculation. Bounds
never grew beyond 64x64, so tiles outside that area rendered as solid
unexplored black.

Fix: new _grow_bounds_from_positions() method reads visible_positions
(always populated from server snapshots). Shader fix: removed the
(explored < 0.01 && vis_raw < 0.01) guard that cut off the Gaussian
gradient at unexplored tile boundaries. Doubled blur step size for
D-066 compliant 6-8 tile soft gradient. Added debug_exploration mode
for diagnostic rendering of the exploration texture.

19 acceptance tests covering exploration persistence, bounds grow-only
invariant, gradient margin, Forward-only visibility writes, and
exploration data surviving texture resize.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements the StartupMessage protocol: client generates world_seed in
SessionManager.new_game(), sends it after handshake, server uses it to
seed SimRng and sample EntanglementConfig.

EntanglementConfig samples flat ∈ [25,35]%, intrigue ∈ [15,25]%, mundane
as remainder (D-029). Same seed produces identical config (D-010
determinism). Different seeds produce distinct configs in ≥90% of pairs.

Protocol flow: HandshakeMessage (server→client) → StartupMessage with
world_seed (client→server) → SimRng initialization → tick loop.

10 Rust tests (determinism, variation, bounds, sum invariant).
9 GDScript test stubs + 2 encode tests for client-side pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Owner

Review: client → main (type: code)

Hoshe (Code Quality): REQUEST_CHANGES

Solid implementation with good test coverage. Three warnings.

# File Severity Issue
1 session_manager.gd:38 warning randi() produces u32 only — upper 32 bits of u64 world_seed always zero. Document limitation or widen entropy.
2 entanglement.rs:183-199 warning mundane_ratio test asserts [40,60] but D-029 specifies [45,55]. Spec divergence shipped as TODO — must resolve or update D-029.
3 fog_state.gd:11 suggestion VIS_PERIPHERAL = 180 is dead code — no longer written anywhere. Remove it.
4 test_fog_sprint22.gd:16-20 suggestion Private field access via get() + push_warning silently passes if fields rename. Consider test-only accessors or fail().
5 fog_state.gd:161-187 suggestion _grow_bounds() is dead code — replaced by _grow_bounds_from_positions(). Remove or document.
6 layer3.rs:88 suggestion Only tests seed=42 — zero and max-u64 not exercised at integration level.

Tyre (Architecture): REQUEST_CHANGES

Clean architecture, seed IPC is well-designed. Two warnings block merge.

# File Severity Issue
1 entanglement.rs:187-202 warning mundane_ratio bounds violate D-029 spec [45,55] — test widens to [40,60] with a TODO. Must fix sampling or update D-029.
2 session_manager.gd + main.rs warning world_seed not persisted to save file — resume_game() sends seed=0 to server, breaking deterministic replay (D-010 principle 4).
3 fog.gdshader:9,93-96 suggestion exp_fade comment says "soft transition" but filter_nearest on exploration_tex makes it a hard step. Comment is misleading.
4 entanglement.rs + main.rs suggestion EntanglementConfig::from_rng() never called in production — seed plumbing works but config isn't consumed yet. Intentional scope boundary? Needs tracking ticket.
5 session_manager.gd:24-25 suggestion game_id hex and world_seed are consecutive outputs from same PRNG — correlated. Mild metagaming concern per D-029.

Verdict: CHANGES REQUESTED

Shared concerns across both reviewers:

  1. mundane_ratio spec divergence — both flagged the [40,60] vs D-029's [45,55]. Fix the sampling or update the spec.
  2. world_seed not in save file (Tyre) — loaded sessions always get seed=0, breaking deterministic replay.
  3. u32 seed entropy (Hoshe) — document the limitation or widen to full u64.

The fog fix (#569) is clean. The entanglement plumbing (#175/#178) is architecturally sound but has these two data integrity gaps that need resolution before merge.

## Review: client → main (type: code) ### Hoshe (Code Quality): REQUEST_CHANGES Solid implementation with good test coverage. Three warnings. | # | File | Severity | Issue | |---|------|----------|-------| | 1 | `session_manager.gd:38` | warning | `randi()` produces u32 only — upper 32 bits of u64 `world_seed` always zero. Document limitation or widen entropy. | | 2 | `entanglement.rs:183-199` | warning | `mundane_ratio` test asserts [40,60] but D-029 specifies [45,55]. Spec divergence shipped as TODO — must resolve or update D-029. | | 3 | `fog_state.gd:11` | suggestion | `VIS_PERIPHERAL = 180` is dead code — no longer written anywhere. Remove it. | | 4 | `test_fog_sprint22.gd:16-20` | suggestion | Private field access via `get()` + `push_warning` silently passes if fields rename. Consider test-only accessors or `fail()`. | | 5 | `fog_state.gd:161-187` | suggestion | `_grow_bounds()` is dead code — replaced by `_grow_bounds_from_positions()`. Remove or document. | | 6 | `layer3.rs:88` | suggestion | Only tests seed=42 — zero and max-u64 not exercised at integration level. | ### Tyre (Architecture): REQUEST_CHANGES Clean architecture, seed IPC is well-designed. Two warnings block merge. | # | File | Severity | Issue | |---|------|----------|-------| | 1 | `entanglement.rs:187-202` | warning | `mundane_ratio` bounds violate D-029 spec [45,55] — test widens to [40,60] with a TODO. Must fix sampling or update D-029. | | 2 | `session_manager.gd` + `main.rs` | warning | `world_seed` not persisted to save file — `resume_game()` sends seed=0 to server, breaking deterministic replay (D-010 principle 4). | | 3 | `fog.gdshader:9,93-96` | suggestion | `exp_fade` comment says "soft transition" but `filter_nearest` on `exploration_tex` makes it a hard step. Comment is misleading. | | 4 | `entanglement.rs` + `main.rs` | suggestion | `EntanglementConfig::from_rng()` never called in production — seed plumbing works but config isn't consumed yet. Intentional scope boundary? Needs tracking ticket. | | 5 | `session_manager.gd:24-25` | suggestion | `game_id` hex and `world_seed` are consecutive outputs from same PRNG — correlated. Mild metagaming concern per D-029. | ### Verdict: CHANGES REQUESTED **Shared concerns across both reviewers:** 1. **`mundane_ratio` spec divergence** — both flagged the [40,60] vs D-029's [45,55]. Fix the sampling or update the spec. 2. **`world_seed` not in save file** (Tyre) — loaded sessions always get seed=0, breaking deterministic replay. 3. **u32 seed entropy** (Hoshe) — document the limitation or widen to full u64. The fog fix (#569) is clean. The entanglement plumbing (#175/#178) is architecturally sound but has these two data integrity gaps that need resolution before merge.
jpmschweitzer added 1 commit 2026-02-28 22:11:26 +01:00
- Widen world_seed entropy from u32 to full u64 by combining two randi()
  calls (Hoshe warning #1)
- Persist world_seed to save directory and restore on resume_game() so
  loaded sessions maintain D-010 deterministic replay (Tyre warning #2)
- Constrain EntanglementConfig intrigue range based on flat value so
  mundane_ratio stays within D-029 spec [45,55]% (both reviewers)
- Remove dead VIS_PERIPHERAL constant and _grow_bounds() method
- Update test_client_p1 peripheral test for forward-only simplification
- Fix misleading exp_fade shader comment (filter_nearest = hard step)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jpmschweitzer added 1 commit 2026-02-28 23:24:51 +01:00
GDScript int is i64 — when randi() returns a value with bit 31 set,
left-shifting by 32 sets bit 63, producing a negative i64. MessagePack
encodes this as a negative integer, which Rust rmp_serde rejects when
deserializing as u64, causing ~50% startup failure rate.

Fix: mask bit 31 before shifting in new_game() to cap entropy at 63
bits. Also mask the sign bit in _read_seed_file() to handle save files
written before this fix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jpmschweitzer closed this pull request 2026-02-28 23:34:41 +01:00

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: jpmschweitzer/settled-reach#78