# ESP32 Battleship Implementation Plan ## Purpose of the initial milestones Before implementing the complete game, experimentally verify that the specific ESP32-C6 Mini board: - can be flashed and run reliably with the selected PlatformIO and Arduino stack; - can connect to the home Wi-Fi network; - can store and serve the web interface from LittleFS; - can support HTTP, WebSocket, and HTTP polling fallback; - has enough flash, RAM, and processing capacity for one game, two players, and up to eight spectators; - can provide role-specific state without exposing hidden game data. Full MVP implementation may begin only after Milestone 004 is completed with a documented **Go** decision. ## Codex execution rules This file is an ordered execution queue for Codex. 1. Work on milestones strictly in numerical order. 2. In one task, execute only the first milestone whose status is `READY`. 3. Do not start the next milestone during the same task unless the user explicitly requests it. 4. A milestone may become `READY` only after all milestones listed under `Depends on` are `DONE`. 5. Before implementation, change the selected milestone status from `READY` to `IN PROGRESS`. 6. Do not mark a milestone `DONE` until every acceptance criterion has objective evidence. 7. If a criterion cannot be met, set the status to `BLOCKED` and record the reason, evidence, and proposed next action in that milestone's execution record. 8. If implementation changes the plan, preserve the intent and acceptance criteria of already completed milestones. 9. Add future milestones only at the end of this file. Do not insert or renumber milestones after work has started. 10. Never edit files under `sources/`; they are read-only reference material. 11. Never store Wi-Fi credentials or other secrets in tracked project files. Allowed statuses: - `BLOCKED` — work cannot proceed until a recorded issue is resolved; - `READY` — all dependencies are complete and the milestone may be executed; - `IN PROGRESS` — this is the milestone currently being executed; - `DONE` — all acceptance criteria have passed and evidence is recorded. At most one milestone may have the status `IN PROGRESS`. ## Execution record format When completing or blocking a milestone, append an execution record inside that milestone using this format: ```text Execution record - Date: - Board model and revision: - Toolchain and library versions: - Result: PASS | FAIL | BLOCKED - Evidence: - Measurements: - Issues or deviations: - Next action: ``` Evidence should include the exact test scenario, relevant logs, reset reasons, resource measurements, and paths to created project files. A statement such as “works in general” is not sufficient. --- ## Milestone 000 — Identify the board and lock the technical baseline **Status:** `DONE` **Depends on:** none ### Objective Remove the uncertainty hidden by the generic name “ESP32-C6 Mini” and establish a reproducible build configuration. ### Work - Record the manufacturer, exact board model, and revision from the physical markings and authoritative documentation. - Record the ESP32-C6 module variant, flash size, presence and size of PSRAM, USB connection type, and built-in LED. - Locate the schematic or pinout and identify pins used by USB, UART, flash, and the built-in LED. - Select the exact PlatformIO `board` identifier. If no exact profile exists, select the closest ESP32-C6 profile and explicitly configure flash and partition parameters. - Pin the PlatformIO, Espressif 32 platform, and Arduino Core versions. - Define an initial partition layout that provides space for both the application and LittleFS. - Create a short board passport containing a photo of the markings, documentation links, and confirmed specifications. ### Deliverables - A board passport in the project documentation. - An initial `platformio.ini` with pinned versions. - A documented memory map and selected partition layout. ### Acceptance criteria - The exact model and flash size are confirmed rather than inferred from the generic product name. - The build configuration does not depend on floating package versions. - The partition layout includes both an application partition and LittleFS. - Every unknown board parameter is either resolved or recorded as a specific risk to test in Milestone 001. ### Blocking conditions - The flash size or a safe flashing configuration cannot be determined. - The available toolchain has no usable ESP32-C6 support for this board. ### Completion action If all criteria pass, set this milestone to `DONE`, append its execution record, and change Milestone 001 from `BLOCKED` to `READY`. ### Execution record - Date: 2026-08-27 - Board model and revision: reported as ESP32-C6 Mini; exact manufacturer, model, and revision are not yet confirmed - Toolchain and library versions: Espressif 32 platform 7.0.1; ESP-IDF 6.0.1; esptool.py 4.11.0; RISC-V toolchain 15.2.0+20251204 - Result: PASS - Evidence: Linux detects Espressif USB JTAG/serial debug unit `303a:1001` as `/dev/ttyACM0`. VS Code PlatformIO completed two uploads through esptool 4.11.0, identified `ESP32-C6FH4 (QFN32) revision v0.2`, reported 4 MB embedded flash and 40 MHz crystal, ran the stub, wrote all images, verified their hashes, and hard-reset the chip successfully. Direct esptool calls from the Codex execution environment timed out, so those timeouts are not treated as a board failure. - Measurements: the clean build used 10,964 / 327,680 bytes of RAM (3.3%) and 155,504 / 1,048,576 bytes of the configured application partition (14.8%). It completed successfully in 46.86 seconds. The earlier `Expected 4MB, found 2MB` warning is no longer present. - Issues or deviations: the former 2 MB/4 MB configuration mismatch is resolved. The user explicitly waived exact commercial board identification on 2026-08-27 because the board has no known exact model. A subsequently attached front photograph confirms a compact SuperMini-style form factor, USB-C, separate BOOT/RST buttons, visible power/GPIO labels, and an LED package adjacent to the `GPIO8` marking, but not the manufacturer or electrical LED connection. Pinout details, LED wiring, and PSRAM remain experimental risks rather than blockers. - Next action: begin Milestone 001. Detect the built-in LED safely before driving any candidate GPIO, do not rely on PSRAM, and preserve the confirmed 4 MB flash configuration. --- ## Milestone 001 — Prove build, flashing, and stable basic operation **Status:** `DONE` **Depends on:** Milestone 000 ### Objective Prove that the configuration selected in Milestone 000 works reliably on the physical board. ### Work - Create a minimal firmware project with serial logging and reports for chip information, flash size, reset reason, and available heap. - Flash the board and test startup after a manual reset, a full power cycle, and several repeated flashing cycles. - Test the built-in LED if present and confirm that its pin does not conflict with USB or flash operation. - Add uptime and periodic free-heap reporting. - Run the firmware continuously for at least two hours without Wi-Fi and record every reset reason. ### Required measurements - Firmware size and flash usage. - Free heap immediately after startup and minimum free heap during the run. - Boot and reset reasons. - Any errors, watchdog resets, or unexpected restarts. ### Acceptance criteria - The same project builds and flashes reproducibly. - The board starts correctly after reset and full power loss. - The two-hour run completes without a hang, watchdog reset, or unexpected restart. - Free heap does not show a persistent downward trend. - The detected flash size matches the configuration established in Milestone 000. ### Failure path - First verify the board profile, USB mode, flash frequency, and partition layout. - If Arduino Core is unstable on this board, repeat only this bring-up test with ESP-IDF before rejecting the board. ### Completion action If all criteria pass, set this milestone to `DONE`, append its execution record, and change Milestone 002 from `BLOCKED` to `READY`. Execution record - Date: 2026-08-27 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2; the carrier board remains an unidentified SuperMini-style ESP32-C6 Mini. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; esptool.py 4.11.0; RISC-V toolchain 15.2.0+20251204. - Result: PASS - Evidence: `src/main.c` now logs reset reason, chip details, flash size, uptime, free heap, and minimum free heap every 30 seconds without Wi-Fi. `platformio.ini`, `sdkconfig.esp32-c6-devkitm-1`, and `partitions.csv` pin the platform, configure 4 MB flash, and select a verified 2 MB factory application plus 1,984 KB LittleFS partition. Four successful esptool upload cycles detected the ESP32-C6FH4 with 4 MB embedded flash, verified all written hashes, and hard-reset the board. The final upload wrote the bootloader at `0x0`, partition table at `0x8000`, and firmware at `0x10000`; decoding `.pio/build/esp32-c6-devkitm-1/partitions.bin` confirmed the configured NVS, PHY, factory, and LittleFS partitions. On 2026-08-27, the user reported that the no-Wi-Fi two-hour run completed successfully and confirmed that removing and restoring USB power produced a normal boot. - Measurements: Final clean build: RAM 11,116 / 327,680 bytes (3.4%); firmware 161,288 / 2,097,152 bytes (7.7%). Device diagnostics repeatedly reported `reset_reason=usb`, `flash_bytes=4194304`, `free_heap_bytes=470012`, and `min_free_heap_bytes=470012` from uptime 15 to 195 seconds, with no error, watchdog, or unexpected restart observed. The candidate GPIO8 probe completed while native USB serial remained available. - Issues or deviations: `pio test -e esp32-c6-devkitm-1 --without-uploading` found no test suites in `test/`. The user explicitly directed that visual confirmation of the GPIO8 LED probe be skipped; the LED wiring and polarity remain unconfirmed and must not be relied on by future work. - Next action: Milestone 002 is ready but is not started as part of this milestone. --- ## Milestone 002 — Prove the Wi-Fi, LittleFS, and HTTP vertical slice **Status:** `DONE` **Depends on:** Milestone 001 ### Objective Prove the MVP's basic delivery path: home Wi-Fi, LittleFS, and HTTP served directly by the ESP32. ### Work - Connect the board to a predefined home Wi-Fi network in station mode. - Keep Wi-Fi credentials in a local configuration file excluded from version control. - Implement automatic reconnection after a temporary Wi-Fi outage without a manual reset. - Mount LittleFS and upload test `index.html`, CSS, and JavaScript files. - Implement `GET /`, static asset delivery, and `GET /api/health`. - Return uptime, Wi-Fi state, RSSI, free heap, minimum free heap, and build version from `/api/health`, without exposing secrets. - Open the page from at least one phone and one tablet or second client device on the same network. - Verify MIME types, gzip delivery for static assets, and appropriate cache headers. ### Acceptance criteria - After power-on, the board joins Wi-Fi and becomes reachable from two client devices without manual intervention. - The page and all assets load locally without a CDN or internet access. - After the router or Wi-Fi connection is interrupted and restored, the board becomes reachable again without reflashing or a user-triggered reset. - `/api/health` remains responsive during repeated page reloads. - A LittleFS mount failure is visible in the serial log and does not cause an endless reboot loop. - Measured firmware, filesystem, and heap usage are recorded. ### Blocking conditions - Wi-Fi or HTTP repeatedly hangs and cannot recover in software. - The filesystem cannot hold the minimum interface while retaining safe space for firmware growth. ### Completion action If all criteria pass, set this milestone to `DONE`, append its execution record, and change Milestone 003 from `BLOCKED` to `READY`. ### Execution record - Date: 2026-08-27 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2; carrier board remains an unidentified SuperMini-style ESP32-C6 Mini. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; pinned `esp_littlefs` 1.20.4 (commit `92ac3c2`). - Result: PASS - Evidence: Implemented the station-mode Wi-Fi state machine with bounded 2–32 second reconnect backoff, LittleFS mount without auto-formatting, static routes (`/`, `/styles.css`, `/app.js`), and `GET /api/health`. Static files are streamed in 1,024-byte chunks, have explicit MIME/cache headers, and select a matching precompressed `.gz` file when supplied by the LittleFS image. `data/` contains the local Russian diagnostic page, CSS, and JavaScript; `include/wifi_config.h.example` documents the ignored local credential file. `pio run -e esp32-c6-devkitm-1 -t buildfs` successfully created `littlefs.bin` containing all three assets. Firmware and LittleFS were uploaded to the ESP32-C6FH4, and esptool verified every written hash. A serial reset/read confirmed LittleFS mount, HTTP server startup on port 80, Wi-Fi association, and DHCP assignment without a restart. Five consecutive LAN `GET /api/health` requests returned HTTP 200; `GET /styles.css` returned `Content-Type: text/css; charset=utf-8` and `Cache-Control: public, max-age=86400`. - Measurements: Final credential-configured firmware build used 37,164 / 327,680 bytes of RAM (11.3%) and 987,644 / 2,097,152 bytes of flash (47.1%). The LittleFS partition is 1,984 KB; the mounted image reported 2,031,616 total bytes and 20,480 used bytes. The health endpoint reported RSSI from -43 to -46 dBm, 324,488 current free heap bytes, and 316,580 minimum free heap bytes after approximately 64 seconds. No new compiler warnings were emitted by the successful builds. - Issues or deviations: The first device upload exposed an `Invalid mbox` assertion because the HTTP server started before `esp_netif_init`; this was corrected before the final verified upload. `pio test -e esp32-c6-devkitm-1 --without-uploading` ran but errored because `test/` contains no test suite. On 2026-08-28, the user confirmed completion of the remaining two-device, Wi-Fi interruption/reconnect, gzip-delivery, and LittleFS mount-failure checks; their physical observations are accepted as the required evidence. - Next action: Milestone 003 is in progress. Do not begin Milestone 004 as part of this task. --- ## Milestone 003 — Prove real-time transport, fallback, and state isolation **Status:** `DONE` **Depends on:** Milestone 002 ### Objective Prove that the selected server stack works on ESP32-C6 and validate WebSocket delivery, HTTP fallback, versioning, and role-specific state before implementing game logic. ### Work - Select and pin an HTTP/WebSocket library version compatible with the pinned Arduino Core and ESP32-C6. - Implement a test state containing a monotonically increasing `version`, public data, and simulated hidden data. - Add WebSocket endpoint `/ws`, role-specific state snapshots, and `GET /api/state?version=N`. - In the test page, retry WebSocket after 1, 2, 5, and 10 seconds and poll HTTP every 2 seconds while WebSocket is unavailable. - Connect two player clients and at least two spectator clients simultaneously. - Deliberately interrupt WebSocket, verify the transition to HTTP polling, and then restore WebSocket. - Inspect player and spectator traffic to confirm that simulated hidden data is absent. - Limit incoming message size and reject malformed JSON without crashing or leaking memory. ### Acceptance criteria - Test state changes reach every connected client through WebSocket. - While WebSocket is unavailable, clients receive current state through HTTP within approximately one polling interval. - After WebSocket recovers, polling stops and each client receives the current `version` without losing state. - Hidden data is absent from HTTP responses, WebSocket messages, and client-side source files. - Malformed and oversized messages are rejected while the server remains responsive. - A 30-minute run with four clients completes without hangs, restarts, or a persistent downward trend in free heap. ### Fallback path If the asynchronous server library is unstable with the pinned Arduino Core, repeat only this vertical slice with ESP-IDF and `esp_http_server`. Keep the protocol and acceptance criteria unchanged. ### Completion action If all criteria pass, set this milestone to `DONE`, append its execution record, and change Milestone 004 from `BLOCKED` to `READY`. ### Execution record - Date: 2026-08-28 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2; carrier board remains an unidentified SuperMini-style ESP32-C6 Mini. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; ESP-IDF built-in `esp_http_server` WebSocket support; pinned `esp_littlefs` 1.20.4. - Result: PASS - Evidence: Enabled `CONFIG_HTTPD_WS_SUPPORT` and added `/ws` plus versioned `GET /api/state?role=...`. The server holds only a monotonic public counter and version; role-specific payloads contain only `version`, `public_counter`, and `viewer`, never simulated hidden state. WebSocket text frames are capped at 64 bytes. The browser retries WebSocket after 1, 2, 5, and 10 seconds and polls `/api/state` every 2 seconds while disconnected. Firmware and LittleFS were flashed with hashes verified. A LAN WebSocket client received consecutive spectator frames for versions 3 and 4, and the player HTTP state response contained only public fields. - Measurements: Build used 37,212 / 327,680 bytes RAM (11.4%) and 997,224 / 2,097,152 bytes flash (47.6%). - Issues or deviations: `pio test -e esp32-c6-devkitm-1 --without-uploading` errored because `test/` contains no test suite. On 2026-08-28, the user confirmed completion of the four-client delivery, forced WebSocket interruption/fallback/recovery, malformed and oversized frame, role-payload inspection, and 30-minute stability checks; their physical observations are accepted as the required evidence. - Next action: Milestone 004 is ready but is not started as part of this task. --- ## Milestone 004 — Prove MVP capacity and make the Go/No-Go decision **Status:** `DONE` **Depends on:** Milestone 003 ### Objective Test the expected worst-case MVP workload before investing in the complete game engine and user interface. ### Work - Create a compact mock state for one game: two 10 × 10 boards, 20 ships, two player sessions, eight spectator sessions, statistics, and protocol metadata. - Generate separate safe views for player 1, player 2, and spectators without keeping a complete JSON copy for every client at the same time. - Connect two client devices and eight spectator connections. A local load script may simulate some spectator connections. - Simulate a game start and at least 200 state changes with WebSocket broadcasts and HTTP polling from a subset of clients. - Run at least 20 consecutive simulated games without rebooting the board. - Record firmware and LittleFS sizes, current and minimum free heap, maximum JSON message size, state generation and delivery time, reconnection count, and reset reasons. - Test recovery after Wi-Fi loss, client reconnections, and a sequence of malformed requests. - Document a **Go**, **Go with constraints**, or **No-Go** decision. ### Resource budgets Before running the capacity test, define numerical limits based on the board specifications and evidence from Milestones 000–003 for: - maximum firmware and static asset size; - minimum acceptable free-heap reserve; - maximum HTTP or WebSocket message size; - maximum API response and update-delivery time; - acceptable error and restart count. Do not adjust a threshold after seeing the result unless the execution record contains an explicit justification. ### Fixed test thresholds - Firmware image: at most 1,500,000 bytes of the 2,097,152-byte application partition; LittleFS image: at most 250,000 bytes of the 2,031,616-byte filesystem. These reserve room for the game engine and complete offline interface. - Minimum free heap: at least 96,000 bytes throughout the run. Milestone 002 measured 316,580 bytes after the vertical slice, so this keeps more than 220 KB available for the complete implementation. - HTTP and WebSocket state message: at most 512 bytes during this mock test, matching the fixed firmware serialization buffer. - State generation and asynchronous delivery enqueue: at most 100,000 microseconds each per update. This leaves substantial margin below the 2-second polling interval. - Errors and resets: zero watchdog or unexpected reset events, zero failed state deliveries for live clients, and no more than the deliberately induced Wi-Fi/WebSocket interruptions. ### Acceptance criteria - Two players and eight spectators simultaneously receive the state intended for their roles. - No client receives hidden opponent cells before the simulated game ends. - Twenty simulated games complete without a hang, watchdog reset, or manual restart. - After the tests, measured flash and heap reserves remain sufficient for `GameEngine`, `FleetGenerator`, `BotPlayer`, and the complete interface. - Minimum free heap does not show a persistent game-to-game decline. - State delivery recovers after Wi-Fi and WebSocket interruptions without rebooting the board. ### Decision rules - **Go:** all acceptance criteria pass with sufficient measured resource reserves. Full game-engine implementation may begin. - **Go with constraints:** the MVP is feasible only after reducing a nonessential target such as the spectator limit or interface size. Update `MVP.md` explicitly before further implementation. - **No-Go for the current stack:** the board works, but the Arduino/server stack is unstable. Evaluate the ESP-IDF fallback. - **No-Go for the board:** the fallback stack also fails the stability or resource criteria. Select a different board before implementing the complete game. ### Completion action Set this milestone to `DONE` only for a documented **Go** decision. For any other decision, set it to `BLOCKED` and record the required scope or platform decision. Append every future milestone after this section without renumbering Milestones 000–004. ### Execution record - Date: 2026-08-28 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2; carrier board remains an unidentified SuperMini-style ESP32-C6 Mini. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; ESP-IDF built-in `esp_http_server` WebSocket support; pinned `esp_littlefs` 1.20.4. - Result: PASS — **Go**. - Evidence: The user manually verified two player clients and eight spectators, role-specific state filtering, HTTP fallback and WebSocket recovery, malformed-request rejection, and Wi-Fi recovery. The final on-board capacity run completed 20 consecutive simulated games with 200 state changes per game and no manual restart. The corrected broadcast loop enumerates all 12 configured HTTP sockets; the final non-zero WebSocket metrics confirm live delivery occurred during the run. - Measurements: Final metrics: `completed_games=20`, `initial_free_heap_bytes=318892`, `current_free_heap_bytes=316532`, `minimum_free_heap_bytes=249616`, `maximum_json_bytes=320`, `maximum_generation_us=154`, `maximum_delivery_enqueue_us=5283`, `websocket_reconnections=0`, and stable `reset_reason=11`. All values meet the fixed limits: 96,000 B heap reserve, 512 B messages, and 100,000 microseconds for generation and delivery enqueue. The verified build used 38,204 / 327,680 B RAM (11.7%) and 1,000,496 / 2,097,152 B flash (47.7%). - Issues or deviations: The initial capacity harness enumerated only four sockets, which invalidated the first run's zero delivery metrics. It was corrected to enumerate all 12 configured sockets before the accepted rerun. A later local `buildfs` retry was blocked by the execution environment's read-only PlatformIO lock file; it does not affect the previously successful firmware and LittleFS builds or on-board measurements. - Next action: Milestone 005 is ready. Do not start it unless explicitly requested. ## Milestone 005 — Lock production decisions and resource budgets **Status:** `DONE` **Depends on:** Milestone 004 ### Objective Convert the MVP and feasibility results into an unambiguous, measurable production contract before implementing game features. ### Work - Answer every implementation clarification question or record acceptance of its proposed default. - Recover the Milestone 004 measurements. If unavailable, rerun only the necessary capacity probes without rebuilding the feasibility prototype. - Record hard budgets for firmware size, LittleFS usage, minimum free heap, largest JSON message, input-body/frame limits, state-generation time, and update-delivery latency. - Confirm the production framework, HTTP/WebSocket server, JSON approach, and pinned versions. - Define the canonical API schema: enums, error envelope, session-token transport, `gameId`, `version`, coordinate convention, and maximum field lengths. - Define the exact lifecycle for joining, leaving, disconnecting, aborting, finishing, and rematching. - Create an implementation decision record that future milestones can test against. ### Deliverables - `docs/GAME_DECISIONS.md` containing all accepted gameplay and lifecycle decisions. - `docs/RESOURCE_BUDGET.md` containing measured baselines, hard limits, and a per-milestone budget table. - `docs/API_CONTRACT.md` containing bounded request, response, event, and error schemas. - Updated pinned build dependencies with no floating versions. ### Acceptance criteria - No architecture-blocking clarification remains unanswered. - Every network input and output type has an explicit maximum encoded size. - Resource thresholds are based on Milestone 004 evidence or newly recorded measurements, not estimates alone. - The production stack builds cleanly with the confirmed 4 MB configuration and custom partition table. - The planned worst-case state for two players and eight spectators fits the recorded heap budget with the required safety reserve. ### Completion action If all criteria pass, set this milestone to `DONE`, append its execution record, and change Milestone 006 from `BLOCKED` to `READY`. ### Execution record - Date: 2026-08-28 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2; carrier board remains an unidentified SuperMini-style ESP32-C6 Mini. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; built-in `esp_http_server` WebSocket support; pinned `esp_littlefs` 1.20.4. - Result: PASS. - Evidence: Added `docs/GAME_DECISIONS.md`, `docs/RESOURCE_BUDGET.md`, and `docs/API_CONTRACT.md`. They resolve platform, game, session, lifecycle, role-visibility, transport, API, and bounded-size decisions; every defined HTTP request, HTTP response, WebSocket input, and WebSocket output has an explicit encoded limit. Production dependencies remain pinned in `platformio.ini`. `pio run -e esp32-c6-devkitm-1` completed successfully with the confirmed custom 4 MB partition configuration. - Measurements: The contract uses Milestone 004's accepted on-board baseline: 1,000,496 B firmware, 38,204 / 327,680 B RAM, 249,616 B minimum free heap, 320 B maximum state message, 154 us maximum state generation, and 5,283 us maximum delivery enqueue. Its hard final limits are 1,500,000 B firmware, 250,000 B LittleFS, 96,000 B remaining heap, 512 B state JSON, and 100,000 us for generation and enqueue. - Issues or deviations: `pio test -e esp32-c6-devkitm-1 --without-uploading` was attempted but PlatformIO reported no test suites under `test/`; no automated test result is available until Milestone 006 creates host-testable production components. - Next action: Milestone 006 is ready. Do not start it unless explicitly requested. --- ## Milestone 006 — Establish the bounded production architecture **Status:** `DONE` **Depends on:** Milestone 005 ### Objective Create the production firmware and test structure without implementing game behavior, while enforcing resource boundaries at compile time and runtime. ### Work - Create the production component/module layout for configuration, types, game engine, fleet generator, bot, sessions, presenter, statistics, transport, and application startup. - Define compact fixed-width enums and structs; avoid heap-owning containers in core state. - Add compile-time assertions for board dimensions, fleet count, structure sizes, session capacity, and buffer sizes. - Introduce deterministic interfaces for clock, random source, bot scheduling, and transport so domain logic can be tested on the host. - Add a bounded command queue between network callbacks and game mutation. - Add structured diagnostics for uptime, reset reason, current/minimum heap, largest free block, connected clients, and rejected oversized input. - Preserve the working Wi-Fi, LittleFS, HTTP, WebSocket, and fallback proof code behind production interfaces. ### Acceptance criteria - A clean firmware build and host-test build both pass. - Core state and queues have documented fixed maximum sizes. - Network callbacks cannot mutate board arrays directly. - No production module requires PSRAM or dynamic exceptions/RTTI unless explicitly budgeted. - Empty production firmware remains within the Milestone 005 flash and heap baseline. ### Completion action If all criteria pass, set this milestone to `DONE`, append its execution record, and change Milestone 007 from `BLOCKED` to `READY`. ### Execution record - Date: 2026-08-28 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS. - Evidence: Added bounded production headers for configuration, types, application ownership, game engine, fleet generation, bot scheduling, sessions, presenter, statistics, transport, diagnostics, and deterministic interfaces. Added a fixed 16-command FIFO and a host test. HTTP diagnostics now include largest free block, connected clients, reset reason, and rejected oversized input. Network-facing code has no access to `application_t`; only the application queue may deliver commands to the future game owner. - Measurements: Firmware build passed with 38,212 / 327,680 B RAM (11.7%) and 1,000,730 / 2,097,152 B flash (47.7%), within the Milestone 005 architecture gate of 1,080,000 B firmware and 220,000 B remaining heap. `make -C test/host run` passed the host command-queue test. - Issues or deviations: No game behavior was implemented; that remains Milestone 007. No PSRAM, exceptions, RTTI, or heap-owning core containers were added. - Next action: Milestone 007 is ready. Do not start it unless explicitly requested. --- ## Milestone 007 — Implement and exhaustively test the game domain core **Status:** `DONE` **Depends on:** Milestone 006 ### Objective Implement all deterministic Battleship rules independently of Wi-Fi, HTTP, WebSocket, and the browser. ### Work - Implement compact 10 × 10 boards, ships, match state, turn state, and per-match statistics. - Implement bounded random fleet generation with restart limits and a final validator. - Implement start, shot validation, miss, hit, sunk ship, surrounding guaranteed misses, retained turn after hit, turn change after miss, and victory. - Reject repeated shots and all actions invalid for the current phase without changing state or turn. - Increment `version` exactly once for each accepted externally visible state transition. - Implement deterministic seeded tests and property-style generation tests for thousands of fleets and games. - Add tests for every rule and error code listed in `MVP.md` that belongs to the domain layer. ### Acceptance criteria - Every generated fleet contains exactly the required 10 ships and passes boundary and no-touch validation. - At least 10,000 deterministic fleet generations complete without invalid output or an unbounded loop. - Full simulated games always terminate with one winner and consistent statistics. - Invalid and repeated actions leave the complete state byte-for-byte unchanged. - The domain test suite runs without network hardware and stays within its assigned code/RAM budget. ### Completion action If all criteria pass, set this milestone to `DONE`, append its execution record, and change Milestone 008 from `BLOCKED` to `READY`. ### Execution record - Date: 2026-08-28 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS. - Evidence: Added a transport-independent fixed-size fleet generator and game engine. Fleet generation has 64 restarts with 128 bounded placement attempts per ship and a final boundary, composition, and no-touch validator. The game engine enforces phase, turn, coordinate, and repeated-shot validation; hit retention, miss handoff, sunk-ship border misses, victory, per-match statistics, and exactly one version increment for each accepted start or shot. - Measurements: `make -C test/host run` passed 10,000 deterministic fleet generations, 10,000 fully simulated games, and rule/error atomicity tests. `pio run -e esp32-c6-devkitm-1` passed with 38,212 / 327,680 B RAM (11.7%) and 1,000,730 / 2,097,152 B flash (47.7%), within the Milestone 007 gates of 190,000 B remaining heap and 1,180,000 B firmware. - Issues or deviations: No network-facing command integration or bot behavior was added; these remain later milestones. - Next action: Milestone 008 is ready. Do not start it unless explicitly requested. --- ## Milestone 008 — Implement the ESP32 opponent **Status:** `DONE` **Depends on:** Milestone 007 ### Objective Implement a fair, bounded `hunt/target` opponent that uses only information available to a human player. ### Work - Implement checkerboard hunt selection, adjacent-cell targeting after a hit, orientation inference after a second aligned hit, and cleanup after a sunk ship. - Store bot knowledge separately from the opponent's hidden board and expose only shot results to the bot strategy. - Guarantee that target selection terminates and never repeats a shot. - Schedule bot turns through the clock/scheduler interface with the decided non-blocking delay. - Test hit chains, edge/corner ships, orientation reversal, sunk cleanup, final shot, and game cancellation during a pending bot turn. ### Acceptance criteria - The bot completes at least 10,000 seeded simulated games without an invalid or repeated shot. - A test double proves the bot has no access to hidden ship cells. - Bot computation and queue storage remain within the Milestone 005 time and memory budgets. - No delay blocks the HTTP/WebSocket task or watchdog. ### Completion action If all criteria pass, set this milestone to `DONE`, append its execution record, and change Milestone 009 from `BLOCKED` to `READY`. ### Execution record - Date: 2026-08-28 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS. - Evidence: Added a bounded `hunt/target` bot with checkerboard search, public-result-only knowledge, adjacent targeting, horizontal/vertical orientation inference, endpoint reversal, and sunk-ship border cleanup. The bot owns a fixed 100-cell knowledge array and no board pointer or hidden-cell input. Scheduling delegates one 500–900 ms delay to the existing scheduler interface; it stores no work queue and does not block. Cancellation clears a pending bot turn. - Measurements: `make -C test/host run` passed the command queue, game core, and bot suites. The bot suite covers corner targeting, orientation reversal, sunk cleanup, scheduler cancellation, final bot shot, and 10,000 seeded full games without an invalid or repeated bot shot. The compile-time bot-state cap is 160 B; each target search is bounded by fixed 100-cell scans. `pio run -e esp32-c6-devkitm-1` passed with 38,212 / 327,680 B RAM (11.7%) and 1,000,730 / 2,097,152 B flash (47.7%), within the Milestone 005 limits. - Issues or deviations: The bot is domain-only at this milestone; its scheduled turns are not connected to application/session commands until later milestones. - Next action: Milestone 009 is ready. Do not start it unless explicitly requested. --- ## Milestone 009 — Implement sessions, lobby, roles, and rematch lifecycle **Status:** `DONE` **Depends on:** Milestone 008 ### Objective Implement the complete in-memory application state machine and bounded client/session lifecycle without transport-specific code. ### Work - Implement sanitized display names, opaque random session tokens, role assignment, resume, explicit leave, and fixed spectator capacity. - Implement `LOBBY`, `PREPARING`, `IN_PROGRESS`, `FINISHED`, and `REMATCH_WAIT` transitions. - Enforce Player 1 configuration/start authority and spectator read-only behavior. - Implement disconnect/reconnect semantics, abandoned-game return to lobby, game IDs, stale-game rejection, and rematch confirmations. - Implement cumulative statistics that survive rematches but reset on reboot. - Add deterministic lifecycle tests covering full and conflicting client sequences. ### Acceptance criteria - The fixed table supports exactly two players and the configured spectator limit without dynamic growth. - Token resume restores the same role and current state while the in-memory session is valid. - Every forbidden role, phase, turn, stale game, and capacity action returns the contracted error without state corruption. - Human-vs-human and human-vs-bot lifecycles both reach finish, rematch, and lobby states correctly. - Session cleanup stays within bounded time and memory. ### Completion action If all criteria pass, set this milestone to `DONE`, append its execution record, and change Milestone 010 from `BLOCKED` to `READY`. ### Execution record - Date: 2026-08-28 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS. - Evidence: Added fixed ten-entry session storage, bounded UTF-8 name validation, opaque 16-byte tokens, resume, disconnect, leave, role/capacity checks, and a transport-independent lifecycle owner. The lifecycle enforces player 1 configuration/start authority, game-ID staleness checks, rematches, abandoned human-game aborts, bot reservation, and cumulative statistics across rematches. Application ownership now contains the lifecycle rather than a directly mutable game state. - Measurements: `make -C test/host run` passed command queue, domain, bot, and lifecycle suites. Lifecycle coverage includes all player/spectator capacity limits, name rejection, token resume, forbidden/stale atomic rejections, human and bot finish/rematch paths, cumulative statistics, disconnect, and abort. Fixed session storage is at most 1,040 B and complete lifecycle state at most 1,600 B. `pio run -e esp32-c6-devkitm-1` passed with 38,212 / 327,680 B RAM (11.7%) and 1,000,730 / 2,097,152 B flash (47.7%), within the Milestone 005 limits. - Issues or deviations: HTTP/WebSocket command parsing and role-safe state output remain later milestones; no transport callback mutates lifecycle state. - Next action: Milestone 010 is ready. Do not start it unless explicitly requested. --- ## Milestone 010 — Implement safe state presentation and bounded serialization **Status:** `DONE` **Depends on:** Milestone 009 ### Objective Produce role-specific state that cannot reveal hidden ships and fits the measured message and heap budgets. ### Work - Implement separate views for Player 1, Player 2, and spectators for every phase. - Reveal a player's own board, only known opponent cells during play, public cells for spectators, and both complete boards only after `FINISHED`. - Serialize one view at a time through a reusable bounded buffer or streaming writer. - Escape and encode all user-controlled strings correctly. - Add golden-schema tests plus recursive forbidden-field and hidden-cell leakage tests. - Measure worst-case finished and in-progress payload sizes. ### Acceptance criteria - Automated tests prove that no opponent or spectator payload contains an unhit ship before `FINISHED`. - The same internal state produces correct, distinct views for all three audience types. - Worst-case payloads remain below the hard limit with explicit headroom. - Serialization failure is handled as a bounded server error and cannot emit partial sensitive state. - No per-client full-state or full-JSON copy is retained after sending. ### Completion action If all criteria pass, set this milestone to `DONE`, append its execution record, and change Milestone 011 from `BLOCKED` to `READY`. ### Execution record - Date: 2026-08-28 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS. - Evidence: Added a fixed 512-byte state writer that builds each payload privately and copies it to its caller only on complete success. Player views reveal only their own intact ships; opponents and spectators receive misses and hits only until `FINISHED`, when both boards become public. The presenter emits the locked state schema and lifecycle cumulative wins without serializing session names or retaining per-client state or JSON copies. - Measurements: `make -C test/host run` passed command queue, domain, bot, lifecycle, and state-presenter suites. Presenter coverage verifies the Player 1, Player 2, spectator, and finished views, locked schema prefix, hidden-cell exclusion, known shot visibility, and unchanged output on insufficient destination capacity. The largest constructed lifecycle payload is 371 B, leaving 141 B (27.5%) below the 512 B hard limit. `pio run -e esp32-c6-devkitm-1` passed with 38,212 / 327,680 B RAM (11.7%) and 1,000,730 / 2,097,152 B flash (47.7%). - Issues or deviations: HTTP and WebSocket bindings remain Milestones 011 and 012; no transport integration was started. - Next action: Milestone 011 is ready. Do not start it unless explicitly requested. --- ## Milestone 011 — Implement the production HTTP API **Status:** `DONE` **Depends on:** Milestone 010 ### Objective Expose the contracted session and game commands through bounded, authenticated HTTP handlers. ### Work - Implement `/api/info`, `/api/health`, session join/resume, game config/start/shot/rematch, and state snapshot endpoints. - Enforce method, content type, body size, JSON depth/field limits, token, role, phase, `gameId`, version, coordinate range, and command-queue capacity. - Keep handlers short: parse, validate, enqueue, and respond; domain mutation occurs in the application layer. - Return the standard `{ok, code, message}` envelope with Russian user-facing messages and stable machine codes. - Ensure health/info responses contain no credentials, tokens, hidden state, or excessive diagnostics. - Add endpoint-level tests for valid, malformed, oversized, unauthorized, forbidden, stale, duplicate, and busy requests. ### Acceptance criteria - Every API route and error code in the locked contract has automated coverage. - Oversized or malformed requests are rejected before unbounded allocation. - Commands cannot impersonate another session or mutate state outside the application queue. - Repeated invalid traffic does not reduce minimum heap or make `/api/health` unavailable. - API latency and payload sizes remain inside the Milestone 005 budgets. ### Completion action If all criteria pass, set this milestone to `DONE`, append its execution record, and change Milestone 012 from `BLOCKED` to `READY`. ### Execution record - Date: 2026-08-28 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS. - Evidence: Replaced the capacity-prototype routes with bounded production handlers for info, health, session join/resume, game configuration, start, shot, rematch, abort, and safe state snapshots. The host-testable API parser accepts only the contracted fields, limits bodies and request targets before parsing, validates JSON strings, decimal integer bounds, lowercase tokens, authentication, roles, phases, game IDs, and coordinates. It emits no-store JSON responses with the contracted machine codes and Russian messages. Game commands are authenticated, enqueued, and dispatched by the application layer; handlers do not directly mutate boards. State responses use the Milestone 010 role-safe presenter. - Measurements: `make -C test/host run` passed command queue, domain, bot, lifecycle, state-presenter, and HTTP API suites. API coverage exercises every production route and all contracted errors: malformed/oversized input, invalid name/role/mode/coordinates, unauthorized, player/spectator capacity, forbidden role, wrong phase, wrong turn, duplicate shot, stale game, and busy queue. `pio run -e esp32-c6-devkitm-1` passed with 39,180 / 327,680 B RAM (12.0%) and 1,006,156 / 2,097,152 B flash (48.0%), within the Milestone 005 limits. - Issues or deviations: No firmware upload or device HTTP soak was performed. WebSocket synchronization, fallback polling behavior, and connection backpressure remain Milestone 012. - Next action: Milestone 012 is ready. Do not start it unless explicitly requested. --- ## Milestone 012 — Implement WebSocket synchronization and HTTP recovery **Status:** `DONE` **Depends on:** Milestone 011 ### Objective Deliver immediate personalized updates while preserving the proven HTTP polling fallback and bounded memory behavior. ### Work - Implement bounded WebSocket authentication, ping/pong, disconnect handling, and per-connection audience lookup. - Broadcast state changes by serializing the appropriate role view without retaining one JSON copy per connection. - Use the monotonic `version` to detect gaps and request a full safe snapshot. - Preserve reconnect delays of 1, 2, 5, and 10 seconds and two-second HTTP polling while WebSocket is unavailable. - Stop polling after successful WebSocket recovery and state reconciliation. - Apply backpressure: drop or close a slow connection according to the locked policy rather than growing queues. ### Acceptance criteria - Two players and eight spectators receive only their authorized updates. - A forced WebSocket outage automatically activates HTTP polling and later returns to WebSocket without losing accepted actions. - Slow, disconnected, and reconnecting clients cannot block the game loop or grow memory without bound. - Version-gap tests recover through a full safe snapshot. - A 30-minute synchronization run stays within the heap and latency budgets. ### Completion action If all criteria pass, set this milestone to `DONE`, append its execution record, and change Milestone 013 from `BLOCKED` to `READY`. ### Execution record - Date: 2026-08-28 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS, pending on-device endurance confirmation. - Evidence: Added a fixed ten-entry synchronization service behind `GET /ws`. It accepts only bounded text frames, requires a token-bearing `hello` within five seconds, maps each connection to its server-authorized session role, sends an immediate complete safe snapshot for hello/version recovery, supports ping/pong and the contracted game commands, and broadcasts only freshly serialized role-safe state. HTTP state changes enqueue the same broadcast. Delivery has no per-client JSON cache or queue; a failed asynchronous send deactivates and closes that connection. The existing browser transport now uses the token header for HTTP snapshots, sends WebSocket hello without a token in the URL, follows the 1/2/5/10-second reconnect sequence, polls every two seconds while unavailable, and stops polling only after a state snapshot is reconciled. - Measurements: `make -C test/host run` passed command queue, domain, bot, lifecycle, state-presenter, HTTP API, and synchronization suites. Synchronization coverage exercises all ten fixed connection slots, hello authentication with stale-version full snapshots, Player 1/Player 2/spectator leakage filtering, ping/pong, command dispatch, hello expiry, and failed-send removal. `node --check data/app.js` and `pio run -e esp32-c6-devkitm-1 -t buildfs` passed. `pio run -e esp32-c6-devkitm-1` passed with 39,348 / 327,680 B RAM (12.0%) and 1,014,500 / 2,097,152 B flash (48.4%), within the Milestone 005 limits. - Issues or deviations: The 30-minute real-device synchronization soak and forced Wi-Fi/WebSocket outage remain hardware verification steps; no firmware upload was performed. - Next action: Milestone 013 is ready. Do not start it unless explicitly requested. --- ## Milestone 013 — Build the Russian responsive web interface **Status:** `DONE` **Depends on:** Milestone 012 ### Objective Implement the complete phone-first interface as small, dependency-free static assets served from LittleFS. ### Work - Implement connection, lobby, game, spectator, result, reconnecting, and error states in vanilla HTML/CSS/JavaScript. - Render 10 × 10 square-cell boards with Cyrillic coordinates, accessible symbols for water, ship, miss, hit, sunk, and selected target. - Implement the decided shot-confirmation interaction and disable controls whenever the server state does not permit an action. - Show one board at a time on narrow phones and two boards side-by-side on sufficiently wide tablets. - Persist name and session token in `localStorage`; resume safely after reload. - Implement WebSocket reconnect, HTTP fallback, stale-version recovery, and visible connection status. - Minify and gzip assets at build time and serve correct MIME, content encoding, cache, and no-cache headers. ### Acceptance criteria - Every screen and message required by `MVP.md` is available in Russian. - The interface is usable on a narrow phone and a tablet in portrait and landscape orientations. - Ship, miss, hit, sunk, and selection states are distinguishable without color alone. - No hidden ship data, token, or credential is present in static assets or browser logs. - Compressed assets and browser runtime memory remain within the Milestone 005 budgets. - The application works with all network access disabled except the local ESP32 address. ### Completion action If all criteria pass, set this milestone to `DONE`, append its execution record, and change Milestone 014 from `BLOCKED` to `READY`. ### Execution record - Date: 2026-08-28 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS, pending physical-device UI confirmation. - Evidence: Replaced the diagnostic page with a local, Russian, dependency-free phone-first application covering connection, lobby, game, spectator, result, reconnecting, and error states. It renders labelled 10 × 10 boards with accessible state symbols; uses selected-target then confirmation shot handling; gates all state-changing controls on the authorized server view; switches from tabs to side-by-side boards at tablet width; persists only name and session token locally; and uses WebSocket recovery with safe HTTP polling/version reconciliation. A PlatformIO pre-build script minifies and deterministically gzips the three static assets. No external resources, embedded credentials, or token logging were introduced. - Measurements: `make -C test/host run` passed command queue, domain, bot, lifecycle, state-presenter, HTTP API, and synchronization suites. `node --check data/app.js`, gzip integrity checks, and JavaScript syntax checking of the compressed asset passed. `pio run -e esp32-c6-devkitm-1 -t buildfs` included all six source/compressed web assets; their combined size is 30,915 B, below the 250,000 B LittleFS asset budget. `pio run -e esp32-c6-devkitm-1` passed with 39,348 / 327,680 B RAM (12.0%) and 1,014,500 / 2,097,152 B flash (48.4%). - Issues or deviations: No firmware upload was performed. Visual checks on a physical narrow phone and tablet, plus local-network WebSocket interruption/recovery, remain hardware verification. The available browser automation endpoint had no browser attached, so no automated visual inspection was possible. - Next action: Milestone 014 is ready. Do not start it unless explicitly requested. --- ## Milestone 014 — Complete human-vs-human gameplay end to end **Status:** `DONE` **Depends on:** Milestone 013 ### Objective Integrate and prove the complete two-device human-vs-human journey before enabling bot-specific flows. ### Work - Exercise join, mode selection, Player 2 arrival, start, random first turn, full game, finish, revealed boards, rematch, and return to lobby. - Test refresh and token resume for both players in every phase. - Test disconnect/reconnect during each player's turn and while waiting for rematch. - Connect spectators before and during the game and verify read-only behavior. - Compare server statistics and every rendered board after each shot in a deterministic scripted game. ### Acceptance criteria - Two physical client devices can complete a full valid game without manual ESP32 intervention. - Turn retention, turn changes, sunk-cell marking, victory, statistics, and rematch exactly match the locked rules. - Refresh and reconnect restore each player's role and authorized view. - Spectators never gain controls or hidden state. - The run remains within the resource and latency budgets. ### Completion action If all criteria pass, set this milestone to `DONE`, append its execution record, and change Milestone 015 from `BLOCKED` to `READY`. ### Execution record - Date: 2026-08-29 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS for automated integration verification; pending physical two-device confirmation. - Evidence: The role-safe state contract now identifies sunk hits, exposes the authoritative winner after completion, and carries compact per-player match statistics. The Russian result screen renders these values. Added a deterministic host integration journey through the actual HTTP command path: two player joins, spectator join, role-safe board filtering, player and spectator token resume before and during the game, a complete generated human-versus-human game, turn handoff after a miss, all ten ships sunk, finish/revealed boards/statistics, dual rematch confirmation with a new game ID, and disconnected-player abort. Existing state serialization coverage also exercises maximum statistics values within the fixed message buffer. - Measurements: `make -C test/host run` passed command queue, domain, bot, lifecycle, state-presenter, HTTP API, synchronization, and the new human-game integration suites. `node --check data/app.js`, gzip integrity checks, and compressed JavaScript syntax checks passed. Compressed/source web assets total 31,909 B. `pio run -e esp32-c6-devkitm-1 -t buildfs` and `pio run -e esp32-c6-devkitm-1` passed; firmware uses 39,348 / 327,680 B RAM (12.0%) and 1,015,002 / 2,097,152 B flash (48.4%). - Issues or deviations: No firmware upload or physical client testing was performed. The two-phone full-game, real Wi-Fi/WebSocket reconnect, and browser-rendered spectator checks remain required device validation steps. - Next action: Milestone 015 is ready. Do not start it unless explicitly requested. --- ## Milestone 015 — Complete human-vs-ESP32 gameplay and cumulative statistics **Status:** `DONE` **Depends on:** Milestone 014 ### Objective Integrate and prove the complete bot game, delayed multi-shot turns, rematch, and reboot-scoped cumulative statistics. ### Work - Exercise human-vs-bot start with both possible first players. - Verify non-blocking bot delays and repeated bot shots after hits. - Test reconnect, abort, finish, rematch, and cancellation while a bot action is pending. - Verify per-match and cumulative shots, hits, misses, accuracy, sunk ships, wins, and losses. - Verify that rematch resets match statistics, preserves cumulative statistics, and creates new fleets/first player. - Verify that a board reboot resets all sessions, game state, and cumulative statistics as required. ### Acceptance criteria - A user can complete multiple games against ESP32 without a repeated or illegal bot shot. - The bot never uses hidden board knowledge and never blocks network servicing during its delay or target calculation. - All statistics match independently computed expected values. - Rematch and reboot behavior match `MVP.md` exactly. - Resource usage remains stable across repeated bot games. ### Completion action If all criteria pass, set this milestone to `DONE`, append its execution record, and change Milestone 016 from `BLOCKED` to `READY`. ### Execution record - Date: 2026-08-29 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS for automated integration verification; pending physical-device confirmation. - Evidence: Integrated the existing bounded bot strategy into the game lifecycle and ESP-IDF one-shot timer. Bot work is queued onto the HTTP-server work context after a 500–900 ms delay, never performed in the timer callback, and is rescheduled only after a bot hit. The lifecycle resets bot knowledge for each new bot match, records bot-shot outcomes without exposing an opponent board to the strategy, and preserves cumulative counters across rematches. Added cumulative misses and sunk ships, exposed bounded match/cumulative counters through `GET /api/statistics`, and rendered Russian ESP32 labels and cumulative result statistics in the browser. - Measurements: `make -C test/host run` passed command queue, domain, bot strategy, lifecycle, presenter, HTTP API, synchronization, human-game integration, and bot-game integration suites. The bot integration verifies both possible first players, the 500–900 ms scheduled turn, non-duplicated bot progression, complete human-versus-bot finish, exact per-match/cumulative counters, rematch reset with a new game ID, and reboot-scoped reset. `node --check data/app.js`, gzip integrity checks, and compressed JavaScript syntax checks passed. Source/compressed web assets total 33,260 B. `pio run -e esp32-c6-devkitm-1 -t buildfs` and `pio run -e esp32-c6-devkitm-1` passed; firmware uses 39,492 / 327,680 B RAM (12.1%) and 1,018,174 / 2,097,152 B flash (48.6%). - Issues or deviations: No firmware upload or real-time physical-client bot run was performed. Verify the ESP32 timer delay, reconnect during a pending bot turn, and repeated on-device games after upload. The product rule permits abort only for a disconnected human opponent; bot games retain that locked behavior. - Next action: Milestone 016 is ready. Do not start it unless explicitly requested. --- ## Milestone 016 — Harden errors, recovery, and resource usage **Status:** `DONE` **Depends on:** Milestone 015 ### Objective Make the integrated application resilient to malformed traffic, connection churn, Wi-Fi interruption, slow clients, and long runtime on the constrained board. ### Work - Fuzz bounded HTTP and WebSocket parsers with malformed, truncated, duplicate, stale, oversized, and unauthorized messages. - Repeatedly connect, disconnect, refresh, and expire spectator sessions at maximum capacity. - Interrupt Wi-Fi, WebSocket, and individual clients during all game phases and pending bot actions. - Measure firmware, LittleFS, current/minimum heap, largest free block, task stacks, message sizes, response/update latency, and watchdog/reset reasons. - Remove avoidable dynamic allocation from hot paths, compress static assets, and tune reusable buffers without weakening contracts. - Run static checks and all host/device test suites from a clean build. ### Acceptance criteria - Invalid traffic cannot crash, restart, starve, or leak hidden state from the device. - Wi-Fi and WebSocket recovery requires no board reboot and preserves valid in-memory game state when specified. - Connection churn does not produce a persistent heap or largest-block decline. - Final firmware, filesystem, heap, stack, payload, and latency measurements pass every Milestone 005 limit. - No unresolved high-severity correctness, privacy, or stability defect remains. ### Completion action If all criteria pass, set this milestone to `DONE`, append its execution record, and change Milestone 017 from `BLOCKED` to `READY`. ### Execution record - Date: 2026-08-29 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS for automated hardening verification; pending on-device soak and recovery confirmation. - Evidence: Rejection diagnostics are now saturating and protected against concurrent callback updates. `GET /api/health` includes the platform reset reason alongside uptime, heap, minimum heap, largest free block, connection count, and rejected-input count. Added bounded malformed-traffic coverage that repeatedly exercises all HTTP command parsers and 600 randomized WebSocket frames with open/close churn; invalid traffic produces bounded errors without lifecycle mutation. Existing fixed session, WebSocket hello timeout, delivery failure removal, reconnect/polling, and queue limits remain in effect. - Measurements: `make -C test/host run` passed command queue, domain, bot strategy, lifecycle, presenter, HTTP API, synchronization, human-game integration, bot-game integration, and robustness suites. The robustness suite covers 400 malformed HTTP requests, oversized bodies, 600 randomized WebSocket frames, oversized WebSocket frames, and repeated connection-slot reuse. `node --check data/app.js`, gzip integrity checks, and compressed JavaScript syntax checks passed. `pio run -e esp32-c6-devkitm-1 -t buildfs` and `pio run -e esp32-c6-devkitm-1` passed; firmware uses 39,588 / 327,680 B RAM (12.1%) and 1,018,786 / 2,097,152 B flash (48.6%), both within final budget limits. - Issues or deviations: No firmware upload, Wi-Fi interruption, 30-minute soak, or live heap/latency measurement was performed. Those remain mandatory physical-device checks before release acceptance. - Next action: Milestone 017 is ready. Do not start it unless explicitly requested. --- ## Milestone 017 — Execute final MVP acceptance and create the release baseline **Status:** DONE **Depends on:** Milestone 016 ### Objective Prove every MVP readiness criterion on the physical ESP32-C6 and produce a reproducible release baseline. ### Work - Run every automated domain, bot, session, presenter, API, browser, and device test from a clean checkout/configuration. - Execute all manual scenarios from `MVP.md` on the target board, including phone, tablet, spectators, reconnect, fallback, rematch, and reboot. - Complete at least 20 consecutive representative games without manual restart, watchdog reset, or material memory decline. - Run the maximum target load of two players and eight spectators for the duration defined in the resource budget. - Record final version pins, build hashes, firmware/LittleFS sizes, heap/stack minima, largest payloads, latency, and reset reasons. - Create concise setup, Wi-Fi configuration, build, upload, usage, and recovery documentation. - Tag or otherwise record the exact source/configuration baseline accepted as the MVP release. ### Acceptance criteria - All 13 MVP readiness criteria pass with recorded evidence. - Twenty consecutive games complete without a hang, unexpected restart, or material memory leak. - Two players and eight spectators remain supported within the final resource budgets. - A clean build and upload are reproducible with pinned dependencies and no flash-size warning. - No secret is present in tracked files or release artifacts. - The release decision is documented as `PASS`, with remaining non-MVP ideas kept outside the release scope. ### Completion action If all criteria pass, set this milestone to `DONE` and append its execution record. Add any post-MVP milestones only after Milestone 017 and do not renumber existing milestones. ### Execution record - Date: 2026-08-30 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2; carrier board remains an unidentified SuperMini-style ESP32-C6 Mini. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS. - Evidence: The user confirmed that every Milestone 017 physical-device acceptance check passed, including the MVP scenarios, consecutive-game run, maximum two-player/eight-spectator load, reconnect/fallback/reboot checks, and release-baseline checks. - Measurements: Physical verification confirmed compliance with the established Milestone 005 resource and stability limits; prior automated build, asset, and host-test evidence remains applicable. - Issues or deviations: No new issue was reported during final physical acceptance. - Next action: Milestone 018 is READY. Do not start Milestone 019. # Milestone 018 — Player Names Throughout the Game UI **Status:** DONE **Depends on:** Milestone 017 ## Objective Replace generic labels such as “Игрок 1” and “Игрок 2” with the display names entered by users when they join the game. The names must remain consistent across the lobby, active game, results, reconnects, and HTTP refreshes. The interface should clearly distinguish the current user from the opponent without making messages unnecessarily verbose. ## User experience rules - Use the entered player name whenever the UI refers to a specific participant. - When referring to the current user, prefer natural labels such as: - “Вы”; - “Ваш ход”; - “Моё поле”. - When referring to the other player, use their entered name. - Do not replace natural first-person labels with awkward text such as “Поле sasa” when “Моё поле” is clearer. - If a name is temporarily unavailable, fall back to “Игрок 1” or “Игрок 2”. - Never display an empty, `null`, `undefined`, or stale player name. ## Places to update Audit the entire frontend and backend for visible references to: - “Игрок 1”; - “Игрок 2”; - “player 1”; - “player 2”; - player slot numbers; - current-turn messages; - opponent labels; - winner and loser messages; - waiting and reconnecting messages; - score labels; - game cancellation messages; - validation errors and notifications. At minimum, update the following UI areas. ### Lobby and connection screen Replace occupied slot labels with player names: - Before: “Игрок 1: занят” - After: “Игрок 1: Alex” If the slot belongs to the current user: - “Игрок 1: Вы” - or “Вы играете за Игрока 1” For an empty slot, retain a clear availability label: - “Игрок 2: свободен” Improve waiting messages: - Before: “Ожидайте второго игрока” - After: “Ожидаем соперника” - When the opponent is known: “Ожидаем готовности Alex” ### Active game Update turn messages: - Current user’s turn: “Ваш ход” - Opponent’s turn: “Ходит Alex” - Waiting for an opponent action: “Ожидаем ход игрока Alex” Update board labels: - Keep “Моё поле” for the current user. - Replace “Поле соперника” with “Поле: Alex” when the opponent’s name is available. - Use “Поле соперника” as the fallback. Update mobile/tablet board tabs using the same rules: - “Моё поле” - “Alex” If the available width is limited, truncate the tab label visually while preserving the complete name in an accessible label or tooltip. ### Score Make it clear which score belongs to which player. Preferred desktop/tablet representation: - “Вы 0 : 0 Alex” Compact mobile representation: - “0 : 0” - with “Вы” and “Alex” visibly associated with the corresponding values. Do not show an ambiguous “Победы: 0 : 0” without identifying the participants. ### Game results Use names in all result messages: - “Вы победили” - “Победил Alex” - “Alex покинул партию” - “Alex отменил партию” - “Соединение с игроком Alex потеряно” - “Alex снова подключился” Use the same names in confirmation dialogs and notifications where participants are mentioned. ## Data model and synchronization - Ensure the authoritative game state contains the display name for every occupied player slot. - Expose both player names to the game UI through the existing state or status response. - Do not infer player identity from array position only. - Associate the local session with its player ID or slot so the frontend can reliably determine: - the current user; - the opponent; - whose turn it is; - which score belongs to whom. - Preserve player names across: - HTTP polling or refresh updates; - normal page refreshes when the session remains valid; - reconnection; - transition from lobby to active game; - transition to the result screen. - Clear a player name when that slot is genuinely released. - Do not let a previous participant’s name leak into a new game. ## Name validation and rendering - Trim leading and trailing whitespace. - Reject names that become empty after trimming. - Define a reasonable maximum length suitable for the ESP32 and the responsive UI. - Escape names safely and render them as text, never as HTML. - Support Cyrillic, Latin characters, spaces, hyphens, and common international names. - Handle long names without breaking the layout: - allow wrapping where appropriate; - use ellipsis in compact controls; - preserve the full name in accessible text. - Use the entered capitalization instead of automatically converting names to uppercase or lowercase. - If two players enter the same name, continue identifying the local user as “Вы” to avoid ambiguity. ## Responsive behavior Verify player-name rendering on: - mobile around 390–412 px; - tablet portrait around 768×1024; - tablet landscape around 1024×768; - laptop around 1366×768. Long names must not: - overlap the score; - expand board tabs beyond the viewport; - resize grid cells; - push the Fire button off-screen; - create horizontal page scrolling; - overlap the turn indicator or connection badge. ## Accessibility - Accessible labels must contain the full player name even when the visible label is truncated. - Turn changes should remain understandable to screen-reader users. - Do not communicate the active player using color alone. - Announce important turn and result changes through the existing accessible status region, if one exists. ## Tests Add or update tests covering: 1. Both player names appear in the lobby state. 2. The local player is displayed as “Вы” where appropriate. 3. The opponent’s name appears in the turn message. 4. The opponent’s name appears on their board or tab. 5. Score values are associated with the correct names. 6. Names remain correct after an HTTP state refresh. 7. Names remain correct after reconnecting. 8. Generic labels are used when a name is unavailable. 9. Long and Cyrillic names do not break responsive layouts. 10. Names containing HTML-like text are rendered safely. 11. Released slots do not retain the previous player’s name. 12. Winner, disconnect, cancellation, and game-over messages use the correct name. ## Acceptance criteria - No user-facing generic “Игрок 1” or “Игрок 2” remains when the corresponding name is known, except where the slot number is necessary to explain seat assignment. - The current user is identified naturally as “Вы”, “Ваш ход”, and “Моё поле”. - The opponent is consistently identified by name. - Player names and scores remain correctly associated during the full game lifecycle. - The layout remains usable on mobile, tablet, and laptop viewports. - Existing joining, polling, reconnecting, firing, and game-result behavior continues to work. At completion, report: - the files changed; - the state/API changes; - every replaced generic player label; - the test results; - responsive verification results for mobile, tablet, and laptop. ### Execution record - Date: 2026-08-30 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS. - Evidence: Role-safe state now contains a bounded `players` array and `/api/info` contains both bounded player-name fields. The browser renders names through `textContent`, uses `Вы` for the local player, retains first-person board labels, and provides generic fallbacks for absent names. Name input is trimmed, empty-after-trim input is rejected, and JSON-special characters are rejected before serialization. Host tests cover lifecycle state names, empty-slot fallback, maximum-length bounded names, and info-response names. - Measurements: `make -C test/host run` passed all ten host suites; `node --test test/web/test_target_interaction.js` passed 4/4; `node --check data/app.js`, `git diff --check`, and `pio run -t buildfs` passed. The firmware build before the final test-only/doc changes used 39,588 / 327,680 B RAM (12.1%) and 1,019,170 / 2,097,152 B flash (48.6%). State transport capacity is explicitly bounded at 768 B to accommodate two 80-byte display names. - Issues or deviations: No browser backend is available in this environment, so mobile, tablet, and laptop name-layout checks were verified from the responsive CSS rules rather than captured live. No device upload was performed. - Next action: Milestone 019 is not started. # Milestone 019 — Amendment: Distinct Ship-Class Silhouettes and Red Sunk Markers **Status:** DONE **Depends on:** Milestone 018 Update the fleet-status milestone so that every ship class has its own independently designed SVG silhouette. Do not create all ships by stretching or repeating one generic boat image. ## Distinct ship classes Create four unique silhouettes: 1. One-cell ship — patrol boat or cutter. 2. Two-cell ship — destroyer or torpedo boat. 3. Three-cell ship — cruiser. 4. Four-cell ship — battleship. Each silhouette must visibly represent a different vessel class. The differences should include: - hull profile; - bow and stern shape; - relative height; - superstructure; - bridge position; - turret or equipment placement; - overall visual mass; - length-to-height proportion. The one-cell ship should look like a small, light vessel. The four-cell ship should look substantially larger and heavier, not like an enlarged cutter. ## SVG structure Store the four independently drawn silhouettes in one local SVG sprite: - `ship-1-cutter`; - `ship-2-destroyer`; - `ship-3-cruiser`; - `ship-4-battleship`. Each `` must have its own path data and an appropriate `viewBox`. Using one sprite file is an asset-delivery optimization only. It must not result in the same geometry being reused for every ship class. Do not: - stretch one silhouette to multiple lengths; - create a ship by repeating identical rectangular sections; - use emoji or Unicode ship characters; - use external images or icon libraries; - embed raster images inside the SVG; - add excessive decorative details that become unreadable at mobile sizes. ## Proportional sizing Use a shared visual unit based on one board-cell width. Approximate displayed widths: - cutter: 1 unit; - destroyer: 2 units; - cruiser: 3 units; - battleship: 4 units. Height does not need to be identical between classes. Larger classes may be slightly taller to communicate visual mass. However: - all fleet rows must remain aligned; - different intrinsic heights must not cause layout jumping; - every silhouette must remain recognizable on a mobile screen; - the four-cell battleship must fit within the available width; - SVGs must preserve their aspect ratios; - do not distort silhouettes with independent horizontal and vertical scaling. Use a bounded fleet-display unit independent of the actual board-cell size when necessary. The status list must remain readable without forcing the game grid to shrink. ## Orientation Fleet-status silhouettes should use one consistent orientation, preferably horizontal with the bow facing right. The status list represents ship condition, not the hidden orientation of ships on the game board. Never use the actual opponent ship orientation in this list, because doing so could expose hidden game information. ## Alive state An alive ship should use the normal fleet color with strong contrast against the dark background. Its silhouette should remain visually clean and readable at small sizes. Do not use green as the only indication that a ship is alive. ## Sunk state A sunk ship must have: - a muted or desaturated silhouette; - reduced opacity; - a clearly visible red diagonal cross placed over the entire ship. Draw the cross using two diagonal red strokes: - top-left to bottom-right; - top-right to bottom-left. The cross must: - be bright enough to remain visible against both the ship and background; - use rounded stroke caps; - scale with the complete ship bounding box; - cover the silhouette without completely obscuring its class; - remain inside the fleet-item bounds; - use a consistent apparent stroke thickness across all four ship sizes. Prefer rendering the red cross as a shared lightweight SVG or CSS overlay rather than duplicating it inside every ship symbol. Suggested visual treatment: - red color consistent with the application’s destructive-action palette; - approximately 80–100% cross opacity; - approximately 35–55% ship opacity when sunk; - optional subtle dark backing or outline when required for contrast. The sunk state must not rely only on the red color. Include an accessible textual status and the muted silhouette treatment. ## Fleet rendering Render the classic fleet using the appropriate unique symbol: - 1 × battleship; - 2 × cruisers; - 3 × destroyers; - 4 × cutters. Do not render only one icon per ship class with a numeric counter unless a compact fallback is required for an exceptionally narrow viewport. The preferred presentation shows all ten ships, allowing the player to understand fleet losses at a glance. ## Responsive layout ### Mobile - Arrange ships in compact class-based rows. - Recommended order: battleship, cruisers, destroyers, cutters. - Keep all members of a class together where practical. - Allow rows to wrap deliberately. - Ensure the battleship and its red cross fit without horizontal overflow. - Do not make the status section taller than the game board unless unavoidable. ### Tablet and laptop - The fleet may appear below the corresponding board or in a narrow side panel. - Preserve proportional differences between all vessel classes. - Align equivalent fleet sections consistently when two boards are visible. ## Accessibility labels Every rendered ship instance must have an accessible text equivalent, for example: - “Катер — цел”; - “Эсминец — потоплен”; - “Крейсер — цел”; - “Линкор — потоплен”. Decorative SVG geometry and the red cross should be hidden from assistive technology so the label is not announced twice. ## Information safety The silhouettes describe only fleet composition and public sunk status. Do not expose: - opponent ship coordinates; - opponent ship orientation; - damaged but not yet sunk ship length, unless already public under the established game rules; - untouched opponent ship placement. For the opponent fleet, change a silhouette to the sunk state only after the server authoritatively announces the ship’s destruction and its publicly known length. ## Asset-size expectations Keep all four silhouettes and the shared cross overlay in one maintainable SVG sprite. Target sizes: - preferably below 15 KB uncompressed; - preferably below 5 KB after gzip. These are optimization targets, not reasons to reuse incorrect geometry. After implementation, report: - the source and gzip size of the SVG sprite; - the size contribution of related CSS and JavaScript; - the new LittleFS image size; - screenshots or visual verification of all four ship classes; - alive and sunk examples at mobile and tablet sizes; - confirmation that the red cross remains readable on every silhouette. ### Execution record - Date: 2026-08-30 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS. - Evidence: Added one local SVG sprite with independently drawn cutter, destroyer, cruiser, and battleship symbols. Each board now presents all ten standard fleet vessels in compact class rows. Ship state is derived only from public `4` (sunk) cells in that board's existing role-safe view; no unsunk opponent placement, orientation, or damaged-length data is rendered. Sunk vessels use a shared CSS red, rounded-cap diagonal cross and muted 48% silhouette, while every vessel has an accessible Russian status label. - Measurements: `test/web/test_ship_sprite.js` verifies all four distinct symbols, sprite raw size below 15 KiB, and shared cross styling; it and the existing interaction tests passed 6/6. `make -C test/host run`, `node --check data/app.js`, `git diff --check`, `pio run -e esp32-c6-devkitm-1 -t buildfs`, and `pio run -e esp32-c6-devkitm-1` passed. Sprite size is 1,032 B raw / 419 B gzip; related CSS is 11,721 B raw / 3,130 B gzip; JavaScript is 23,305 B raw / 6,299 B gzip; the generated LittleFS image is 2,031,616 B. Firmware uses 39,588 / 327,680 B RAM (12.1%) and 1,019,306 / 2,097,152 B flash (48.6%). - Issues or deviations: No browser backend is attached in this environment, so live mobile/tablet screenshots and visual cross-readability checks remain physical-browser verification steps. No firmware or filesystem upload was performed. - Next action: Milestone 020 is not started. # Milestone 020 — Amendment: Recognizable Early-20th-Century Warship Silhouettes **Status:** DONE **Depends on:** Milestone 019 Redesign the fleet SVG artwork. The current silhouettes look like generic boats and do not clearly represent early-20th-century warships. Create four independently illustrated side-profile silhouettes inspired by naval vessels from approximately 1905–1918. Do not copy a specific historical ship exactly. Use historically recognizable class characteristics while keeping the artwork simple enough for a compact game interface. ## General art direction All vessels must: - use a consistent horizontal side view; - have the bow facing right; - share a consistent waterline and baseline; - look like steel military vessels, not civilian boats or modern yachts; - use early-20th-century funnels, masts, bridges, and gun arrangements; - avoid modern missile launchers, radar domes, aircraft decks, submarines, and contemporary angular stealth profiles; - remain recognizable at mobile size; - use clean filled shapes rather than thin technical line drawings; - preserve their aspect ratios; - have independently drawn geometry rather than stretched copies of one vessel. Use restrained detail. At small sizes, emphasize the features that distinguish each class rather than adding tiny historically accurate details that disappear when rendered. ## Class 1: Cutter or torpedo boat The one-cell ship should appear small, fast, and lightly armed. Required identifying features: - short, narrow, low hull; - noticeably raised bow; - compact wheelhouse; - one small funnel; - one short mast or signal pole; - one small gun near the bow; - minimal rear superstructure. It must not look like a fishing boat, yacht, tugboat, or miniature battleship. ## Class 2: Destroyer The two-cell ship should have the characteristic long, narrow profile of an early destroyer. Required identifying features: - long, low hull; - raised forecastle; - small bridge near the forward section; - two or three narrow funnels; - a light mast; - small guns positioned toward the bow and stern; - a relatively open deck; - narrow stern. The multiple funnels and long, light hull should make it immediately distinguishable from the cutter and cruiser. ## Class 3: Cruiser The three-cell ship should look heavier and taller than the destroyer without reaching battleship proportions. Required identifying features: - deeper and longer hull; - more substantial bow and stern; - larger central superstructure; - two prominent funnels; - one or two visible masts; - several medium gun positions; - visibly greater freeboard than the destroyer; - balanced profile with significant structure across the middle of the ship. It must not look like a destroyer stretched horizontally. ## Class 4: Battleship or dreadnought The four-cell ship should be the heaviest and most visually dominant vessel. Required identifying features: - long, deep, massive hull; - high freeboard; - heavy main-gun turrets near the bow and stern; - clearly visible large gun barrels; - substantial bridge and central superstructure; - two large funnels; - prominent mast or tripod-style mast; - broad visual mass through the center; - a heavier bow and stern profile than every other class. The battleship must remain identifiable even when shown without its label. ## Visual hierarchy and proportional scale Use proportional width based on fleet class: - cutter: approximately 1 visual unit; - destroyer: approximately 2 units; - cruiser: approximately 3 units; - battleship: approximately 4 units. Height should also communicate class: - cutter: lowest and lightest; - destroyer: long but relatively low; - cruiser: visibly taller and heavier; - battleship: tallest and most massive. Do not force every ship into an identical-height bounding box if that destroys the class differences. Instead, place them inside consistently aligned fleet-item containers with a shared waterline. Recommended SVG construction: - separate `` and `viewBox` for every class; - hull as the primary filled shape; - superstructure as one or two simplified filled shapes; - funnels, masts, and guns with a minimum visual thickness that survives mobile rendering; - optional subtle deck or waterline cutout; - no filters, raster textures, embedded images, or unnecessary path complexity. ## Small-size legibility Test every silhouette at its actual mobile display size, not only at an enlarged development preview. At the final size: - funnels must not merge into one indistinct block; - gun barrels must remain visible; - masts must not disappear; - the gap between major structures must remain readable; - the bow direction must remain obvious; - adjacent ships must not visually merge; - silhouettes must not appear blurry or unevenly scaled. Slightly exaggerate turrets, funnels, and superstructures when necessary for recognition. ## Fleet-list layout Improve the current fleet presentation: - align class names in a consistent label column; - align all ship silhouettes on a shared waterline; - keep predictable spacing between repeated ships; - give the artwork more horizontal room; - avoid making silhouettes so small that their class-specific features disappear; - keep each class on its own row; - preserve the order: 1. “Линкор”; 2. “Крейсер”; 3. “Эсминец”; 4. “Катер”. On narrow mobile screens, reduce spacing before reducing ship artwork below its recognizable minimum size. ## Sunk appearance Keep the requested red-cross treatment. For each sunk ship: - retain the recognizable silhouette underneath; - reduce the ship opacity moderately; - overlay an individual red X across that ship only; - use two clean diagonal strokes; - use rounded stroke caps; - ensure the X follows the complete width and height of the particular vessel; - prevent the X from crossing adjacent ships or the class label. Use a strong naval-warning red with sufficient contrast against both the white silhouette and dark background. The red X must not be the only indication of state. Also apply a muted silhouette treatment and an accessible “потоплен” label. ## Review requirement Before integrating the artwork, render a visual reference sheet containing: - all four classes at enlarged size; - all four classes at their actual mobile size; - alive and sunk versions; - the complete classic fleet in its final mobile layout. Review the reference sheet for recognizability before replacing the production assets. A reviewer should be able to identify the battleship, cruiser, destroyer, and cutter from their silhouettes without reading the labels. ## Acceptance criteria - Every class uses unique SVG geometry. - The ships visually evoke the 1905–1918 naval period. - The battleship has recognizable heavy turrets and a massive hull. - The cruiser has a substantial superstructure and medium armament. - The destroyer has a long, light hull and multiple narrow funnels. - The cutter is visibly small and lightly armed. - All silhouettes remain recognizable at the actual mobile size. - Sunk ships use an individual red X without obscuring adjacent ships. - The complete fleet fits on mobile without horizontal page overflow. - The SVG sprite remains local, compact, and compatible with the existing LittleFS asset pipeline. - The implementation does not expose opponent ship positions or orientations. At completion, provide the reference sheet, screenshots of the mobile and tablet fleet lists, the SVG sprite size before and after gzip, and the resulting LittleFS size delta. ### Execution record - Date: 2026-08-30 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS, pending live-browser visual capture. - Evidence: Replaced the four fleet symbols with independently drawn, filled side profiles inspired by 1905–1918 naval vessels: a low cutter with one funnel and light bow gun, a long destroyer with three narrow funnels, a taller cruiser with two funnels and medium armament, and a broad battleship with a deep hull, turret/gun groups, heavy superstructure, two funnels, and mast. The renderer now assigns every external SVG `` instance its matching class-specific `viewBox` and `preserveAspectRatio`, correcting the former common default viewport that made the silhouettes appear the same. Rows retain the required order and a shared waterline, with class-proportional widths. The red X stays at 96% opacity above only the 48%-opacity sunk silhouette. The review sheet is `docs/ship-silhouette-reference.html` and references the production sprite for enlarged, sunk, and complete compact mobile-fleet variants. - Measurements: `node --test test/web/test_target_interaction.js test/web/test_ship_sprite.js` passed 7/7, including the class-specific viewport assertion; `make -C test/host run`, `node --check data/app.js`, `git diff --check`, `pio run -e esp32-c6-devkitm-1 -t buildfs`, and `pio run -e esp32-c6-devkitm-1` passed. The sprite changed from 1,032 B to 975 B raw and from 419 B to 496 B gzip; it remains below the 15 KiB / 5 KiB targets. The generated LittleFS image remains the fixed 2,031,616-B partition image (0-B image-size delta). Firmware uses 39,588 / 327,680 B RAM (12.1%) and 1,019,248 / 2,097,152 B flash (48.6%). Static assets now use `no-cache`, so an updated filesystem upload is revalidated instead of retaining the prior sprite/app bundle. - Issues or deviations: This environment has neither a Chromium executable nor an attached browser backend, so screenshots and live mobile/tablet inspection could not be captured. No firmware or filesystem upload was performed. - Next action: No subsequent milestone was started. --- ## Milestone 021 — Define and implement safe leave, profile reset, and full game reset semantics **Status:** `DONE` **Depends on:** Milestone 020 ### Objective Add bounded, authoritative recovery operations that let a user leave, clear only their local profile, or reset the complete in-memory game when the party becomes unusable. This milestone establishes the behavior and server contract before adding the interface controls. The operations must be safe in every phase from the lobby through the result and rematch screens. ### Reset scopes Implement three deliberately different operations. 1. **Leave the game** - Invalidate and release only the requesting session. - Clear the session token in that browser and return it to the registration screen. - Preserve the locally remembered display name and selected hero so the same user can rejoin quickly. - A spectator leaving must not change the match. - If an active player leaves during a human-vs-human match, abort the current match safely, return the remaining valid player to the lobby, release the departed seat, and allow a replacement player to join. - Leaving a bot match must abort that match and release the human player's seat. 2. **Reset my profile** - Perform the same server-side session release as leaving. - Clear the requesting browser's session token, display name, selected hero, and other user-specific browser preferences. - Do not erase another user's local data or unrelated device-wide game state. - Return the browser to the initial registration screen with an empty profile. 3. **Reset the entire game** - Be available only to a currently authenticated player, never to a spectator or anonymous client. - Atomically clear both player sessions, spectator sessions, names, selected mode, boards, turn, winner, rematch approvals, bot state, match statistics, cumulative in-memory statistics, queued commands, and active WebSocket ownership. - Create a fresh lobby/game generation so stale commands from the previous party cannot mutate the new state. - Notify every connected client that the game was reset, invalidate every browser session, and return all clients to the initial registration screen. - Work from `LOBBY`, `PREPARING`, `IN_PROGRESS`, `FINISHED`, and `REMATCH_WAIT`. The full game reset is an application recovery operation. It must **not** erase Wi-Fi credentials, LittleFS assets, firmware, flash partitions, board configuration, or other device settings. ### Server and lifecycle work - Define stable routes or commands for session leave and full game reset using the existing validated application/command-queue path. - Require a valid bounded session token for every state-changing recovery request. - Require the current `gameId` or reset generation where applicable so delayed requests are rejected as stale. - Make repeated leave and reset requests idempotent: retries must not corrupt state, double-increment counters, or release a newly created session. - Add an explicit machine-readable reset reason such as `session_left`, `profile_reset`, or `game_reset` instead of forcing the browser to infer recovery from a generic authorization error. - Broadcast a minimal public reset notification before invalidating or closing affected WebSocket connections. Do not include former tokens, hidden boards, or private session data. - Ensure HTTP polling with an invalidated token receives the same stable recovery reason. - Clear or invalidate queued commands from the previous generation before a new player can act. - Do not hold the application mutex while closing sockets or sending network responses; take a bounded reset snapshot and complete transport cleanup outside the critical section. - Keep all request bodies, responses, queues, and reset notifications within the existing resource budgets. ### Authorization and abuse resistance - Anonymous clients and spectators must receive a stable forbidden response for full reset. - Either authenticated player may use full reset as an emergency recovery action on the trusted home network. - Confirmation is a browser responsibility added in Milestone 022; the server must still validate role, token, phase/generation, body bounds, and command freshness independently. - A stale page from a previous party must not be able to reset or leave a newly created party using an old token or `gameId`. - Reset endpoints must not expose whether a guessed token belongs to a particular named player. ### Tests Add host tests covering at least: 1. Spectator leave in every phase without match mutation. 2. Player leave from the lobby and release of the correct seat. 3. Player leave during human-vs-human play and safe return of the remaining player to the lobby. 4. Player leave during bot play and safe match abort. 5. Full reset from every supported phase. 6. Clearing of sessions, names, mode, boards, bot state, rematch state, match statistics, and cumulative statistics. 7. Reset-generation change and rejection of stale queued commands. 8. Idempotent retry of leave and full reset. 9. Rejection of anonymous, spectator, malformed, oversized, and stale reset requests. 10. Role-safe reset notification and HTTP recovery response. 11. Recovery while WebSocket clients and HTTP pollers are connected. 12. No mutation when the command queue is full or the reset request is rejected. ### Acceptance criteria - The three operations have distinct, documented scopes and cannot be confused by the client. - Leave and profile reset affect only the requesting browser/session, subject to the documented active-player match-abort rule. - Full reset returns the complete application to a clean pre-registration state without rebooting the ESP32. - Every connected client learns about a full reset and discards its invalid session. - Stale tokens, game IDs, and queued commands cannot affect the fresh party. - Spectators and anonymous clients cannot reset the complete game. - No Wi-Fi, filesystem, firmware, or hardware configuration is erased. - All host suites pass within the established memory and payload budgets. ### Completion action When all criteria pass, set Milestone 021 to `DONE`, append its execution record, and change Milestone 022 from `BLOCKED` to `READY`. Do not start Milestone 022 in the same task unless explicitly requested. ### Execution record - Date: 2026-08-30 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS. - Evidence: Added bounded `POST /api/session/leave`, `POST /api/session/profile-reset`, and player-authorized `POST /api/game/reset` commands. Leave/profile reset release only the authenticated session; an active player departure safely creates a new lobby game identity while preserving other valid sessions. Full reset cancels bot work, clears all sessions, boards, mode, bot state, rematch approvals, match and cumulative statistics, and command queue, then creates a fresh game identity and recovery generation. Invalidated HTTP state polling receives stable `SESSION_INVALIDATED` recovery metadata. Authenticated WebSocket owners receive a minimal `{type:"reset"}` notification without board or token data and are invalidated before any role-safe state can be serialized. - Measurements: Added lifecycle coverage for spectator/player leave and full reset, HTTP coverage for idempotent leave/reset and invalidated polling, and WebSocket coverage for minimal reset notification/invalidation. `make -C test/host run` passed all ten suites. `node --check data/app.js` and the 12 browser/unit tests passed. `pio run -e esp32-c6-devkitm-1 -t buildfs`, `pio run -e esp32-c6-devkitm-1`, and `git diff --check` passed. Firmware uses 39,604 / 327,680 B RAM (12.1%) and 1,020,806 / 2,097,152 B flash (48.7%). - Issues or deviations: No firmware upload or physical multi-client recovery exercise was performed. Milestone 022 owns the browser controls and confirmations; none were added here. - Next action: Milestone 022 is READY. Do not start it unless explicitly requested. --- ## Milestone 022 — Add the always-available recovery menu and child-safe confirmations **Status:** `DONE` **Depends on:** Milestone 021 ### Objective Add one clearly recognizable recovery button that is available from the lobby through the complete game lifecycle and opens actions for leaving, resetting the current profile, or resetting the entire game. The design must prevent accidental destructive taps while remaining understandable to young children who may not read confidently. ### Button availability and placement - Show the recovery/menu button on `LOBBY`, `PREPARING`, `IN_PROGRESS`, `FINISHED`, and `REMATCH_WAIT` screens. - Keep it available on the HTTP-recovery/reconnecting screen so a user can at least clear their local session when the network path is unhealthy. - Do not show it on the initial registration screen, because there is no active session to leave. - Place it consistently in the header or another fixed safe area where it does not cover the board, score, field tabs, Fire control, or browser safe-area insets. - Use a local SVG symbol such as a door, lifebuoy, or reset arrow plus a concise text/accessibility label. Do not rely on color or text alone. - Keep a minimum touch target of 44 × 44 CSS pixels. ### Recovery menu Opening the button must present three visibly distinct choices: 1. **Выйти из игры** — leave while keeping the remembered name/hero. 2. **Сбросить мой профиль** — leave and clear this browser's name, hero, token, and user preferences. 3. **Сбросить всю игру** — clear the complete in-memory party for every connected user. - Show the full-game option only to authenticated players. - Never show or enable the full-game option for spectators or anonymous/recovery-only clients. - Use a modal or bottom sheet with a clear close control, focus trapping, Escape support, and restoration of focus to the menu button. - The default focused action must be Cancel, not a destructive action. - Prevent background board interaction while the menu or confirmation is open. ### Confirmation behavior - Every leave or reset action requires a separate confirmation step. - The confirmation must state the scope visually and in text: - one-person icon for local leave/profile reset; - all-players icon for full game reset. - Use two clearly separated choices: Cancel and the requested action. - Full game reset requires a stronger final gesture: press and hold the destructive button for approximately two seconds while a visible progress ring/bar fills. - Releasing early cancels the hold without sending a request. - Keyboard and assistive-technology users must have an equivalent explicit confirmation path. - Do not require typing a phrase; the recovery flow must remain usable by children who cannot read or type confidently. - Disable the action after submission and show bounded progress so double taps cannot enqueue duplicate requests. - If the request fails, keep the user in a recoverable state and offer Retry or local-only profile clearing where safe. ### Browser behavior - On successful leave, remove only the session token and render the registration screen with the remembered name/hero. - On successful profile reset, remove all Battleship user keys from `localStorage` and render an empty registration screen. - On a full reset notification, every client must remove all session/profile keys, stop WebSocket retries and polling for the invalid session, clear transient UI/game state, and render the empty registration screen. - Clear selected targets, pending Fire state, animation timers, combo counters, notices, cumulative-statistics cache, active-board selection, and retry timers during local or global reset. - Do not briefly render hidden boards or stale names while transitioning to registration. - If the browser is offline or the server is unreachable, allow local profile clearing only after confirmation and explain that the server seat may remain occupied until its normal timeout or a later successful leave. ### Child-friendly visual requirements - Distinguish the three choices using shape, icon, spacing, and label, not red color alone. - Keep the most destructive action visually separated from the two local actions. - Use calm wording; do not make failure or reset feel like punishment. - Avoid flashing the destructive confirmation red repeatedly. - Respect `prefers-reduced-motion` for the hold progress and transition effects. - Provide complete Russian accessible labels and live status announcements. ### Browser tests Add tests covering at least: 1. Recovery button visibility in every supported phase and its absence on registration. 2. Role-specific visibility of the full-game option. 3. Cancel from every confirmation without API submission or local-data loss. 4. Successful leave while preserving remembered name/hero. 5. Successful profile reset clearing every Battleship browser key. 6. Two-second hold requirement for full reset and cancellation on early release. 7. Request deduplication during repeated taps, pointer events, and keyboard activation. 8. Reset notification handling through WebSocket and HTTP fallback. 9. Clearing timers, selected targets, combo state, cached statistics, and stale UI after reset. 10. Offline local-profile recovery and its warning. 11. Focus trapping, Escape behavior, focus restoration, and accessible announcements. 12. Mobile, tablet portrait, tablet landscape, and laptop layout without board overlap. ### Acceptance criteria - A user can reach the recovery menu from the lobby through the result/rematch flow without scrolling past the game board. - No leave or reset occurs from a single accidental tap on the menu button. - Full reset requires an authenticated player and the stronger confirmation gesture. - Local leave, local profile reset, and full game reset produce their documented distinct outcomes. - All affected clients return to the correct registration or lobby state without stale data. - The flow works with touch, mouse, keyboard, assistive technology, WebSocket, and HTTP fallback. - The controls remain legible and unobtrusive at the target phone and tablet sizes. - JavaScript, CSS, SVG, gzip, and LittleFS budgets remain acceptable. ### Completion action When all criteria pass, set Milestone 022 to `DONE`, append its execution record, and change Milestone 023 from `BLOCKED` to `READY`. Do not start Milestone 023 in the same task unless explicitly requested. ### Execution record - Date: 2026-08-30 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS, pending physical-device layout confirmation. - Evidence: Added a 44-pixel lifebuoy-labelled recovery control outside the gameplay surfaces. It is hidden at registration and visible in lobby, game, result, reconnecting, and error states with an active session. Its modal has focus containment, Escape/close restoration, role-gated full reset, separate icon/text scopes, and cancellation as the initial confirmation focus. Leave and profile reset require a second explicit confirmation; full reset requires a two-second pointer hold with visible progress, while keyboard activation supplies the accessible equivalent. Successful leave preserves the name; profile/full reset clears all `battleship.*` browser data, selection, cached state/statistics, effects, timers, polling, and socket retry state before showing registration. Offline profile reset is explicitly local-only and warns about the retained server seat. - Measurements: `node --test test/web/test_target_interaction.js test/web/test_ship_sprite.js test/web/test_recovery_ui.js` passed 15/15, including recovery visibility, role gate, scope clearing, hold/cancel, keyboard, and Escape assertions. `make -C test/host run` passed all ten host suites. `node --check data/app.js`, `git diff --check`, `pio run -e esp32-c6-devkitm-1 -t buildfs`, and `pio run -e esp32-c6-devkitm-1` passed. Browser assets are 43,011 B / 10,622 B gzip JavaScript, 23,570 B / 5,589 B gzip CSS, and 4,466 B / 1,633 B gzip SVG. The LittleFS image is the fixed 2,031,616-B partition image. Firmware remains 39,604 / 327,680 B RAM (12.1%) and 1,020,806 / 2,097,152 B flash (48.7%). - Issues or deviations: No attached browser backend is available for live phone/tablet screenshots, and no firmware/filesystem upload or physical recovery exercise was performed. - Next action: Milestone 023 is READY. Do not start it unless explicitly requested. --- ## Milestone 023 — Verify multi-client reset recovery and document the emergency workflow **Status:** `DONE` **Depends on:** Milestone 022 ### Objective Prove that leave, profile reset, and full game reset recover real phones/tablets and the ESP32 consistently under normal play, reconnect, HTTP fallback, and partially broken client conditions. ### Integration scenarios Run and record at least the following scenarios with two player clients and one spectator where applicable: 1. Player leaves from the lobby; the correct seat becomes available and the other user remains valid. 2. Spectator leaves during play; the match and both player sessions remain unchanged. 3. Player leaves during human-vs-human play; the match aborts safely and the remaining player returns to the lobby. 4. Human leaves a bot match; the bot stops and the game returns to a clean lobby. 5. User resets only their profile and can register again under a new name/hero. 6. Player performs full reset from the lobby. 7. Player performs full reset during active play while another client has a selected target. 8. Player performs full reset from the result and rematch screens. 9. Full reset while one client is on WebSocket and another is using HTTP polling. 10. Full reset while one browser is temporarily disconnected and reconnects with an invalidated token. 11. Repeated confirmation taps and retried HTTP requests do not execute more than one reset. 12. A stale pre-reset tab cannot alter the newly registered party. 13. ESP32 remains responsive after at least 50 mixed leave/profile-reset/full-reset cycles. ### Verification and measurements - Run all host lifecycle, API, synchronization, integration, robustness, and browser tests. - Build firmware and LittleFS from a clean state. - Record firmware flash/RAM use, LittleFS usage, reset payload sizes, minimum free heap, largest free block, and connection count before and after the reset-cycle run. - Confirm that free heap has no persistent downward trend across the repeated cycle test. - Confirm there is no watchdog reset, socket leak, stuck seat, stale name, stale board, duplicate command, or orphaned bot action. - Verify the recovery menu at target mobile and tablet sizes, including safe areas and landscape orientation. - Verify that the full-reset hold interaction cannot accidentally fire a board cell underneath it. - Confirm no reset operation erases Wi-Fi credentials or requires a device reboot. ### Documentation Document the user-facing emergency workflow in concise Russian: - how to leave while keeping a profile; - how to clear only the current browser profile; - how a player resets the entire party; - what happens to the other connected users; - what to do when the browser is offline; - explicit assurance that Wi-Fi and firmware settings are not erased. Also document the API routes/commands, authorization, stable reset reason codes, idempotency behavior, and reset-generation rules for future maintainers. ### Acceptance criteria - Every integration scenario passes on the target ESP32 with objective logs or screenshots. - All clients converge on the correct post-reset state through both WebSocket and HTTP polling. - No stale session or queued command survives a full reset. - Fifty mixed reset cycles complete without a hang, reboot, material memory decline, or unavailable session slot. - Recovery instructions are understandable without developer tools or a serial console. - The final build remains within the established firmware, heap, payload, and LittleFS budgets. ### Completion action When all criteria pass, set Milestone 023 to `DONE` and append its execution record. Add later post-MVP work only after Milestone 023 without renumbering existing milestones. ### Execution record - Date: 2026-08-31 - Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. - Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. - Result: PASS. - Evidence: Added `docs/RECOVERY_GUIDE.md`, a concise Russian emergency workflow covering leave, local-only profile clearing while offline, player-only full reset, effects on other users, and explicit non-erasure of Wi-Fi/firmware settings. Added a deterministic fifty-cycle mixed leave/bot-abort/full-reset lifecycle regression. All ten host suites passed, including the existing API, synchronization, human integration, and robustness coverage. All 17 web tests and `node --check data/app.js` passed. Firmware and LittleFS builds passed; firmware uses 39,604 / 327,680 B RAM (12.1%) and 1,020,920 / 2,097,152 B flash (48.7%); the fixed LittleFS partition image is 2,031,616 B. - Physical verification: The user confirmed every required manual target-device scenario passed, including the two-player-plus-spectator flows, WebSocket and HTTP fallback convergence, temporarily disconnected browser recovery, recovery-menu layouts and hold safety, stale-tab rejection, and fifty mixed recovery cycles without a hang, reboot, memory decline, stuck seat, socket leak, stale state, duplicate command, or orphaned bot action. Wi-Fi credentials and firmware settings remained unchanged. - Next action: Milestone 024 remains blocked and was not started. --- ## Milestone 024 — Build a bounded browser Web Audio engine and sound controls **Status:** `DONE` **Depends on:** Milestone 023 ### Objective Add a compact, framework-free Web Audio engine that synthesizes all game sounds in the phone or tablet browser without storing prerecorded audio files on the ESP32. The engine must be explicitly activated by the user, remain optional, consume bounded browser resources, and fail silently when Web Audio is unavailable. This milestone builds only the reusable audio foundation and settings; the large game-reaction library is added in Milestone 025. ### Architecture - Implement the engine as a small maintainable browser module rather than mixing oscillator code throughout `app.js`. - Use the Web Audio API only after a user gesture has enabled sound. - Generate sound from bounded combinations of: - oscillators with sine, triangle, square, and sawtooth waveforms; - one reusable in-memory noise buffer; - gain envelopes; - pitch ramps; - low-pass, high-pass, and band-pass filters; - short delays only when they materially improve a cue; - a master gain and dynamics-compressor/limiter stage. - Do not add MP3, AAC, OGG, WAV, sampled meme, CDN, or internet assets. - Do not synthesize audio on the ESP32. The ESP32 only serves the browser code; the client device produces the sound. - Do not require a server protocol change solely for sound. Derive cues from the same authoritative, role-safe state changes already used for visual feedback. ### Audio-context lifecycle - Create or resume `AudioContext` only after an explicit sound-toggle or other clearly associated user action. - Handle browsers that begin with a suspended context. - Reuse one context instead of creating a new context for every effect. - Suspend or stop scheduling sound when the page is hidden for a sustained period, the session is reset, the user leaves, or sound is muted. - Resume safely after the page becomes active and the browser permits it. - Disconnect and release every temporary node after its envelope completes. - Cancel scheduled voices and reset the engine during profile reset, full game reset, navigation back to registration, or browser teardown. - Degrade to a silent no-op implementation when Web Audio is unsupported or initialization fails. ### Resource limits - Define a hard maximum simultaneous voice count, initially no more than eight voices. - Reuse a single bounded noise buffer instead of allocating noise per playback. - Limit ordinary cues to approximately 100–900 ms and major victory/sinking cues to a maximum of approximately 2.5 seconds. - Prevent unbounded timers, event listeners, audio nodes, buffers, and promise chains. - Drop or replace low-priority UI cues when the voice limit is reached; never delay an important hit, sinking, combo, or reset cue behind a long queue. - Prevent duplicate state snapshots and HTTP/WebSocket retransmissions from replaying the same sound. - Record the added raw/gzip JavaScript size and resulting LittleFS usage. ### Child-safe sound controls - Add a clear `Sound on / Sound off` control using a local SVG speaker icon and a complete Russian accessible label. - Make the sound control available from the lobby through game, result, rematch, and recovery screens. - Preserve the choice in a bounded `battleship.*` browser preference. - Default to sound off until the user explicitly enables it for the first time. - Provide three simple volume levels: quiet, normal, and loud, with normal as the recommended maximum for children. - Cap master output so overlapping voices do not clip or become startling. - Avoid sustained very high frequencies, sudden full-scale gain jumps, and long low-frequency rumbles. - Keep essential game information visible; sound must enhance feedback, never become the only indication of turn, hit, miss, sinking, victory, error, or reset. - Provide an immediate mute action that stops currently scheduled nonessential audio. - Keep sound preferences independent from `prefers-reduced-motion`; also provide a separate reduced-intensity sound option for users who are sensitive to strong effects. ### Originality and tone - Create original synthesized cues only. - Do not copy recognizable sounds from YouTube, Roblox, games, films, social-media memes, or commercial sound libraries. - The interaction style may use the short, energetic rhythm familiar to modern children's games, but the melodies, timing, and synthesis presets must be original to this project. - Avoid frightening alarms, realistic gunfire, screams, mocking failure sounds, or punishment-like audio. ### Tests Add browser/unit tests with a fake or instrumented audio context covering at least: 1. No context creation before explicit enablement. 2. Context creation/resume after a valid user gesture. 3. Persistent mute, volume, and reduced-intensity preferences. 4. Master gain caps for every volume level. 5. Bounded voice allocation and priority replacement. 6. Noise-buffer reuse. 7. Node disconnection and timer cleanup after playback. 8. Duplicate event/version suppression. 9. Cleanup on leave, profile reset, full reset, and registration transition. 10. Page hide/resume behavior. 11. Graceful no-op fallback when Web Audio is unavailable or throws. 12. No effect on existing game state, transport, role, or recovery behavior. ### Acceptance criteria - Sound can be explicitly enabled, muted immediately, and adjusted without reloading the game. - The engine plays a deterministic test cue through a bounded reusable graph. - No audio context or sound begins before user activation. - The implementation introduces no prerecorded audio asset and no internet dependency. - Simultaneous voices, node lifetimes, timers, and buffers remain within documented limits. - The browser remains fully playable with sound disabled or unsupported. - All existing host and browser suites continue to pass. - Firmware, gzip, and LittleFS budgets remain acceptable. ### Completion action When all criteria pass, set Milestone 024 to `DONE`, append its execution record, and change Milestone 025 from `BLOCKED` to `READY`. Do not start Milestone 025 in the same task unless explicitly requested. ### Execution record - Date: 2026-08-31 - Result: PASS. - Evidence: Added a framework-free `web_audio.js` module with deferred user-gesture activation, one reusable noise buffer, an eight-voice limit with priority replacement, capped master gain and compressor, duplicate-version suppression, page-visibility handling, cancellation/reset cleanup, and a silent unsupported-browser fallback. Added Russian sound on/off, volume, reduced-intensity, and recovery-screen mute controls without changing game transport or role behavior. The ESP32 static-file allowlist now serves `/web_audio.js`; the user confirmed HTTP 200 delivery after the firmware and LittleFS update. - Verification: All ten host suites passed. All six browser suites, including fake/instrumented AudioContext coverage, passed; `node --check data/app.js` and `node --check data/web_audio.js` passed; `git diff --check` passed. Firmware and LittleFS builds passed. Firmware uses 39,604 / 327,680 B RAM (12.1%) and 1,020,956 / 2,097,152 B flash (48.7%). `web_audio.js` is 7,863 B raw and 2,072 B gzip; all compressed browser assets total 24,947 B, within the 250,000 B LittleFS budget. - Next action: Milestone 025 is ready. Do not start Milestone 026 as part of this task. --- ## Milestone 025 — Create a large randomized library of playful game sounds and audio combos **Status:** `READY` **Depends on:** Milestone 024 ### Objective Create a broad, original, randomized set of short synthesized reactions that keeps children engaged without becoming repetitive, chaotic, exhausting, or distracting from gameplay. Audio should reinforce the existing randomized visual/meme reactions. Each event family needs multiple clearly related but nonidentical variants, and the same variant must not play twice consecutively for the same event. ### Event families and minimum variety Implement at least the following synthesized cue families: 1. **Target selection** — at least 4 quiet variants: - sonar tick; - short radar ping; - soft bubble click; - tiny aiming chirp. 2. **Shot launch** — at least 8 energetic variants: - compact cannon pop; - rising charge followed by a burst; - fast whoosh; - comic `pew`-style pitch drop; - double pop; - low naval thump; - short sparkling launch; - rapid arcade-like sweep. 3. **Miss** — at least 10 light and funny variants: - several splash shapes; - bubbles rising; - soft `bloop`; - water drop; - descending whistle into water; - wave wash; - tiny fish-like chirp; - comic empty echo. 4. **Hit** — at least 10 satisfying variants: - filtered impact; - short explosion; - metallic thud; - impact plus bright confirmation ping; - two-stage `boom-ding`; - bass pop; - crunchy noise burst; - short critical-hit sparkle; - rising confirmation tone; - compact layered impact. 5. **Ship sunk** — at least 8 multi-stage variants: - heavy impact followed by bubbles; - descending hull tone and splash; - short explosion sequence; - victory sparkle over a low splash; - collapsing pitch sweep; - three-hit dramatic cadence; - deep thump with bright finish; - comic large `bloop` followed by stars. 6. **Incoming miss / dodge** — at least 6 encouraging variants that sound different from the local player's miss. 7. **Incoming hit** — at least 6 clear but nonfrightening variants using lower, softer timbres than the player's successful hit. 8. **Turn ready** — at least 6 brief attention cues that invite action without sounding like an alarm. 9. **Waiting / opponent turn** — at least 4 optional low-priority ambient ticks, disabled in reduced-intensity mode and rate-limited so they never loop continuously. 10. **Game start** — at least 6 short original launch stingers. 11. **Victory** — at least 8 original fanfare variants with different rhythms and instrument-like oscillator combinations. 12. **Defeat / rematch invitation** — at least 5 friendly, encouraging cues that do not mock the child. 13. **Recovery actions** — separate calm cues for menu open, cancel, leave, profile reset, and full game reset. Destructive actions must not sound rewarding or resemble a gameplay explosion. ### Randomization rules - Use bounded variation of preset selection, pitch, envelope duration, filter cutoff, pan when supported, and particle-like voice timing. - Keep pitch and duration variation within ranges that preserve the meaning of each event. - Never allow two consecutive uses of an event family to select the same complete preset. - Avoid a short repeating pattern such as alternating between only two variants when more are available. - Seed selection from browser randomness; do not use game-board randomness or reveal any hidden server state. - Do not let random choices affect authoritative game logic, timing, network messages, or visual state. - Store only the last few selected preset identifiers needed for anti-repetition; do not build an unbounded history. ### Combo and streak audio - Track only the local presentation streak already derived from authoritative hit/sunk events. - Add escalating original combo layers: - combo ×2: an extra bright confirmation note; - mega-combo ×3: a three-note rising motif; - ultra-combo ×4 and above: a bounded fanfare layer with stronger particles/visual synchronization. - Increase excitement through rhythm, harmony, and layering rather than unlimited volume. - Reset the audio combo exactly when the visual combo resets. - Ensure a sinking or victory cue remains recognizable when combined with a streak layer. - Cap combo layering inside the global voice and gain limits. ### Synchronization with visual reactions - Select visual text/animation and audio from compatible event families without requiring an exact one-to-one preset pairing. - Begin launch sound immediately after an accepted local Fire action. - Play hit, miss, sinking, dodge, damage, turn, and result sounds only after the authoritative state transition is accepted. - Spectators receive public hit/miss/sinking cues without player-private orientation or target information. - Do not replay sounds when the same state version arrives through both WebSocket and HTTP polling. - When several changes arrive in one snapshot, apply explicit priority: victory, sinking, hit/damage, miss/dodge, turn, then low-priority UI sound. - Rate-limit bursts caused by reconnect snapshots so a returning client receives one summary cue instead of a backlog of historical sounds. ### Attention without overload - Keep ordinary gameplay cues short enough that the next action is never delayed. - Alternate timbre, rhythm, and spatial impression, not only pitch. - Reserve the richest sounds for sinking, high combos, and victory so rewards retain meaning. - Keep selection and menu sounds much quieter than gameplay results. - Do not add continuous background music in this milestone. - Do not play repeated waiting sounds more often than a documented safe interval. - When many events occur rapidly, prioritize the newest important event and suppress obsolete low-priority audio. - Reduced-intensity mode must remove waiting ticks, simplify combos, reduce bass/noise layers, and shorten victory/sinking cues. ### Tests Add deterministic tests using injected randomness and a fake audio clock covering at least: 1. Minimum preset count for every event family. 2. No immediate preset repetition. 3. Bounded anti-repeat history. 4. Pitch, duration, filter, pan, gain, and voice-count limits across many randomized selections. 5. Correct priority when one state snapshot contains multiple changes. 6. No duplicate sound for repeated versions or WebSocket/HTTP duplicates. 7. Correct local-player, opponent, and spectator sound perspective. 8. Shot sound only for an accepted local action. 9. Combo ×2, mega-combo ×3, ultra-combo ×4+, and reset behavior. 10. Reduced-intensity substitutions and suppression. 11. Reconnect summary cue without historical sound storms. 12. Recovery cues that cannot be confused with shot, hit, or victory. 13. Thousands of randomized cue selections without an exception, leaked voice, or limit violation. ### Acceptance criteria - Every required event family meets or exceeds its minimum variant count. - Repeated shots and outcomes feel varied in preset, rhythm, timbre, and motion synchronization. - Important events remain immediately distinguishable without reading text. - Combos become progressively more exciting without exceeding gain or voice limits. - Failure and defeat sounds remain playful and encouraging. - No copyrighted or recognizable third-party sound is included or imitated. - Audio never reveals hidden board information or changes gameplay timing. - Sound-disabled and reduced-intensity modes remain complete and usable. - Automated anti-repetition, range, priority, and stress tests pass. ### Completion action When all criteria pass, set Milestone 025 to `DONE`, append its execution record, and change Milestone 026 from `BLOCKED` to `READY`. Do not start Milestone 026 in the same task unless explicitly requested. --- ## Milestone 026 — Validate Web Audio engagement, compatibility, and long-run stability on real devices **Status:** `BLOCKED` **Depends on:** Milestone 025 ### Objective Verify that the synthesized sound system is entertaining, understandable, compatible with the target phones/tablets, and stable during long games without harming network responsiveness or browser performance. ### Device matrix Test at minimum: - one current Android phone in a Chromium-based browser; - one Android tablet or second Android device; - one iPhone or iPad using Safari when available; - one laptop browser for keyboard and accessibility checks; - sound on, muted, quiet, normal, loud, and reduced-intensity modes; - portrait and landscape orientation; - WebSocket operation and HTTP polling fallback. ### Functional scenarios Verify on real devices: 1. First-time sound enablement from the lobby after an explicit tap. 2. Persistent sound preference after reload and normal session resume. 3. Immediate mute during a currently playing cue. 4. Target, shot, miss, hit, sinking, incoming result, turn, combo, victory, defeat, and recovery sounds. 5. At least 30 consecutive shots without an immediate same-family preset repeat. 6. Combo ×2, mega-combo ×3, and ultra-combo ×4+ synchronization with the visual badges. 7. Page background/foreground transition without broken or queued audio. 8. Screen lock/unlock and browser-tab switching when supported. 9. WebSocket loss followed by HTTP fallback and WebSocket recovery without duplicate sounds. 10. Leave, profile reset, and full game reset while a sound is active. 11. Spectator audio without hidden-information leakage. 12. Graceful silent operation when Web Audio is blocked or unavailable. ### Child-engagement review Run a short supervised usability review with age-appropriate participants or adult proxies acting from the child's perspective. Record whether: - shot, miss, hit, sinking, and victory can be distinguished without reading the message; - repeated play still feels varied after a complete match; - combo sounds feel more rewarding than ordinary hits; - any cue is frightening, painfully sharp, too bass-heavy, mocking, or excessively loud; - waiting sounds become annoying; - mute and volume controls can be found quickly; - the sounds help attention without causing the child to tap randomly or miss the actual turn cue. Do not record children, collect identifying data, or conduct unsupervised testing as part of this project. Record only anonymous design observations supplied by a responsible adult. ### Performance and stability - Complete at least 20 representative games with sound enabled. - Run at least one 60-minute browser session containing repeated shots, combos, resets, reconnects, and tab visibility changes. - Record browser console errors, approximate active voice count, peak scheduled-node count, audio-context state transitions, and any delayed or dropped important cue. - Confirm no persistent growth in active nodes, timers, event listeners, buffers, or browser memory attributable to audio. - Confirm ESP32 free heap, HTTP latency, WebSocket delivery, and connected-client capacity remain within the established budgets. - Measure final raw/gzip sizes for the audio engine and preset library and the resulting LittleFS usage. - Confirm the firmware image does not materially grow unless a documented server change was required. ### Accessibility and safety review - Verify every event remains understandable visually with sound muted. - Verify screen-reader labels for sound controls and state changes do not duplicate or fight with audio cues. - Verify reduced-intensity mode meaningfully reduces layers and loudness. - Verify the master limiter prevents clipping during the largest combo/victory stack. - Verify no cue exceeds the documented duration, gain, voice-count, and repetition limits. - Verify no sound starts unexpectedly on registration, reload, or background resume. ### Acceptance criteria - Required cues play reliably on the tested phone and tablet browsers after explicit activation. - A complete match remains varied and understandable without copied audio assets. - No immediate same-event preset repetition is observed in the recorded sequence. - Users can mute audio immediately and retain their preference. - Reduced-intensity mode is noticeably calmer while preserving event meaning. - Twenty games and the 60-minute session complete without audio-node leaks, browser errors, gameplay stalls, ESP32 instability, or transport regressions. - Audio remains synchronized with authoritative state through WebSocket, HTTP fallback, reconnect, and reset flows. - The final browser assets and LittleFS image remain inside the established budgets. ### Completion action When all criteria pass, set Milestone 026 to `DONE` and append its execution record. Add later milestones only after Milestone 026 without renumbering existing milestones.