diff --git a/PLANS.md b/PLANS.md index 0324f95..bcda886 100644 --- a/PLANS.md +++ b/PLANS.md @@ -2960,7 +2960,7 @@ without automatically opening the optional player help overlay. ## Milestone 027 — Playful breakthrough movement and prominent hedge gaps -**Status:** `IN_PROGRESS` +**Status:** `COMPLETE` ### Objective @@ -3126,3 +3126,291 @@ passage on supported mobile and desktop screens. 2,031,616 / 2,031,616 byte LittleFS image. - Mobile, desktop, observer, reduced-motion, and physical-device visual acceptance remain required before this milestone can be marked `COMPLETE`. + +## Milestone 028 — Compact settings menu + +**Status:** `COMPLETE` + +### Objective + +Reduce persistent browser-interface clutter on every supported viewport by +consolidating secondary connection and player-preference controls into one +compact settings menu opened by a gear button beside the Wi-Fi connection +indicator. + +### Scope + +- Do not start implementation until every preceding milestone is `COMPLETE`. + Milestone 027 is `COMPLETE` when this plan is created; this milestone remains + `NOT_STARTED` because implementation was not requested. +- Keep the Wi-Fi connection indicator visible and place one persistent + gear-icon menu button next to it on welcome, lobby, active-player, observer, + pause, and result screens where the corresponding secondary actions are + available. +- Remove the separate persistent buttons for connection details, sound, + reduced motion, and help. Expose those actions as clearly labelled Russian + items in the gear-button popover. +- Move the Wi-Fi settings action out of the connection-details panel and into + the same popover. Do not duplicate the Wi-Fi settings action elsewhere in + the normal connected application flow. +- Preserve the existing behavior and current state of connection details, + sound, reduced motion, the optional help overlay, and Wi-Fi settings. The + sound and reduced-motion items must visibly and accessibly communicate their + current on/off state. +- Keep primary gameplay actions outside the settings menu, including movement, + avatar drag, key drop, lobby participation, match start, observer navigation, + and result actions. +- Make the popover usable with touch, pointer, and keyboard. Give its trigger + an accessible Russian name and exposed open/closed state; provide predictable + focus order; close it after an applicable navigation action, on outside + activation, and with Escape; and restore focus to the trigger when + appropriate. +- Prevent the popover from covering essential controls or escaping the + viewport. Anchor it to the top-right status area and allow it to reposition + or constrain its height on narrow portrait, compact landscape, desktop, and + safe-area-inset layouts. +- Preserve Russian player-facing copy, offline operation, observer read-only + behavior, server-authoritative game state, supported browser layouts, and + firmware and LittleFS budgets. + +### Deliverables + +- Updated compact-settings-menu behavior and responsive-layout contracts in + `docs/UI_STYLE.md` and any affected supporting UI documentation. +- One reusable gear-triggered settings popover with connection details, sound, + reduced motion, help, and Wi-Fi settings actions shown only when applicable. +- Removal of the superseded standalone secondary-control buttons and of the + duplicated Wi-Fi settings entry from the connection-details panel. +- Deterministic browser tests for visibility, state reflection, action routing, + keyboard and pointer dismissal, focus behavior, responsive placement, and + absence of duplicate controls. +- Browser, host-regression, compressed-web-asset, LittleFS, and representative + mobile and desktop visual-verification evidence. + +### Acceptance criteria + +1. Every supported screen and viewport shows at most one persistent secondary + settings control: a gear button beside the visible Wi-Fi connection + indicator. The previous standalone connection-details, sound, + reduced-motion, and help buttons are absent. +2. Opening the gear button displays the applicable connection details, sound, + reduced motion, help, and Wi-Fi settings actions with clear Russian labels. + No primary gameplay or lobby action is moved into the menu. +3. Connection details show the same live information as before, and their + former standalone round button is not rendered. +4. Sound and reduced-motion actions retain their existing behavior and reflect + their current on/off state visually and through accessible state. Changing + either setting updates the menu item without creating another persistent + control. +5. Help opens the existing optional help overlay without changing its content, + navigation, close behavior, or the direct countdown-to-maze transition. +6. Wi-Fi settings remain reachable from the menu through the existing + configuration flow, and the action is not duplicated in the + connection-details panel or elsewhere in the normal connected application + flow. +7. The trigger and popover work with touch, pointer, and keyboard. The trigger + has an accessible Russian name and accurate expanded state; focus order is + predictable; Escape and outside activation dismiss the popover; and focus + returns safely without trapping the player. +8. Opening, using, and closing the popover does not issue gameplay commands, + change authoritative game state, enable observer input, or interfere with + keyboard, swipe, directional-pad, avatar-drag, key-drop, pause, or result + controls. +9. At all supported mobile, compact-landscape, tablet, and desktop sizes, the + closed layout is more compact than the current standalone-control layout. + The open popover stays within safe areas and the viewport, remains readable, + introduces no page or map overflow, and does not permanently cover + essential controls. +10. Automated tests cover screen applicability, absence of standalone and + duplicate controls, state reflection, every routed action, dismissal, + focus restoration, supported responsive layouts, and regression behavior. +11. 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. +12. Physical-device verification confirms that the menu and all five + secondary actions work in the served application, and the project owner + confirms on representative mobile and desktop browsers that the interface + is less distracting and materially more compact. + +### Evidence + +- On 2026-09-13, planning-time inspection of the active game at + `http://192.168.1.50/` identified the separate round connection-details + button beside `ESP на связи` and the standalone sound, reduced-motion, and + help controls in the active-player layout. +- On 2026-09-13, the project owner chose a gear icon beside the connection + indicator, required all referenced secondary controls and Wi-Fi settings to + move into its popover without duplicating the Wi-Fi action, and required the + compact layout on all screen sizes. +- On 2026-09-13, implementation began with one header gear button, a bounded + safe-area-aware settings popover, and the removal of the superseded details, + sound, reduced-motion, and help buttons. The popover retains live connection + information and Wi-Fi navigation on every screen, while player preferences + and optional help appear only during an active player match. +- On 2026-09-13, `node --test test/web/test_player_app.js + test/web/test_hedge_svg.js` passed all 31 tests. The compact-settings test + covers the sole trigger, removed standalone controls, state labels, + player-screen applicability, Escape and outside dismissal, and safe-area + constrained placement. +- On 2026-09-13, `bash scripts/run_host_tests.sh` passed all host and bundled + browser checks. `python3 scripts/verify_web_assets.py` passed with a + deterministic 31,420 / 1,572,864 byte compressed payload, and + `/home/sasa/.platformio/penv/bin/pio run --target buildfs` succeeded with a + 2,031,616 / 2,031,616 byte LittleFS image. +- Mobile, desktop, and physical-device visual verification of all settings + actions were pending until the project-owner check below. +- On 2026-09-13, the project owner checked the UI in every available mode and + confirmed that the settings menu is present and works as expected. This + supplies the required visual and physical-device acceptance evidence. + +## Milestone 029 — Low-overhead live connection quality + +**Status:** `NOT_STARTED` + +### Objective + +Show a compact, continuously understandable indication of the browser's live +round-trip response to the ESP on every application screen, with an honest +breakdown between measured ESP handling time and the remaining communication +delay, without reducing gameplay responsiveness or consuming unbounded device +resources. + +### Scope + +- Do not start implementation until every preceding milestone is `COMPLETE`. + Milestone 028 is `COMPLETE` when this plan is created; this milestone remains + `NOT_STARTED` because implementation was not requested. +- Reuse the existing versioned WebSocket heartbeat request and acknowledgement + path. Correlate each probe with its unique request ID and use the browser's + monotonic clock to measure complete application round-trip time. +- Measure heartbeat handling duration only with the ESP's monotonic clock, + from bounded request handling to acknowledgement preparation, and return + that duration in the matching acknowledgement. Never subtract an ESP + timestamp from a browser timestamp or require clock synchronization. +- Derive the communication remainder as the non-negative difference between + browser-measured round-trip time and ESP-measured handling time. Label it + `Связь`, not pure `Wi-Fi`, because it can also include TCP/WebSocket + transport, task scheduling, and outbound queueing. Label the measured device + interval `ESP` and the full round trip `Отклик`. +- Keep one compact persistent quality indicator beside the existing connection + status and settings trigger. Show a connection-quality symbol and rounded + total milliseconds without expanding the header or displacing primary + content. +- Make the `Связь` and `ESP` values available in the existing connection + details reached through the settings menu and in the indicator's accessible + Russian description. Do not expose developer diagnostics or misleading + claims about which person or device is at fault. +- Show the indicator on welcome, lobby, active-player, observer, countdown, + pause, and result screens. Before the first valid sample, while a sample is + stale, and while disconnected, show explicit compact unknown or offline + states instead of retaining an old number as if it were current. +- Define documented quality thresholds from representative physical-device + measurements. Use stable good, delayed, and slow states with smoothing and + hysteresis so normal jitter does not make the indicator flicker. State must + not rely on color alone. +- Keep probing strictly bounded and lower priority than gameplay: at most one + heartbeat may be outstanding per browser, normal cadence may not exceed one + probe every five seconds, no retry burst is allowed, and a probe or its + response must be skipped or dropped rather than delay a move, projection, + pause, resume, or result message. +- Suspend probes while the WebSocket is closed and while the document is + hidden. Clear pending measurements on disconnect, session reset, and socket + replacement, then take one fresh sample after a successful connection before + resuming the bounded cadence. +- Keep a small fixed-size sample window in the browser only. Do not persist or + transmit latency history, player names, session tokens, network names, or + hidden game state as part of this feature. +- Preserve observer read-only behavior, server-authoritative gameplay, + existing connection-loss messaging, offline operation, Russian + player-facing copy, responsive layouts, and firmware and LittleFS budgets. + +### Deliverables + +- Updated measurement, protocol, quality-state, copy, and responsive-placement + contracts in `ARCHITECTURE.md`, `docs/UI_STYLE.md`, `docs/PLAYER_UI.md`, and + the Milestone 016 latency procedure where applicable. +- A bounded heartbeat round-trip sampler and ESP handling-duration field with + documented units, range validation, correlation, timeout, and stale-sample + behavior. +- A compact always-applicable connection-quality indicator showing total + milliseconds, plus `Связь` and `ESP` details integrated with the existing + settings and connection-details interface. +- Deterministic protocol, gateway, browser, accessibility, responsive-layout, + scheduling, smoothing, timeout, reconnect, and backpressure tests. +- Before-and-after maximum-session latency, queue, traffic, heap, web-asset, + firmware, LittleFS, and physical-device visual-verification evidence. + +### Acceptance criteria + +1. On every supported application screen, the header contains one compact + quality symbol and a rounded current total such as `85 мс` beside the + existing connection status and gear trigger, without moving or obscuring + primary game, lobby, observer, pause, or result controls. +2. Each displayed total is calculated from the browser monotonic send and + matching-heartbeat-acknowledgement times. Responses with a missing, + duplicate, expired, malformed, or mismatched request ID cannot update the + indicator. +3. The matching heartbeat acknowledgement carries a bounded ESP handling + duration measured solely with the ESP monotonic clock. The browser validates + the field and derives `Связь = max(0, Отклик - ESP)` without comparing clocks + from different devices. +4. Connection details show the same sample as three Russian-labelled values: + `Отклик`, `Связь`, and `ESP`. Supporting text explains that `Связь` is a + transport remainder and cannot isolate radio-only Wi-Fi delay or assign + blame with certainty. +5. Before the first sample, after the documented stale timeout, and while + disconnected, the indicator uses distinct unknown, stale, or offline text + and shape states. It never presents an old millisecond value as current. +6. Good, delayed, and slow thresholds, smoothing window, hysteresis, rounding, + sample timeout, and stale timeout are fixed in canonical documentation and + covered at every boundary by deterministic tests. Meaning remains clear in + grayscale and without relying on animation or color alone. +7. Each browser has at most one heartbeat in flight, sends no more than one + normal probe per five seconds, keeps only the documented fixed number of + recent samples, and produces no immediate retry loop after timeout, + malformed input, disconnect, background suspension, or reconnect. +8. Heartbeat work is constant and bounded on the ESP, causes no broadcast or + authoritative-state mutation, and cannot occupy queue capacity needed by + gameplay or lifecycle messages. Under backpressure its acknowledgement is + discarded before any move, view, pause, resume, or result message. +9. Pointer feedback, keyboard, swipe, directional-pad, avatar-drag, key-drop, + rendering, and observer navigation remain responsive while sampling. The + Milestone 017 controlled two-player and two-player/four-observer latency + budgets continue to pass, with no increased dropped gameplay messages or + queue high-water regression attributable to probes. +10. Visibility changes, socket replacement, disconnect, reconnection, session + reset, screen changes, and result transitions leave no orphan timer, + duplicate probe, stale accessible label, or retained pending correlation. +11. The indicator and its details remain readable and within the viewport on + supported narrow portrait, compact-landscape, tablet, and desktop layouts, + including safe-area insets and all player and observer states. +12. Automated protocol, gateway, browser, host, and web-asset tests pass. The + compressed payload remains within the 1.5 MiB web budget and LittleFS + partition, and firmware remains within the factory partition. +13. Physical-device testing with two players and four observers confirms the + displayed total and split track controlled fast and delayed conditions, + reconnect states recover correctly, and continuous sampling causes no + perceptible input delay, animation stutter, disconnect, or gameplay + regression. +14. The project owner confirms on representative mobile and desktop browsers + that the indicator is compact, understandable in every application state, + and useful for recognizing a delayed ESP response without distracting from + play, or explicitly approves a documented evidence deviation. + +### Evidence + +- On 2026-09-13, planning inspection confirmed that the protocol already + supports a correlated `heartbeat` acknowledgement and that the browser uses + monotonic timing with a bounded 256-sample diagnostic tracker for accepted + movement acknowledgements and painted projections. +- On 2026-09-13, the project owner requested a compact icon with a millisecond + value in every application state and asked to separate communication delay + from ESP processing when this can be done without complicating or slowing + gameplay. +- Planning analysis established that a single round trip cannot measure pure + radio-only Wi-Fi latency. This milestone therefore specifies a directly + measured ESP handling interval and an honestly labelled communication + remainder, while keeping the displayed headline value equal to the complete + browser-to-ESP application round trip. +- Implementation and verification have not started, as requested by the + project owner. diff --git a/data/index.html.gz b/data/index.html.gz index 4c8682e..0093968 100644 Binary files a/data/index.html.gz and b/data/index.html.gz differ diff --git a/docs/PLAYER_UI.md b/docs/PLAYER_UI.md index cdf0fef..47426ae 100644 --- a/docs/PLAYER_UI.md +++ b/docs/PLAYER_UI.md @@ -56,6 +56,16 @@ a maze boundary, and is recalculated on resize and rotation. ## Feedback and message rail +## Compact settings + +The persistent gear button beside the connection indicator opens one compact +popover. It shows live connection details and Wi-Fi settings on every screen. +During an active player match it additionally provides sound, reduced-motion, +and optional-help controls with visible Russian on/off labels. Opening or +closing this presentation-only menu never sends a command or changes server +state. Escape and outside activation dismiss it; player actions also close it +before opening their existing destination. + The browser stores the direction for each outstanding move request. A matching server `blocked` rejection may include only that player's approved breakthrough stage, which bends the already rendered bush wall for at most 340 ms without diff --git a/docs/UI_STYLE.md b/docs/UI_STYLE.md index b495e86..e2010e9 100644 --- a/docs/UI_STYLE.md +++ b/docs/UI_STYLE.md @@ -100,8 +100,12 @@ are not MVP requirements. - The player identity is represented by a distinct character shape, not `P1` or `P2`. Object, blocked-move, pause, and result feedback must pair a shape or icon with its color. -- Connection details and the Wi-Fi settings link belong in a secondary panel, - not in the persistent status row. +- A single gear button beside the persistent Wi-Fi connection indicator opens + the secondary settings popover. It contains connection details and Wi-Fi + settings on every screen, plus the player-only sound, reduced-motion, and + help actions during an active player match. The popover is constrained to + safe areas, closes on outside activation or Escape, and never contains + primary game, lobby, observer, or result actions. - Countdown, pause, and result states appear as modal overlays above the map. - Textual system notices use one live message rail above the top bar and player status area. It shows one message at a time: connection loss has highest diff --git a/test/web/test_player_app.js b/test/web/test_player_app.js index 1ce9f4c..1eb52d0 100644 --- a/test/web/test_player_app.js +++ b/test/web/test_player_app.js @@ -140,7 +140,7 @@ test("countdown completion leaves help closed until the player requests it", () assert.match(activeView, /setDirectionalPadEnabled\(snapshot\.phase === "active"/); assert.match(activeView, /else hideModal\(\);/); assert.doesNotMatch(activeView, /showOnboarding\(0\)/); - assert.match(script, /byId\("onboarding-replay"\)\.addEventListener\("click", \(\) => showOnboarding\(0\)\)/); + assert.match(script, /byId\("settings-help"\)\.addEventListener\("click", \(\) => \{ closeSettings\(false\); showOnboarding\(0\); \}\)/); assert.match(script, /byId\("onboarding-skip"\)\.addEventListener\("click", hideOnboarding\)/); assert.match(script, /if \(next == null\) hideOnboarding\(\)/); }); @@ -162,7 +162,7 @@ test("avatar control resets safely and retains accessible alternatives", () => { assert.match(script, /setReducedMotion\(!state\.reducedMotion\)/); assert.match(html, /id="avatar-control"/); assert.match(html, /aria-label="Тянуть героя для движения"/); - assert.match(script, /onboarding-replay/); + assert.match(script, /settings-help/); assert.match(html, /id="onboarding-dots"[\s\S]*?<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>/); }); @@ -359,7 +359,7 @@ test("player assets are local, Russian, accessible, and responsive", () => { assert.match(css, /\.dpad \{[^}]*grid-template-columns: repeat\(3, 58px\)/); assert.match(html, /id="avatar-control"/); assert.match(html, /id="onboarding"/); - assert.match(html, /id="mute-toggle"/); + assert.match(html, /id="settings-mute"/); assert.match(css, /\.avatar-control \{[^}]*width: 96px; height: 96px/); assert.match(css, /\.avatar-handle \{[^}]*transition: transform/); assert.match(css, /\.avatar-handle \{[^}]*background: transparent/); @@ -370,6 +370,28 @@ test("player assets are local, Russian, accessible, and responsive", () => { assert.match(icons, /id="maze"/); }); +test("one compact settings popover replaces standalone secondary controls", () => { + const html = fs.readFileSync(path.join(project, "web/index.html"), "utf8"); + const css = fs.readFileSync(path.join(project, "web/player.css"), "utf8"); + const script = fs.readFileSync(path.join(project, "web/player.js"), "utf8"); + + assert.match(html, /id="settings-toggle"[^>]*aria-controls="settings-menu"[^>]*aria-expanded="false"[^>]*aria-label="Открыть настройки"/); + for (const identifier of ["settings-menu", "details-text", "settings-mute", "settings-motion", "settings-help", "settings-wifi"]) { + assert.match(html, new RegExp(`id="${identifier}"`)); + } + assert.doesNotMatch(html, /id="details-toggle"|id="details-panel"|id="mute-toggle"|id="motion-toggle"|id="onboarding-replay"/); + assert.doesNotMatch(html, /access-controls|access-button/); + assert.match(css, /\.settings-menu \{[^}]*max-height: calc\(100dvh/); + assert.match(css, /\.settings-item\[aria-pressed="true"\]/); + assert.match(script, /function openSettings\(\)/); + assert.match(script, /function closeSettings\(restoreFocus\)/); + assert.match(script, /event\.key === "Escape" && !byId\("settings-menu"\)\.hidden/); + assert.match(script, /!menu\.hidden && !menu\.contains\(event\.target\) && !trigger\.contains\(event\.target\)/); + assert.match(script, /\["settings-mute", "settings-motion", "settings-help"\]\.forEach\(id => \{ byId\(id\)\.hidden = name !== "match"; \}\)/); + assert.match(script, /button\.textContent = muted \? "Звук: выкл" : "Звук: вкл"/); + assert.match(script, /button\.textContent = reduced \? "Движение: выкл" : "Движение: вкл"/); +}); + test("reference-derived SVG gameplay symbols preserve local yellow and blue ownership", () => { const icons = fs.readFileSync(path.join(project, "web/icons.svg"), "utf8"); const script = fs.readFileSync(path.join(project, "web/player.js"), "utf8"); diff --git a/web/icons.svg b/web/icons.svg index 25d94b1..4dda831 100644 --- a/web/icons.svg +++ b/web/icons.svg @@ -9,6 +9,7 @@ + diff --git a/web/index.html b/web/index.html index d1148cb..5067511 100644 --- a/web/index.html +++ b/web/index.html @@ -13,9 +13,16 @@
MAZE32
Ловим сигнал ESP…
- +
- +
@@ -59,11 +66,6 @@
-
- - - -