Added a visible danger-line indicator: a CSS overlay, not a WebGPU-rendered entity
Context
Identified during the prior session's feature-gap research (alongside the drop-preview fix, decision 0030) as a real, standard genre convention this project was missing: pe.gleam has tracked a danger_line_y (the world-y above which the lose-condition timer starts) since Phase 6, but nothing ever rendered where that line actually is -- a player can only discover it by losing. Continuing autonomously per the user's instruction to keep developing without per-step check-ins, addressed this next since it's the same category of gap as decision 0030 (real state, never made visible) and directly supports predictability, which the last several decisions (0029, 0030, 0032) have all been targeting from different angles.
Options considered
Decision
Added pub fn bounds.y_fraction_from_top(world_y, bounds) -> Float to pe/bounds.gleam (pure, fully gleam test-covered: at-top/at-bottom/midpoint/linearity/non-zero-origin/no-clamping cases, mirroring x_from_canvas_pixel's own existing test suite exactly). Made pe.danger_line_y pub (previously private) so game.gleam can compute the same y a real game-over decision uses, not a second hand-copied value. Added game_ffi.mjs's setDangerLinePosition (a single non-branching style.top write) wrapped by game.gleam's set_danger_line_position, called once in main() right after constructing the initial Model. index.html gained a #glemy-canvas-wrap positioning container and a #glemy-danger-line absolutely-positioned dashed-border div (pointer-events: none, so it can't intercept real clicks meant for the canvas underneath it).
Verification
152 Erlang / 175 JavaScript gleam test cases passing on both targets (6 new tests for y_fraction_from_top). Extended tools/browser_check.ts with a real-browser assertion that #glemy-danger-line's actual computed style.top reads exactly "5%" after main() runs (matching bounds 0..100 and danger_line_margin_from_top 5.0) -- confirms the real Gleam-to-FFI-to-DOM path actually executes, not just that the pure math is correct in isolation. Re-ran the check after the change: still passes cleanly, zero page errors, click-to-spawn and world-x conversion still work correctly (the new wrapper div doesn't interfere with the canvas's own getBoundingClientRect()-based click handling).
Consequences
A player can now see exactly where the lose condition triggers before ever reaching it, rather than discovering the danger line only by losing. The line's position would automatically stay correct if danger_line_margin_from_top or bounds are ever changed, since it's computed from the same source the real game-over logic uses, not a second hardcoded value. This pattern (a one-time-computed, FFI-set CSS position for a static world-space fact) is now the template for any future static overlay this project might want, distinct from pe.preview_entity's pattern (a per-frame, GPU-rendered synthetic entity) for anything that actually needs to track moving state.