feat: lock production decisions and resource budgets

This commit is contained in:
2026-08-28 22:21:36 +03:00
parent 63e33510b3
commit 4ff1718307
6 changed files with 680 additions and 15 deletions
+450 -1
View File
@@ -268,7 +268,7 @@ If all criteria pass, set this milestone to `DONE`, append its execution record,
## Milestone 004 — Prove MVP capacity and make the Go/No-Go decision
**Status:** `IN PROGRESS`
**Status:** `DONE`
**Depends on:** Milestone 003
### Objective
@@ -325,3 +325,452 @@ Do not adjust a threshold after seeing the result unless the execution record co
### 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 000004.
### 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:** `READY`
**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`.
---
## Milestone 007 — Implement and exhaustively test the game domain core
**Status:** `BLOCKED`
**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`.
---
## Milestone 008 — Implement the ESP32 opponent
**Status:** `BLOCKED`
**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`.
---
## Milestone 009 — Implement sessions, lobby, roles, and rematch lifecycle
**Status:** `BLOCKED`
**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`.
---
## Milestone 010 — Implement safe state presentation and bounded serialization
**Status:** `BLOCKED`
**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`.
---
## Milestone 011 — Implement the production HTTP API
**Status:** `BLOCKED`
**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`.
---
## Milestone 012 — Implement WebSocket synchronization and HTTP recovery
**Status:** `BLOCKED`
**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`.
---
## Milestone 013 — Build the Russian responsive web interface
**Status:** `BLOCKED`
**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`.
---
## Milestone 014 — Complete human-vs-human gameplay end to end
**Status:** `BLOCKED`
**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`.
---
## Milestone 015 — Complete human-vs-ESP32 gameplay and cumulative statistics
**Status:** `BLOCKED`
**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`.
---
## Milestone 016 — Harden errors, recovery, and resource usage
**Status:** `BLOCKED`
**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`.
---
## Milestone 017 — Execute final MVP acceptance and create the release baseline
**Status:** `BLOCKED`
**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.