feat(trails): render authoritative player route trails

This commit is contained in:
2026-09-13 23:31:26 +03:00
parent 8ad856b931
commit 3518f8a152
12 changed files with 367 additions and 6 deletions
+205 -1
View File
@@ -3436,7 +3436,7 @@ resources.
## Milestone 030 — Explicit session exit and slot release
**Status:** `IN_PROGRESS`
**Status:** `COMPLETE`
### Objective
@@ -3644,3 +3644,207 @@ observer departure or a lobby departure must not reset gameplay.
flow in the browser and confirmed that the issue was fixed: a remaining
participant is returned from the result modal to a usable waiting lobby when
the other player explicitly leaves.
## Milestone 031 — Authoritative persistent player trails
**Status:** `IN_PROGRESS`
### Objective
Show a persistent dotted record of every successful route used by each player
during the current match, using the established yellow and blue ownership
colors and a mixed two-color treatment for shared passages. Each player may see
trail data only inside the current server-approved visibility projection, while
observers may see both complete trails. Trails must survive refresh and
reconnection without adding client requests or materially increasing ESP,
network, or rendering load.
### Scope
- Do not start implementation until every preceding milestone is `COMPLETE`.
Milestone 030 is `IN_PROGRESS` when this plan is created, so this milestone
must remain `NOT_STARTED` until Milestone 030 is complete.
- Keep trail ownership authoritative on the ESP. A browser-only history cannot
provide consistent trails to another player and observers after refresh or
reconnection, and must not be used as the source of truth.
- Record only accepted authoritative player transitions between cells. Mark
ordinary successful moves and the successful fifth-impact movement through a
newly opened hedge. Do not mark blocked, rejected, rate-limited, stale,
queued-but-superseded, or otherwise unsuccessful movement attempts.
- Represent an undirected traversed cell edge with a compact two-bit ownership
mask: Player 1, Player 2, both, or neither. Use a fixed-size per-match layout
derived from the bounded maze dimensions; do not allocate per step, store an
ordered movement history, or create a collection that grows with match
duration.
- Preserve the trail for the complete current match, including countdown-to-
active lifecycle, pause, normal reconnect, and page refresh. Clear it exactly
when authoritative match state is reset, cancelled, replaced by a new match,
or cleared by device reboot.
- Piggyback compact trail ownership on the existing role-appropriate view
projection and sequencing path. Do not add polling, a new browser request,
an extra message per movement, or a second trail-specific broadcast.
- Send player clients only the trail edges whose endpoints and passage are
inside their current authorized projection. Never serialize trail
coordinates outside the current visibility radius, and do not let the
browser retain or reconstruct hidden maze geometry from past projections.
- Send observers the complete authoritative trail together with their existing
full-map projection. Preserve observer read-only behavior and existing
snapshot chunk, message-size, queue, and backpressure bounds.
- Reconstruct the visible trail entirely in the frontend from each complete
server-approved projection. Prefer a fixed small number of batched SVG paths
or an equivalently bounded drawing layer rather than one DOM element per dot,
step, or historical event.
- Render a dotted yellow route for Player 1 and a dotted blue route for Player
2. When both players used the same passage, render an unmistakable mixed
yellow-and-blue dotted pattern on the same centerline rather than overwriting
one owner or blending them into an unrelated third color.
- Draw trails along traversable passage centers below players, keys, artifacts,
exits, doors, feedback, and controls but above the quiet floor. Trails must
not resemble walls, obscure objects, imply that a closed hedge is passable,
or expose a breakthrough until its opened edge is present in the same
authoritative projection.
- Keep current-player and other-player ownership understandable without color
alone by using a documented dot rhythm, outline, alternation, or equivalent
secondary cue consistent with the established player icon system. Shared
edges must remain recognizable in grayscale and at supported cell sizes.
- Preserve server-authoritative movement, fog of war, match results, explicit
session exit, reconnect behavior, compact controls, connection-quality
sampling, reduced-motion behavior, offline operation, Russian player-facing
copy, and firmware and LittleFS budgets.
### Deliverables
- Updated trail-state, visibility, protocol, lifecycle, ownership, layering,
and responsive-rendering contracts in `MVP.md`, `ARCHITECTURE.md`,
`docs/PROTOCOL.md`, `docs/UI_STYLE.md`, and `docs/PLAYER_UI.md`.
- A fixed-size authoritative two-player traversed-edge representation with
deterministic initialization, successful-move updates, reconnect retention,
and match-reset cleanup.
- A compact backward-safe view encoding that carries only role-authorized
trail ownership inside existing view chunks and broadcasts.
- A bounded player and observer trail renderer with yellow, blue, and mixed
two-color dotted treatments and no per-move accumulation of DOM nodes,
timers, animations, or client history.
- Deterministic game, protocol, visibility, serialization, browser, rendering,
lifecycle, reconnect, backpressure, size, and regression tests.
- Before-and-after maximum-session traffic, queue, heap, render-time,
compressed-web-asset, firmware, LittleFS, and representative physical-device
visual-verification evidence.
### Acceptance criteria
1. Every accepted authoritative in-maze move marks exactly one undirected edge
for the moving player's ownership. Ordinary passage moves and a successful
hedge breakthrough are marked; blocked, rejected, stale, duplicate,
rate-limited, and superseded attempts leave the trail unchanged.
2. Trail storage has a documented compile-time maximum derived from the bounded
maze cell count and consumes a fixed number of bits per possible canonical
edge and player. Repeated traversal, loops, backtracking, and arbitrarily long
matches cannot grow heap use or trail state.
3. Player 1 traversal produces the established yellow dotted treatment, Player
2 traversal produces the established blue dotted treatment, and traversal
of the same edge by both players produces one clearly mixed yellow-and-blue
pattern that preserves both ownership meanings without a third blended
color.
4. A player projection contains and renders trail ownership only for passages
fully inside that player's current server-approved visibility. It contains
no coordinates, ownership bits, inferred continuation, or retained drawing
outside the current projection, including edges previously visible to that
browser.
5. Each player sees both players' trails when those used passages are inside the
current permitted projection. The opponent trail cannot reveal the
opponent's current or historical route outside that projection.
6. An observer receives and renders the complete authoritative trails of both
players across the full maze, including shared edges, without gaining any
command authority or changing player projections.
7. Refreshing or reconnecting a player or observer during the same match
reconstructs the correct role-authorized trail from the fresh authoritative
view without browser-persisted trail history. Pause and resume do not add,
remove, duplicate, or reorder trail ownership.
8. A normal match reset, explicit player-leave cancellation, completed-match
restart, new maze, or device reboot clears all trail ownership before the
next match view. Session restoration alone never clears it.
9. Trail data is carried only in the existing view sequencing and chunking
flow. The implementation introduces no trail polling, new periodic request,
per-move trail message, additional broadcast pass, or independent retry
queue, and incomplete or stale view chunks never partially update a trail.
10. Every encoded view remains within the existing 512-byte WebSocket message
limit and published chunk-count and queue bounds. Deterministic worst-case
player and observer projections with every edge owned by both players
serialize successfully without hidden-state disclosure.
11. The frontend uses a documented fixed upper bound of drawing layers and DOM
nodes independent of move count. Projection replacement removes obsolete
geometry, and refresh, reconnect, reset, leave, result, and screen changes
leave no stale paths, timers, or retained hidden trail data.
12. Trails follow the center of actual open or server-opened passages and sit
below all players, objects, exits, hedge art, breakthrough effects, and
controls. They do not create overflow, cover interaction targets, resemble
walls, or make an intact hedge appear open.
13. Yellow, blue, and shared trail states remain distinguishable at 34, 40, and
46 CSS-pixel cells, in normal color and grayscale, and on supported narrow
portrait, compact-landscape, tablet, and observer layouts without relying
on sound, vibration, animation, or color alone.
14. Under the maximum supported two-player/four-observer load and a worst-case
fully marked maze, accepted movement latency, outgoing queue high-water,
dropped player messages, heap minima, observer responsiveness, and browser
rendering remain within the established Milestone 010 and Milestone 017
budgets with no material regression attributable to trail processing.
15. Automated game, protocol, browser, and host tests cover both owners, shared
edges, repeated traversal, loops, breakthroughs, rejected movement,
visibility clipping, observer completeness, chunk boundaries, worst-case
size, refresh, reconnect, pause, reset, explicit leave, stale views,
layering, cleanup, accessibility, and responsive layouts.
16. All host and browser tests pass; compressed-web-asset validation passes;
the payload remains within the 1.5 MiB web budget and LittleFS partition;
and firmware remains within the factory partition.
17. Physical-device testing confirms correct trail continuity during a complete
two-player match, refresh and reconnect reconstruction, mixed shared
passages, observer completeness, reset cleanup, and stable gameplay with no
perceptible input delay, rendering stutter, disconnect, or accumulated
resource use.
18. The project owner confirms on representative player and observer displays
that the dotted trails make used routes easy to follow, shared passages are
clearly attributed to both players, fog of war remains intact, and the map
stays readable and responsive, or explicitly approves a documented evidence
deviation.
### Evidence
- On 2026-09-13, the project owner required Player 1 and Player 2 routes to
remain visible until the end of the current match, survive refresh and
reconnect, and include only successful authoritative movement.
- The project owner required each player to see trail data only within the
current permitted visibility projection, while observers see both complete
trails, and selected a mixed two-color dotted treatment when both players use
the same passage.
- Planning analysis found that frontend-only state cannot consistently satisfy
cross-client, observer, refresh, and reconnect requirements without violating
fog of war. The milestone therefore limits ESP state to fixed-size per-edge
ownership bits, piggybacks authorized data on existing projections, and
delegates all trail drawing to a bounded frontend layer without additional
requests or broadcasts.
- Implementation and verification have not started, as requested by the
project owner.
- On 2026-09-13, implementation added a fixed 512-byte packed two-bit owner
mask for the at-most 2,048 canonical maze edges. Accepted in-maze movement,
including a completed hedge breakthrough, marks its canonical edge; rejected
and outward moves do not. Each existing view cell carries only authorized
east and south ownership masks, so no trail request, broadcast, or chunk type
was added. The browser replaces one non-interactive SVG layer per projection
with yellow, blue, and dual-rhythm shared dotted paths.
- On 2026-09-13, `bash scripts/run_host_tests.sh` passed all suites, including
authoritative shared-edge ownership and visibility coverage. The worst-case
observer serialization remained bounded at 88 chunks and 501 / 512 bytes.
`node --test test/web/test_player_app.js` passed 32 tests, including trail
mask validation and bounded rendering treatment. `python3
scripts/verify_web_assets.py` passed with deterministic payload
`245f402b654fdec986974d62d7f7cd3190fd79293a36c7c1e289200127d98f73` at
33,635 / 1,572,864 bytes. `/home/sasa/.platformio/penv/bin/pio run` passed
with firmware at 1,030,784 / 2,097,152 bytes, and `/home/sasa/.platformio/
penv/bin/pio run --target buildfs` passed at 2,031,616 / 2,031,616 bytes.
- On 2026-09-13, trail presentation was softened without changing protocol or
authority: stroke width is 0.065 cells, opacity is 58%, and both ownership
rhythms use shorter, more widely spaced dots. `node --test
test/web/test_player_app.js`, LittleFS build, and web-asset validation passed
with payload `5546c680b4d527815381735ec1e9016e02f0002efa865cb09e45b84e8f697cec`
at 33,638 / 1,572,864 bytes.