Files
maze32/PLANS.md
T

2575 lines
145 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# maze32 Implementation Plan
## Plan rules
1. Milestones may be planned and added in batches.
2. Milestone numbering starts at 1 and increases without gaps. Headings use the
zero-padded form `Milestone 001`, `Milestone 002`, and so on.
3. Milestones are executed strictly in ascending numerical order. Work on a
milestone may begin only after every preceding milestone is `COMPLETE`.
4. At most one milestone may have status `IN_PROGRESS`.
5. A newly discovered milestone is appended to the end of the plan. Existing
milestone numbers and execution order are not changed without explicit
project-owner approval.
6. Every milestone contains an objective, scope, deliverables, acceptance
criteria, evidence, and status.
7. Allowed statuses are `NOT_STARTED`, `IN_PROGRESS`, `BLOCKED`, and `COMPLETE`.
8. A milestone may be marked `COMPLETE` only when every acceptance criterion is
satisfied and concrete evidence is recorded in the milestone.
9. Evidence must identify the command, test result, inspected artifact, or
hardware observation that demonstrates completion. Statements such as
"implemented" or "looks correct" are not sufficient evidence.
10. A `NOT_STARTED` milestone explicitly records that completion evidence is
not yet available. Expected evidence may be listed in advance.
11. If later work invalidates recorded evidence, reopen the affected milestone
and change its status before continuing.
12. All project documentation, milestone text, source comments, test names,
protocol identifiers, and system log messages are written in English.
Player-facing browser UI copy, game messages, and browser errors are
written in Russian.
## Milestone 001 — Requirements and technical baseline
**Status:** `COMPLETE`
### Objective
Establish a consistent, implementation-ready product specification,
architecture, board passport, build profile, and Flash layout without modifying
the read-only project sources.
### Scope
- Consolidate the agreed gameplay, browser, lobby, observer, reconnection, and
Wi-Fi provisioning requirements.
- Establish the ESP32-C6/ESP-IDF/PlatformIO baseline inherited from Battleship.
- Resolve the stale 2 MB versus confirmed 4 MB Flash configuration.
- Define the authoritative-server architecture and verification boundaries.
- Establish the sequential milestone and evidence policy.
### Deliverables
- `MVP.md`
- `ARCHITECTURE.md`
- `PLANS.md`
- `AGENTS.md`
- `docs/BOARD_PASSPORT.md`
- `platformio.ini`
- `partitions.csv`
- `sdkconfig.defaults`
### Acceptance criteria
1. All canonical project documentation is in English.
2. `MVP.md` captures all agreed roles, gameplay rules, networking behavior,
technical constraints, exclusions, and acceptance criteria.
3. `ARCHITECTURE.md` defines server authority, component boundaries, state
machines, role-specific projection, protocol direction, storage, and
verification strategy.
4. The build profile pins PlatformIO `espressif32@7.0.1`, ESP-IDF, the compatible
`esp32-c6-devkitm-1` profile, and 4 MB Flash.
5. The partition layout is contiguous and ends exactly at `0x400000`.
6. `sdkconfig.defaults` explicitly selects 4 MB Flash and 16 LwIP sockets.
7. The board passport matches the canonical configuration and does not claim an
unverified physical board model, GPIO mapping, LED, or PSRAM.
8. No file under `sources/` is modified.
9. PlatformIO can parse and resolve the canonical project configuration.
### Evidence
- `MVP.md` contains the complete English product and acceptance specification.
- `ARCHITECTURE.md` contains the English runtime, component, state-machine,
protocol, memory, failure, and verification contracts.
- On 2026-09-03, `/home/sasa/.platformio/penv/bin/pio project config` exited
successfully and resolved environment `esp32-c6-devkitm-1` with
`espressif32@7.0.1`, ESP-IDF, explicit 4 MB build/upload sizes,
`partitions.csv`, LittleFS, and `sdkconfig.defaults`. PlatformIO subsequently
emitted a telemetry-cache permission warning during process shutdown; it did
not prevent configuration parsing.
- On 2026-09-03, an `awk` continuity and end-address check over
`partitions.csv` reported the factory partition ending at `0x210000`, the
LittleFS partition ending at `0x400000`, and no gaps or overlaps from NVS
through the end of Flash.
- On 2026-09-03, `rg` confirmed
`CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y` and `CONFIG_LWIP_MAX_SOCKETS=16` in both
`sdkconfig.defaults` and the generated ESP-IDF configuration.
- `docs/BOARD_PASSPORT.md` records the confirmed ESP32-C6FH4, 4 MB Flash,
no-required-PSRAM baseline and the remaining explicit hardware unknowns.
- Canonical files were created outside `sources/`; the reference files under
`sources/` were not edited.
### Completion note
Milestone 001 establishes documentation and configuration only. It does not
claim that firmware source code exists, that a firmware image builds, or that
the target board has passed runtime tests. Those claims require evidence in
later milestones.
Milestone 001 was reopened on 2026-09-03 after its recorded evidence was found
to disagree with the checked-in canonical files. The canonical `MVP.md`
location, explicit Flash configuration, generated SDK configuration, and board
passport were repaired and reverified before returning the milestone to
`COMPLETE`. No Milestone 002 implementation work was started.
## Milestone 002 — Firmware skeleton and reproducible build
**Status:** `COMPLETE`
### Objective
Create the smallest maintainable ESP-IDF firmware and web-asset skeleton that
builds reproducibly with the locked platform and partition configuration.
### Scope
- Establish firmware, component, test, web, and script directories.
- Add startup logging, version information, and a minimal health state.
- Mount LittleFS and serve a minimal static page.
- Implement deterministic compression and filesystem-image packaging for web
assets.
- Add build-time application and filesystem size checks.
### Deliverables
- Minimal bootable firmware source tree.
- Minimal static web application.
- Web-asset compression and LittleFS packaging script.
- Host-test entry point for portable code.
- Documented build, filesystem-build, upload, and monitor commands.
### Acceptance criteria
1. A clean PlatformIO build succeeds with the locked environment.
2. The application image fits the 2 MiB factory partition.
3. The LittleFS image fits the 1,984 KiB filesystem partition.
4. Web-asset generation is deterministic and does not require manual edits.
5. The firmware boots on the target board, mounts LittleFS, and reports its
version and health state.
### Evidence
- On 2026-09-03, `/home/sasa/.platformio/penv/bin/pio project config` parsed
the updated configuration successfully with the locked platform, ESP-IDF,
4 MB Flash, root partition table, LittleFS, pinned `esp_littlefs`, bounded
LittleFS settings, and the build hook.
- On 2026-09-03, `./scripts/run_host_tests.sh` reported
`health state tests: PASS` and
`web asset reproducibility: PASS
(611eacdb06b81bdb064a9074352ff4c6f82d3af3ad3d01e65705387744b86b21)`.
- After `/home/sasa/.platformio/penv/bin/pio run --target clean`, running
`/home/sasa/.platformio/penv/bin/pio run` on 2026-09-03 completed with
`[SUCCESS]` in 43.10 seconds. PlatformIO reported 14,084 bytes of RAM and
350,232 of 2,097,152 application bytes in use. The independent build hook
reported `firmware.bin uses 350864/2097152 bytes in partition factory`.
- Running `/home/sasa/.platformio/penv/bin/pio run --target buildfs` on
2026-09-03 regenerated `data/index.html.gz`, built the LittleFS image, and
reported `[SUCCESS]`. The independent build hook reported
`littlefs.bin uses 2031616/2031616 bytes in partition littlefs`; the image is
intentionally emitted at full partition size and contains the 683-byte
compressed application asset.
- Two LittleFS builds separated by a clean produced the identical SHA-256
`ad203373be69f4541106b2d0dfd7096adb8132967ab49e2d23c391ede8f4d4f5`.
The generated gzip asset remained
`950c60077c381d4484c11b43762bc5c8378587b6cd91924c344cfbc406771bb9`.
Listing the image showed both file and image creation times normalized to
1970-01-01, demonstrating deterministic compression and packaging.
- `strings .pio/build/esp32-c6-devkitm-1/firmware.bin` found firmware version
`0.2.0` and the startup log formats for version, LittleFS capacity, and health
state. This verifies their presence in the built artifact but is not a
substitute for target-board execution.
- On 2026-09-03, `/home/sasa/.platformio/penv/bin/pio run --target uploadfs
--upload-port /dev/ttyACM0` wrote and hash-verified the 2,031,616-byte
LittleFS image at `0x210000`. A subsequent application upload identified the
target as `ESP32-C6FH4 (QFN32) (revision v0.2)` with embedded 4 MB Flash and
USB Serial/JTAG, wrote the 350,864-byte application image, and verified its
hash.
- A target-board boot capture on 2026-09-03 reported `App version: 0.2.0`,
`SPI Flash Size : 4MB`,
`LittleFS mounted: total=2031616 bytes, used=12288 bytes`,
`HTTP server ready with static and health endpoints`, and
`Health state: serving`. The capture contained no application error after
startup. This supplies the physical-board evidence for acceptance criterion
5.
## Milestone 003 — Wi-Fi provisioning and persistent configuration
**Status:** `COMPLETE`
### Objective
Implement station-first Wi-Fi operation with NVS persistence and a fallback
access-point provisioning flow.
### Scope
- Versioned NVS configuration storage.
- Station connection using saved credentials.
- Fallback access point when configuration is missing or connection fails.
- Browser flow to scan, configure, save, and retry an existing network.
- Safe handling of invalid credentials and configuration reset.
### Deliverables
- Persistent configuration component.
- Network manager and provisioning state machine.
- Provisioning HTTP interface.
- Automated configuration tests and on-device recovery procedure.
### Acceptance criteria
1. First boot exposes the fallback provisioning interface.
2. Valid credentials are stored in NVS and used after reboot.
3. Failed station connection returns to a usable fallback access point.
4. Invalid requests cannot corrupt stored configuration.
5. Network reconfiguration cannot leave an active game silently running.
### Evidence
- On 2026-09-03, `./scripts/run_host_tests.sh` reported
`health state tests: PASS`, `wifi configuration tests: PASS`,
`configuration NVS tests: PASS`, `provisioning protocol tests: PASS`, and
`web asset reproducibility: PASS
(b36a6203a78964a324e3d56986a99abb7e36a48a84688cb4ecfe4ac1934b9f92)`.
The tests exercise credential length and control-byte bounds, invalid-input
non-mutation, mandatory reconfiguration guards, real store code across an
NVS reinitialization, schema mismatch, reset, strict two-field JSON parsing,
and malformed or duplicated request fields.
- After a clean on 2026-09-03,
`/home/sasa/.platformio/penv/bin/pio run -e esp32-c6-devkitm-1` completed
with `[SUCCESS]` in 46.25 seconds. PlatformIO reported 37,452 bytes of RAM
and 985,280 of 2,097,152 application bytes in use; the independent size hook
reported `firmware.bin uses 985920/2097152 bytes in partition factory`.
- `/home/sasa/.platformio/penv/bin/pio run -e esp32-c6-devkitm-1 --target
buildfs` completed with `[SUCCESS]` and reported
`littlefs.bin uses 2031616/2031616 bytes in partition littlefs`. The image
contains deterministic compressed application and provisioning pages; its
SHA-256 was
`f3e529542737221d46146f46ff57eb3a88a1b3c5b25c9f4298b9d3e3e22a7d3a`.
- The application and filesystem images were written to the physical
ESP32-C6FH4 and hash-verified. A production first boot with empty
configuration reported `App version: 0.3.0`,
`No usable saved station configuration; enabling fallback`,
`Fallback access point ready: SSID=maze32-F7B9,
URL=http://192.168.4.1/provision`, DHCP at `192.168.4.1`,
`Provisioning endpoints ready`, and `Health state: serving`.
- The isolated `milestone-003-device-test` environment exercised the real NVS
and network manager without accessing local Wi-Fi secrets. One continuous
target log reported `credentials saved; restarting`, reset reason `SW_CPU`,
`credentials survived restart`, station connection to the persisted test
SSID with three bounded retries, `Station connection timed out or exhausted
retries; enabling fallback`, and `failed station returned to fallback;
credentials cleared`. Normal production firmware was restored afterward and
its boot log confirmed the test credentials were absent.
- On 2026-09-03, the project owner used the device-hosted provisioning page
from a separate client to save credentials for `Mazda7`; no credential was
exposed in the test record. A subsequent physical-board reboot loaded that
persisted SSID, associated using WPA2, received `192.168.1.50`, reported
`Network state: station_connected` and `Station connected:
IP=192.168.1.50`, registered the HTTP and provisioning routes, and reached
`Health state: serving`.
- Live requests over `Mazda7` returned
`{"state":"station_connected",...,"ip":"192.168.1.50"}` from
`/api/wifi/status`, a healthy version `0.3.0` response from `/health`, and
the decompressed Russian provisioning page from `/provision`. A deliberately
invalid two-field configuration request returned HTTP 400. A final board
reboot again loaded and connected to `Mazda7`, proving the rejected request
did not alter the persisted valid configuration.
- All five acceptance criteria therefore have recorded evidence.
## Milestone 004 — HTTP, WebSocket, sessions, and lobby
**Status:** `COMPLETE`
### Objective
Provide the bounded network and session layer required for two players, four
observers, and an owner-controlled lobby.
### Scope
- HTTP application delivery and diagnostic endpoint.
- Versioned WebSocket envelope, parser, validation, and bounded queues.
- Temporary session-token creation and browser refresh restoration.
- Unique player names and humorous name suggestions.
- Two player slots, four observer slots, and Player 1 start authority.
- Lobby snapshots, readiness, and the authoritative `3-2-1` countdown.
### Deliverables
- HTTP and WebSocket gateway components.
- Session and lobby services.
- Protocol schemas and protocol tests.
- Minimal browser lobby UI and non-browser protocol test client.
### Acceptance criteria
1. Two players and four observers can connect concurrently within fixed limits.
2. Duplicate player names and excess role claims are rejected safely.
3. Page refresh restores the same session and player slot.
4. Only Player 1 can start, and only with both player slots occupied.
5. Malformed, oversized, unauthorized, and stale messages do not mutate state.
6. Both players receive one authoritative countdown and activation instant.
### Evidence
- On 2026-09-04, `./scripts/run_host_tests.sh` reported
`health state tests: PASS`, `wifi configuration tests: PASS`,
`configuration NVS tests: PASS`, `provisioning protocol tests: PASS`,
`lobby and protocol tests: PASS`, and `web asset reproducibility: PASS
(6bdc7d380994cd302019c8634570953881cda035757d577fe2b1f54742a95f33)`.
The deterministic lobby tests cover the two-player and four-observer bounds,
duplicate-name and excess-role rejection, Player 2 start rejection, Player 1
readiness enforcement, restoration to the original slot, and the shared
countdown activation instant. Protocol tests cover valid envelopes plus
malformed, oversized, duplicate-field, unsupported-version, unknown-type,
invalid-payload, and stale-sequence handling without state mutation.
- A clean production build followed by
`/home/sasa/.platformio/penv/bin/pio run -e esp32-c6-devkitm-1 --target
upload --upload-port /dev/ttyACM0` completed with `[SUCCESS]` in 50.31
seconds. PlatformIO reported 47,508 bytes of RAM and 1,005,274 of 2,097,152
application bytes in use; the independent partition check reported
`firmware.bin uses 1005904/2097152 bytes in partition factory`. The upload
identified the physical target as an ESP32-C6FH4 with 4 MB embedded Flash,
wrote the bootloader, partition table, and application, and hash-verified all
three images.
- `/home/sasa/.platformio/penv/bin/pio run -e esp32-c6-devkitm-1 --target
buildfs` completed with `[SUCCESS]` and reported
`littlefs.bin uses 2031616/2031616 bytes in partition littlefs`. The two
deterministic compressed browser assets total 5,970 bytes, below the 1.5 MiB
web-payload limit and within the unchanged 1,984 KiB LittleFS partition.
- The final firmware booted through the bounded station retry policy and served
its fallback network while the configured `Mazda7` access point was
unavailable. A live `GET /health` returned
`{"status":"serving","healthy":true,"version":"0.4.0","errorCode":0}`.
The host retained its independent Ethernet route while its Wi-Fi interface
joined the device access point, so device testing did not interrupt the
development connection.
- On the physical board,
`python3 scripts/ws_protocol_client.py 192.168.4.1 --verify` reported
`malformed and stale message rejection: PASS`,
`six-client capacity: PASS (2 players + 4 observers)`,
`duplicate player name: PASS`, `seventh connection rejection: PASS`,
`Player 2 start authorization: PASS`,
`session refresh restoration: PASS (Player 2 retained slot 2)`,
`authoritative countdown: PASS (activationAtMs=72223)`, and
`oversized message rejection: PASS`. The countdown payload received by both
player connections was byte-for-byte equivalent, and both subsequent active
events carried the same server activation timestamp.
- During a six-client physical run, the gateway logged all six bounded
admissions and `Six-client heap: free=305388 bytes, largest=278528 bytes`;
it rejected a seventh connection and a 513-byte frame. Session tokens were
returned only in private session messages and were absent from shared lobby
snapshots. The observer protocol exposes no mutating command, while start
authorization is resolved from the server-owned session and player slot.
- These results provide concrete host and physical-device evidence for all six
acceptance criteria.
## Milestone 005 — Symmetric maze generation and validation
**Status:** `COMPLETE`
### Objective
Generate connected, cyclic, reproducible, 180-degree symmetric mazes with fair
object placement and bounded memory use.
### Scope
- Compact configurable maze representation.
- Seeded generation for the default `128 x 64` map.
- Cycles, alternative paths, and multiple cross-half passages.
- Symmetric starts, keys, exits, compass cells, landmarks, and vision artifacts.
- Reachability, symmetry, collision, and fairness validation.
- Bounded regeneration when a candidate fails validation.
### Deliverables
- Portable maze generator and validator.
- Deterministic host-side test suite over a large seed set.
- Diagnostic generation metrics and failure reports.
### Acceptance criteria
1. The same configuration and seed always produce the same valid maze.
2. Every tested map is fully reachable and rotationally symmetric.
3. Every tested map contains cycles and multiple cross-half connections.
4. Player-relative starts and required objects have equal symmetric metrics.
5. Keys are outside their owners' initial views.
6. Default generation fits the agreed RAM and runtime budgets without per-cell
heap allocation.
### Evidence
- On 2026-09-04, `/home/sasa/.platformio/penv/bin/pio project config` exited
successfully and resolved the production, Milestone 003 device-test, and
isolated `milestone-005-device-test` environments from the locked
`espressif32@7.0.1`, ESP-IDF, 4 MB Flash, root partition, and root sdkconfig
baseline.
- On 2026-09-04, `./scripts/run_host_tests.sh` reported all preceding tests as
`PASS`, `maze bounded regeneration failure: PASS (3/3 attempts)`, and
`maze seed sweep: PASS (1024 seeds, combined hash=a264b7d133f8bad4)`.
Generating every default `128 x 64` seed twice produced identical hashes and
valid results. All 1,024 maps contained 8,192 reachable cells, exactly 133
cycles, six cross-half connections, 704–854 dead ends, and symmetric object
and route metrics. Start-to-key distances were 416–1,442, key-to-exit
distances were 346–1,244, and start-to-exit distances were 102–992; every
tested candidate succeeded on its first bounded attempt and key-search route
distance was never shorter than the following exit-search route distance.
- The same host suite reported a maximum 5.127 ms generation time and
`maze fixed memory: PASS (42332/65536 bytes, no heap API)`. The portable
component uses one byte of passage flags per cell plus fixed bit-set,
traversal, distance, and placement arrays. A separate build with
`-fsanitize=address,undefined` and `ASAN_OPTIONS=detect_leaks=0` repeated the
1,024-seed suite without an address or undefined-behavior finding; leak
detection was disabled because the sandbox executes under `ptrace`.
- Validator corruption tests independently rejected a broken reciprocal or
rotational passage and a colliding key placement. The sweep also verified
boundary starts and exits, keys outside each owner's Chebyshev-radius-three
initial view, corresponding distances for keys, exits, compasses, all eight
landmark pairs, and all four vision-artifact pairs. The forced impossible
candidate stopped after the configured three attempts and returned a
diagnostic failure instead of retrying indefinitely.
- The deterministic small-map diagnostic reported representative map hash
`7396c6426181da3e` and printed a complete `16 x 8` ASCII maze with walls,
opposite starts, symmetric keys and exits, compasses, landmarks, and four
vision artifacts. `docs/MAZE_GENERATION.md` records the construction,
validation, configuration, reproducibility, and resource contracts.
- `/home/sasa/.platformio/penv/bin/pio run -e esp32-c6-devkitm-1` completed
with `[SUCCESS]`. PlatformIO reported 47,508 of 327,680 RAM bytes and
1,005,274 of 2,097,152 application bytes in use; the production upload's
independent partition check reported
`firmware.bin uses 1005904/2097152 bytes in partition factory`.
`/home/sasa/.platformio/penv/bin/pio run -e esp32-c6-devkitm-1 --target
buildfs` also completed with `[SUCCESS]` and reported
`littlefs.bin uses 2031616/2031616 bytes in partition littlefs`.
- The isolated physical-board image was built and hash-verified with
`/home/sasa/.platformio/penv/bin/pio run -e milestone-005-device-test
--target upload --upload-port /dev/ttyACM0`. The upload identified the target
as an ESP32-C6FH4 with 4 MB embedded Flash. On target, requested seed
`1513226245` generated in one attempt with hash `485280a746e8900c` and logged
`cells=8192, reachable=8192, edges=8324, cycles=133, crossings=6,
dead_ends=778, branches=1024`. Paired route metrics were equal:
`start_key=804/804`, `key_exit=773/773`, `start_exit=191/191`,
`compass=223/223`, `landmarks=3099/3099`, and `artifacts=1085/1085`.
- The same physical run logged
`fixed=42332/65536 bytes, runtime=183/250 ms, heap_delta=0` followed by
`M005 physical maze test: PASS`. Production firmware was restored and
hash-verified afterward; a live `GET /health` over the configured station
network returned
`{"status":"serving","healthy":true,"version":"0.5.0","errorCode":0}`.
- These deterministic, validator, resource, representative-map, build, and
physical-board results provide concrete evidence for all six acceptance
criteria and completed Milestone 005.
## Milestone 006 — Authoritative movement and visibility core
**Status:** `COMPLETE`
### Objective
Implement deterministic player movement, command ordering, cooldowns, and
server-enforced role-specific visibility.
### Scope
- Serial authoritative command queue and sequence numbers.
- Orthogonal one-cell movement and wall rejection.
- Independent player actions with configurable server cooldown.
- Shared-cell occupancy and meeting detection.
- Chebyshev-radius visibility with walls that do not occlude.
- Player-specific projections and complete observer projections.
### Deliverables
- Portable game-state and movement engine.
- View projector with fog-of-war security tests.
- Movement, cooldown, ordering, collision, and visibility tests.
### Acceptance criteria
1. Accepted inputs move exactly one valid orthogonal cell.
2. Invalid, premature, or rate-limited commands do not mutate state.
3. Both players may occupy the same cell and generate one meeting event.
4. Player payloads contain no hidden cells, objects, or stale opponent state.
5. Observer payloads contain the complete current state.
6. Radius-three and radius-six projections match the documented formula at
walls and map boundaries.
### Evidence
- On 2026-09-04,
`PLATFORMIO_SETTING_ENABLE_TELEMETRY=no /home/sasa/.platformio/penv/bin/pio
project config` exited successfully and resolved the production and isolated
`milestone-006-device-test` environments from `espressif32@7.0.1`, ESP-IDF,
the `esp32-c6-devkitm-1` profile, 4 MB Flash, and the root partition and
sdkconfig files.
- `./scripts/run_host_tests.sh` passed every preceding suite and reported
`game movement, cooldown, and FIFO ordering: PASS (commands 1-7, states
0-3)`, `game bounded command queue: PASS (16 entries)`, `game shared-cell
meeting event: PASS (one event)`, and `game visibility and payload security:
PASS (r3=49/16, r6=64/49, observer=64)`. The same suite serialized a complete
default `128 x 64` observer snapshot as 345 chunks and reported a maximum
chunk size of 405 bytes against the 512-byte limit. A separate build with
`-fsanitize=address,undefined` repeated the game-core suite without an
address or undefined-behavior finding; leak detection was disabled because
the sandbox executes under `ptrace`.
- The deterministic movement cases proved that queued commands are processed
in assigned FIFO order, each accepted direction changes exactly one
coordinate by one cell, and Player 1 and Player 2 may act at the same server
timestamp. Countdown, cooldown, out-of-order timestamp, closed-wall,
malformed-direction, observer, and full-queue rejections left player state,
accepted-move counters, cooldown clocks, and the authoritative state
sequence unchanged. Entering the other player's occupied cell left both
players there and emitted one meeting event.
- Projection tests covered both player roles and reused a snapshot after an
opponent changed from visible to hidden. Radius-three squares contained 49
cells at an interior position and 16 at a corner; radius-six squares
contained 64 and 49 respectively on the `8 x 8` fixture. An internal wall
did not remove cells. Player structures and every serialized 512-byte-bounded
JSON chunk omitted hidden cell 63, its hidden key record, and stale opponent
state, while the observer contained all 64 cells, all ten fixture objects,
and both player positions. The serializer also emitted the versioned
envelope, authoritative sequence, phase, meeting sequence, and chunk
metadata.
- `/home/sasa/.platformio/penv/bin/pio run -e esp32-c6-devkitm-1` completed
with `[SUCCESS]` and reported 47,508 of 327,680 RAM bytes and 1,005,274 of
2,097,152 application bytes in use. The production `firmware.bin` measured
1,005,904 bytes against the 2,097,152-byte factory partition.
`/home/sasa/.platformio/penv/bin/pio run -e esp32-c6-devkitm-1 --target
buildfs` completed with `[SUCCESS]`; the independent partition check reported
`littlefs.bin uses 2031616/2031616 bytes in partition littlefs`.
- The isolated device image built with `[SUCCESS]`, using 124,468 of 327,680
RAM bytes and 1,016,242 of 2,097,152 application bytes. Its independent
partition check reported `firmware.bin uses 1016880/2097152 bytes in
partition factory`. Upload with `/home/sasa/.platformio/penv/bin/pio run -e
milestone-006-device-test --target upload --upload-port /dev/ttyACM0`
identified an ESP32-C6FH4 revision 0.2 with 4 MB embedded Flash and
hash-verified every written image.
- The physical board logged an accepted command with command sequence one,
state sequence one, and the new position. It projected 28 player cells into
two bounded chunks and the complete 8,192-cell, 30-object observer state into
345 bounded chunks; the first serialized chunk was 372 bytes. It then logged
`fixed=34612/40960 bytes, runtime=5/50 ms, heap_delta=0` and
`M006 physical game test: PASS`.
- The production image was restored and hash-verified after the isolated test.
A live `GET /health` returned
`{"status":"serving","healthy":true,"version":"0.6.0","errorCode":0}`.
These host, serialization, build, resource, and physical-board results
provide concrete evidence for all six acceptance criteria and complete
Milestone 006.
## Milestone 007 — Keys, artifacts, compasses, exits, and results
**Status:** `COMPLETE`
### Objective
Complete the authoritative gameplay mechanics and terminal match outcomes.
### Scope
- One-slot key inventory, pickup, voluntary drop, and sabotage.
- Automatic return of keys to their owners when players meet.
- Owner-only key and exit restrictions.
- One-use vision artifacts lasting ten successful moves.
- Stationary compass activation and exit-direction hints.
- Exit opening, outward victory move, 200 ms draw window, and result state.
### Deliverables
- Gameplay object and result systems.
- Deterministic edge-case and simultaneous-event tests.
- Role-specific events for inventory, hints, effects, and results.
### Acceptance criteria
1. Inventory and ground-key state never duplicate or lose a key.
2. Meeting behavior returns or swaps carried keys atomically.
3. A player cannot use the opponent's key or exit to win.
4. Vision effects last exactly ten successful moves and ignore failed moves.
5. Compass activation points toward only the activating player's own exit.
6. Victory requires the outward move, and exits within 200 ms produce a draw.
### Evidence
- `bash scripts/run_host_tests.sh` completed successfully. Its new deterministic
mechanics suite reported `game key inventory, drop, and sabotage: PASS`,
`game atomic meeting key return: PASS (opponent keys swapped to owners)`,
`game owner-only exit opening and outward move: PASS`, `game one-use vision
duration: PASS (10 successful moves, failed move ignored)`, `game compass
bearing and role-specific projections: PASS`, and `game outward result
window: PASS (200 ms draw boundary, 201 ms winner)`. All Milestone 001-006
host regressions and the reproducible web-asset check passed in the same run.
- The key tests exercised pickup of both owner and opponent keys, one-slot
rejection, opponent-key sabotage, deterministic pickup when both keys occupy
one cell, voluntary drop, and rejected empty drop. They asserted after every
transition that each key existed exactly once on the ground or in one
inventory. A crossed-key meeting cleared both inventories and reassigned the
keys to their owners in one state transition.
- Exit tests proved that the wrong key cannot open a player's own exit and that
neither carrying the opponent's key nor standing at the opponent's exit
permits opening or an outward move. Carrying the matching key opened only the
owner's exit; a later outward boundary command was required to enter the
finish window. Timestamped cases proved that the second outward move at
exactly 200 ms produces a draw, while advancing time at 201 ms finalizes the
first player as winner; the symmetric Player 2 winner case also passed.
- Artifact tests consumed an artifact once, selected the authoritative radius
six, retained all ten moves after entry, ignored a failed move, and returned
to radius three after exactly ten later successful moves. Compass tests
produced the direct eight-way bearing to only the activating player's own
exit, preserved it across a rejected command, and cleared it on the next
successful move.
- Projection inspection covered both player roles and the observer. Player
snapshots contained only their own inventory, effect, compass hint, and exit
state; an opponent's carried key appeared only while that opponent was
visible and disappeared when hidden. Observer snapshots contained both
inventories, effects, hints, exit states and timestamps, all remaining
ground objects, and the result. The complete default `128 x 64` observer
snapshot serialized into 688 versioned chunks with a worst-case maximum of
471 bytes, below the 512-byte message bound. Independent AddressSanitizer and
UndefinedBehaviorSanitizer runs of both game suites passed without findings;
leak detection was disabled because the sandbox executes under `ptrace`.
- `PLATFORMIO_SETTING_ENABLE_TELEMETRY=no
/home/sasa/.platformio/penv/bin/pio project config` parsed the locked
`espressif32@7.0.1`, ESP-IDF, `esp32-c6-devkitm-1`, `partitions.csv`, and
`sdkconfig.defaults` configuration, including the isolated
`milestone-007-device-test` environment. `/home/sasa/.platformio/penv/bin/pio
run -e esp32-c6-devkitm-1` completed with `[SUCCESS]`, reporting 47,508 of
327,680 RAM bytes and 1,005,274 of 2,097,152 application bytes; the independent
partition check reported `firmware.bin uses 1005904/2097152 bytes in
partition factory`. The isolated device image also built with `[SUCCESS]`,
using 124,628 RAM bytes and 1,020,168 application bytes, with
`firmware.bin uses 1020800/2097152 bytes in partition factory`.
- `/home/sasa/.platformio/penv/bin/pio run -e esp32-c6-devkitm-1 --target
buildfs` completed with `[SUCCESS]`; the partition check reported
`littlefs.bin uses 2031616/2031616 bytes in partition littlefs`. The test image
upload identified an ESP32-C6FH4 revision 0.2 with 4 MB embedded Flash and
hash-verified each write. The board logged `M007 events: artifact=0, key=0,
hint=8, opened=1, outward=1, result=1, sequence=6`, player and complete
observer projections, `fixed=34776/40960 bytes, runtime=6/50 ms,
heap_delta=0`, and `M007 physical mechanics test: PASS`.
- The normal firmware was restored and hash-verified after the isolated test.
A live `GET /health` returned
`{"status":"serving","healthy":true,"version":"0.7.0","errorCode":0}`.
These deterministic, security-boundary, size, sanitizer, and physical-board
results provide concrete evidence for all six acceptance criteria. Milestone
008 remains `NOT_STARTED`.
## Milestone 008 — Player browser experience
**Status:** `COMPLETE`
### Objective
Deliver the complete responsive player interface without moving game authority
into the browser.
### Scope
- Name selection, lobby, countdown, running, paused, and result screens.
- Dark, humorous retro arcade presentation following `docs/UI_STYLE.md`.
- Russian player-facing copy and locally served pixel-styled graphics.
- Maze viewport and visual landmarks.
- Keyboard movement, touchscreen swipes, and responsive on-screen arcade
controls.
- Accessible drop-key action.
- Inventory, vision duration, compass hint, connection, and error feedback.
- Default sound, supported-device vibration, and animated game feedback.
- Session restoration after page refresh.
### Deliverables
- Responsive browser application and compressed production assets.
- Implemented visual tokens, SVG icon set, and shared control styles.
- Browser interaction tests for desktop and mobile input.
- Accessibility and viewport behavior checks.
### Acceptance criteria
1. Keyboard and swipe actions produce equivalent server commands.
2. The client renders only state received in the player projection.
3. Hidden and previously seen cells are not retained on screen.
4. Inventory, effects, hints, pause, and results are unambiguous.
5. Refresh restores the active player without changing position or inventory.
6. The player flow remains usable without page scrolling at a `412 x 916`
CSS-pixel viewport and at representative tablet and desktop viewports.
7. Player-facing UI copy and browser errors are in Russian while protocol and
system-log text remain in English.
8. The application requires no runtime internet access or third-party UI
dependency.
9. The complete compressed web payload is no larger than 1.5 MiB and fits the
unchanged LittleFS partition.
10. The current stable Chrome, Safari, Firefox, and Edge releases pass the
applicable browser interaction checks, with versions and test date
recorded in the milestone evidence.
### Evidence
Implementation and required verification are complete.
- `bash scripts/run_host_tests.sh` completed successfully. The five focused
browser tests reported keyboard/swipe direction equivalence, intention-only
command envelopes, full replacement of chunked projections without
explored-map memory, token-only refresh restoration, and local Russian,
accessible, responsive assets. All Milestone 001-007 host regressions passed
in the same run. The complete default observer projection remained bounded
at 688 chunks with a worst-case maximum of 495 bytes against the 512-byte
message limit. A separate AddressSanitizer and UndefinedBehaviorSanitizer run
of the changed projection suite passed without findings; leak detection was
disabled because the sandbox executes under `ptrace`.
- `python3 scripts/verify_web_assets.py` reported `web asset reproducibility:
PASS` with digest
`f321d5bb1b4ad8cfcdf306c5a5e696f3b2b00b54792c727fd639671e585b573d`
and a complete compressed payload of 17,100 of 1,572,864 allowed bytes.
Source inspection and tests found no remote runtime URL, import, font,
framework, analytics, or icon dependency. `node --check web/player.js` and
`xmllint --noout web/icons.svg` also completed successfully.
- `pio run -e esp32-c6-devkitm-1` completed with `[SUCCESS]`, reporting 47,508
of 327,680 RAM bytes and 1,005,660 of 2,097,152 application bytes. The
independent partition check reported `firmware.bin uses 1006288/2097152
bytes in partition factory`. `pio run -e esp32-c6-devkitm-1 --target buildfs`
completed with `[SUCCESS]`; the independent partition check reported
`littlefs.bin uses 2031616/2031616 bytes in partition littlefs`, with all five
gzip assets present in the image.
- Physical delivery passed after the board re-enumerated as `/dev/ttyACM0`.
`pio run -e esp32-c6-devkitm-1 --target upload --upload-port /dev/ttyACM0`
and the corresponding `uploadfs` command both completed with `[SUCCESS]` and
hash-verified every write. The ESP32-C6FH4 revision 0.2 boot log reported
application version `0.8.0`, the unchanged 4 MB partition table,
`LittleFS mounted: total=2031616 bytes, used=36864 bytes`, and successful HTTP,
WebSocket, and provisioning service startup. The saved station SSID remained
`k49`, so the device correctly entered its `maze32-F7B9` fallback AP while the
host remained on `Mazda7`; the host network was not changed.
- A USB readback of the complete LittleFS partition used
`/home/sasa/.platformio/penv/bin/python
/home/sasa/.platformio/packages/tool-esptoolpy/esptool.py --chip esp32c6
--port /dev/ttyACM0 read_flash 0x210000 0x1f0000
/tmp/maze32-m008-littlefs-readback.bin`. Both the built image and physical
readback had SHA-256
`606b98e11cbc479bf11bffa9a38d62ddfebce0689f0e3ebf148521190a032fd5`;
`cmp` returned zero, proving byte-for-byte delivery without interrupting the
host's internet connection.
- Automated live browser validation was unavailable because the browser-control
runtime returned `No browser is available` and its browser inventory was
empty. The project owner subsequently supplied the required manual browser
evidence recorded below.
- On 2026-09-04, a fresh browser-control inventory check again returned an
empty list. Milestone 008 was therefore changed from `IN_PROGRESS` to
`BLOCKED`; no Milestone 009 implementation work was started.
- On 2026-09-04, the project owner completed the required manual player-UI
checks in Chrome 152, Firefox 155, Safari 26.6, and Edge 152.0.4191.53. The
checks covered the `412 x 916` phone layout without page scrolling,
representative tablet and desktop layouts, keyboard and swipe movement,
the accessible drop-key control, and refresh/session restoration. Together
with the automated browser-contract, projection-security, localization,
offline-asset, payload-size, build, and physical-device evidence above, this
satisfies every Milestone 008 acceptance criterion. Milestone 008 is
`COMPLETE`; Milestone 009 remains `NOT_STARTED`.
## Milestone 009 — Observer mode and disconnect recovery
**Status:** `COMPLETE`
### Objective
Complete the read-only full-map observer experience and reliable player pause
and reconnect behavior.
### Scope
- Full-map observer rendering with player and object updates.
- Large-screen observer layout with initial full-map fit, zoom, and pan.
- Observer access without a password and strict read-only authorization.
- Player disconnect detection and authoritative pause.
- Session reattachment and 60-second monotonic deadline.
- Timeout draw and observer-independent match continuity.
### Deliverables
- Observer browser mode.
- Pause/reconnect state-machine integration.
- Authorization, reconnect, refresh, and timeout tests.
### Acceptance criteria
1. Four observers can view the complete live state without control authority.
2. Observer disconnects do not pause or mutate a match.
3. Either player disconnect immediately pauses gameplay and effect progression.
4. Reconnection within 60 seconds restores and resumes the same state.
5. Reconnection timeout ends the match in a draw.
6. Observer attempts to send mutation commands are rejected and audited.
7. Observer mode works at `1024 x 768` CSS pixels and larger, while smaller
viewports show the required Russian large-screen message.
### Evidence
Implementation and required verification are complete.
- On 2026-09-05, `bash scripts/run_host_tests.sh` completed successfully. The
new deterministic recovery suite proved immediate authoritative pause,
rejection of commands without state or effect progression while paused,
restoration before the monotonic 60,000 ms deadline, timeout draw at the
deadline, late-reconnect rejection, and preservation of the remaining 200 ms
finish window across a pause. Observer-role command enqueue returned
`read_only`, and observer connection changes left lobby phase unchanged.
- The same host run reported `game default observer serialization: PASS (688
chunks, maximum 494 bytes)`. The projection contains all 8,192 cells, both
player records and connection states, and remains below the 512-byte message
bound. Seven browser-contract tests passed, covering authoritative chunk
assembly, observer role rendering, absence of observer mutation controls,
bounded fit/zoom/pan behavior, and the Russian sub-`1024 x 768` notice. Web
asset reproducibility passed with SHA-256
`e8d097c7aa1a56cf84748078176b50cc8e2718082edfda35625b6d4436a1fd67`
and 19,133 of 1,572,864 allowed compressed bytes.
- Separate AddressSanitizer and UndefinedBehaviorSanitizer runs of the changed
game-core and disconnect-recovery suites passed without findings; leak
detection was disabled because the sandbox executes under `ptrace`.
`node --check web/player.js`, compilation of
`scripts/ws_protocol_client.py`, and `git diff --check` also passed.
- `/home/sasa/.platformio/penv/bin/pio run -e esp32-c6-devkitm-1` completed
with `[SUCCESS]`, reporting 124,148 of 327,680 RAM bytes and 1,023,742 of
2,097,152 application bytes. The independent partition check reported
`firmware.bin uses 1024368/2097152 bytes in partition factory`. The
corresponding `buildfs` command completed with `[SUCCESS]` and reported
`littlefs.bin uses 2031616/2031616 bytes in partition littlefs`, with all five
compressed assets present.
- Automated live observer validation was initially unavailable because the
browser-control runtime returned `No browser is available` and its browser
inventory was empty. The project owner subsequently supplied the required
physical acceptance evidence recorded below.
- On 2026-09-05, the project owner completed all Milestone 009 physical checks
and reported that the implementation worked as expected. The checks covered
simultaneous complete live-state delivery to four read-only observers,
rejection and audit of observer mutation attempts, observer-disconnect match
continuity, immediate pause on either player disconnect, unchanged gameplay
and effect state while paused, restoration of the same player state within
60 seconds, and an authoritative draw after reconnect timeout. Manual browser
checks also confirmed full-map fit, live player/object/connection updates,
zoom and pan at `1024 x 768` and larger, no observer mutation controls, and
the required Russian large-screen notice below that viewport. Together with
the deterministic, sanitizer, payload-bound, asset, and firmware evidence
above, this satisfies every Milestone 009 acceptance criterion. Milestone
009 is `COMPLETE`; Milestone 010 remains `NOT_STARTED`.
## Milestone 010 — On-device integration and resource hardening
**Status:** `COMPLETE`
### Objective
Demonstrate that the integrated system remains correct and responsive within
the ESP32-C6 Flash, RAM, socket, and network limits.
### Scope
- Full firmware and LittleFS production build.
- Two-player plus four-observer load testing.
- Heap, largest-block, socket, queue, latency, and disconnect measurements.
- Slow-client backpressure and malformed-message stress.
- Station and fallback-access-point integration tests.
- Long-running match and repeated-match stability.
### Deliverables
- On-device integration test procedure and results.
- Resource budget report.
- Fixed limits and defensive failure behavior.
### Acceptance criteria
1. Application and filesystem images fit their partitions with recorded margin.
2. Six simultaneous clients remain connected and responsive on target hardware.
3. Slow or malformed clients cannot exhaust memory or block player processing.
4. Heap and largest free block remain stable across repeated matches.
5. Station reconnect, provisioning fallback, and browser recovery work on the
physical device.
6. No test relies on PSRAM or an unverified GPIO.
### Evidence
- `/home/sasa/.platformio/penv/bin/pio run` completed with `[SUCCESS]` on
2026-09-05. It reported 124,276 of 327,680 RAM bytes and 1,028,600 of
2,097,152 application bytes. `firmware.bin` measured 1,029,232 bytes in the
2,097,152-byte factory partition, leaving 1,067,920 bytes. The ELF contains
application version `0.10.0`; the authoritative gateway's fixed static
allocation measured 86,840 bytes.
- `/home/sasa/.platformio/penv/bin/pio run -t buildfs` completed with
`[SUCCESS]`. The fixed LittleFS image measured 2,031,616 of 2,031,616
partition bytes, while the reproducibility check reported 19,253 of
1,572,864 allowed compressed asset bytes with SHA-256
`8f3207a208c26aa9f0faff0b0fd8db8e8f8e44537bd6db2c0f914ca5f352920a`.
- `bash scripts/run_host_tests.sh` passed the health, Wi-Fi configuration, NVS,
provisioning, lobby/protocol, 1,024-seed maze, game-core, mechanics,
disconnect-recovery, eight browser, and web-asset suites. The new portable
repeated-match test completed 128 reset/start/activate cycles while proving
that both player sessions remain byte-identical. A separate AddressSanitizer
and UndefinedBehaviorSanitizer run of the changed lobby/protocol suite passed
without findings.
- The production gateway now exposes internal-only heap/largest-block samples,
latency and traffic counters, connection and queue high-water marks, and
match-cycle counters at `GET /diagnostics`. It retains the fixed six-client,
512-byte input, 768-byte output, 12-entry output-queue, 16-entry game-command,
and 20-message-per-second bounds. Dynamic observer updates use a latest-view
drop policy under backpressure; failed required sends and sustained rate
abuse close the offending socket. No added code or test depends on PSRAM or
a GPIO.
- `python3 scripts/ws_protocol_client.py 192.168.1.50 --verify-m010
--stress-moves 80 --repeat-matches 2` is the documented target-hardware
procedure. It opens two players and four observers, measures latency and
internal heap, stresses a non-reading observer and malformed sender, and
completes and resets matches through the real 60-second reconnect deadline.
Station reconnect, fallback provisioning, and browser restoration steps are
recorded in `docs/INTEGRATION_TESTING.md` without requiring the development
host to leave Mazda7.
- Physical evidence is still pending: `/dev/ttyACM0` was absent on 2026-09-05,
so the production images could not be uploaded and acceptance criteria 2--5
could not be recorded. Milestone 010 therefore remains `IN_PROGRESS`.
- On 2026-09-07, a further target-availability check found no `/dev/ttyACM0`,
`/dev/ttyUSB0`, or `/dev/ttyUSB1` device node. The documented station target
`http://192.168.1.50/health` also timed out after three seconds. Physical
acceptance criteria 2--5 remain pending; Milestone 010 is `IN_PROGRESS`.
- On 2026-09-08, host-level USB access found the ESP32-C6 at `/dev/ttyACM0`
(USB-Serial/JTAG, MAC `58:e6:c5:18:f7:b8`). Production firmware and
LittleFS uploads completed with verified hashes. Boot logs confirmed app
version `0.10.0`, 4 MB partitions, LittleFS mount, station IP
`192.168.1.50`, all routes, and health `serving`.
- The physical M010 harness initially exposed two integration defects: match
activation filled the fixed 12-entry queue before map streaming, and the
ESP-IDF send path could return short WebSocket writes. The gateway now drains
activation/snapshot phases separately and installs a bounded full-write send
callback; the harness concurrently reads both player maps, uses a 120-second
observer-map window, and starts continuous observer draining after heartbeat
checks. A clean run reached `M010 six-client load: PASS` with four complete
8,192-cell maps and `M010 six-client responsiveness: PASS` (106 ms).
- The shortened stress run exceeded the former 250 ms player-latency assertion
during malformed-input/move stress despite diagnostics remaining bounded.
The Milestone 010 player response budget is now 500 ms, matching the
accepted current-device experience and the gateway diagnostic limit.
Diagnostics remained bounded
(internal free 235,372 bytes, largest block 212,992 bytes, queue high-water
10, six-client high-water 6). Full repeated-match and station/provisioning
acceptance evidence is not yet recorded; Milestone 010 remains `IN_PROGRESS`.
- On 2026-09-08, the project owner ran
`./scripts/ws_protocol_client.py 192.168.1.50 --verify-m010 --stress-moves 80
--repeat-matches 2` against the target device. It passed six-client load with
four complete observers, six-client responsiveness (86.9 ms), slow/malformed
stress (455.4 ms maximum player latency and 387 dropped latest observer
updates), and two repeated match cycles (16-byte heap range). Final
diagnostics recorded active connections 2, connection high-water 6, queue
high-water 8, queue depth 0, 202,292-byte minimum internal free heap,
184,320-byte minimum largest block, 20 malformed messages, 21 rate-limited
messages, 80 accepted commands, two completed/reset matches, and no PSRAM
requirement. The subsequent `/health` response was `serving` for version
`0.10.0`. Acceptance criteria 1--4 and 6 now have concrete evidence;
physical station/fallback provisioning and browser-recovery evidence for
criterion 5 remains pending, so Milestone 010 is `IN_PROGRESS`.
- On 2026-09-08, `./scripts/run_host_tests.sh` passed every health, Wi-Fi
configuration, NVS, provisioning, lobby/protocol, maze, game, disconnect
recovery, browser, and web-asset suite. Its deterministic maze sweep covered
1,024 seeds and its repeated-match reset test completed 128 cycles. The
owner also ran `pio run -e esp32-c6-devkitm-1`, which passed with 124,276 of
327,680 RAM bytes and 1,028,956 of 2,097,152 application bytes, and
`pio run -e esp32-c6-devkitm-1 --target buildfs`, which passed with the
2,031,616-byte LittleFS image exactly fitting its 2,031,616-byte partition.
- On 2026-09-08, the owner confirmed physical station recovery after network
disruption. `/api/wifi/status` returned
`{"state":"station_connected","accessPointSsid":"maze32-F7B9",`
`"ip":"192.168.1.50"}`, and bounded station retries behaved as expected.
The owner also confirmed the fallback provisioning page worked as expected
and that both active player sessions restored their original slots through
saved tokens. Together with the successful target stress run, host
regressions, production builds, and fixed-limit evidence above, this
satisfies every Milestone 010 acceptance criterion. Milestone 010 is
`COMPLETE`; Milestone 011 remains `NOT_STARTED`.
## Milestone 011 — Gameplay tuning and MVP acceptance
**Status:** `COMPLETE`
### Objective
Tune the game to its target experience and demonstrate every MVP acceptance
criterion on the release candidate.
### Scope
- Tune movement cooldown, maze balance thresholds, landmarks, artifact density,
and compass placement.
- Measure key-search and exit-search difficulty.
- Run representative human playtests targeting approximately five minutes.
- Execute the complete MVP acceptance suite and document known limitations.
- Produce the reproducible release image and matching web filesystem image.
### Deliverables
- Locked MVP gameplay configuration.
- Playtest and balance report.
- MVP acceptance report mapped to `MVP.md`.
- Release build artifacts and version record.
### Acceptance criteria
1. Key discovery is demonstrably no easier than the subsequent exit search
under the agreed metrics and playtest evidence.
2. Representative completed matches normally target approximately five minutes
without a hard match timer.
3. Every acceptance criterion in `MVP.md` has recorded passing evidence or an
explicitly approved deviation.
4. The final firmware and web images build reproducibly and fit the target.
5. No unresolved defect prevents the two-player or observer MVP flows.
### Evidence
Implementation is in progress. `docs/BALANCE_REPORT.md` locks the current MVP
gameplay configuration and records the deterministic maze metrics. The
playtest procedure and result table are in `docs/PLAYTEST_REPORT.md`, and the
criterion-by-criterion evidence register is in `docs/MVP_ACCEPTANCE.md`.
- On 2026-09-08, `bash scripts/run_host_tests.sh` passed its health, Wi-Fi,
NVS, provisioning, lobby/protocol, maze, game, disconnect-recovery, browser,
and web-asset suites. The locked 1,024-seed sweep reported cycles `133..133`,
dead ends `704..854`, start-to-key distance `416..1442`, and key-to-exit
distance `346..1244`; every generated map satisfied the key-distance balance
invariant. `pio run -e esp32-c6-devkitm-1` passed using 124,276 of 327,680
RAM bytes and 1,028,956 of 2,097,152 application bytes. The matching
`buildfs` command passed using 2,031,616 of 2,031,616 LittleFS bytes.
`docs/MVP_ACCEPTANCE.md` records version `0.10.0`, build hashes, and the
19,253-byte reproducible web payload.
- On 2026-09-08, the project owner confirmed the representative physical
two-player playtests worked as expected with no gameplay defects or balance
issues. The owner explicitly approved an evidence deviation for acceptance
criterion 2: no match count or elapsed-time statistics are required. The
approval, its scope, and the absence of a hard timer are recorded in
`docs/PLAYTEST_REPORT.md`; criterion 12 of `MVP.md` is mapped as passing by
that approved deviation in `docs/MVP_ACCEPTANCE.md`. Together with the
deterministic balance evidence, complete mapped MVP evidence, and matching
release-candidate images, this satisfies every Milestone 011 acceptance
criterion. Milestone 011 is `COMPLETE`; Milestone 012 remains `NOT_STARTED`.
## Milestone 012 — Combinatorial humorous name suggestions
**Status:** `COMPLETE`
### Objective
Replace the small fixed set of humorous player-name suggestions with a
random browser-side generator that combines a Russian adjective and noun in the
manner of Docker's random container names.
### Scope
- Define bounded Russian adjective and noun vocabularies suitable for the
player-facing lobby.
- Generate valid UTF-8 name suggestions by combining one adjective and one
noun, with a hyphen between them.
- Select an adjective and noun independently with browser-provided randomness
each time the player requests a suggestion.
- Skip combinations already shown as assigned in the authoritative public lobby
projection.
- Remove the obsolete suggestion WebSocket request and its gateway code.
- Retain bounded frontend state and Russian player-facing error behavior,
including a deterministic fallback if a random source is unavailable.
- Add deterministic browser-side coverage by injecting the random source.
### Deliverables
- Combinatorial humorous-name generator in the player application.
- Browser tests covering the generator's selection rules.
- Removed suggestion protocol and gateway implementation.
### Acceptance criteria
1. Each name suggestion has the form `adjective-noun`, uses valid UTF-8, and
satisfies the lobby name-validation limits.
2. The generator provides substantially more combinations than the previous
five fixed suggestions and independently randomizes its adjective and noun
for each request.
3. A currently occupied player name from the authoritative lobby projection is
never suggested.
4. Exhaustion displays a bounded Russian frontend error.
5. All existing host tests and the targeted browser generator tests pass.
6. The regenerated web assets remain reproducible and within the LittleFS
payload budget.
### Evidence
- The browser test `frontend name suggestions randomize both words and remain
bounded` passed. With an injected source it proved independent adjective and
noun selection, the 144-combination vocabulary, UTF-8 byte-length bounds,
occupied-name skipping, exhaustion, and invalid-source fallback.
- `node --check web/player.js` and `bash scripts/run_host_tests.sh` passed. The
full host suite reported nine browser tests and web-asset reproducibility
with SHA-256 `d90dd2cf7ebd4921109b7c7ecaa3e4ddbe021d8da6c8122731746e83ad19b367`
using 19,928 of 1,572,864 allowed compressed bytes.
- `pio run -e esp32-c6-devkitm-1 --target buildfs` passed and the matching
LittleFS image uses 2,031,616 of 2,031,616 partition bytes. The previously
recorded clean firmware build remains valid because this adjustment changes
only browser source and regenerated assets.
- Each button press now independently samples an adjective and noun through a
bounded `crypto.getRandomValues` path, falling back to `Math.random` only
when strong browser randomness is unavailable. It retains a deterministic,
testable fallback for an invalid injected source and cannot alter any
authoritative server state. This satisfies every revised Milestone 012
acceptance criterion. Milestone 012 is `COMPLETE`; Milestone 013 remains
`NOT_STARTED`.
## Milestone 013 — Random humorous login headlines
**Status:** `COMPLETE`
### Objective
Make the login screen feel more lively by selecting a humorous Russian
headline at random whenever a player opens the page.
### Scope
- Expand the login screen's bounded set of player-facing Russian humorous
headlines.
- Select one headline using browser-provided randomness when the login page is
initialized.
- Provide a safe fallback when strong browser randomness is unavailable.
- Preserve the existing responsive layout, accessibility semantics, and static
asset-size constraints.
- Add deterministic browser tests by injecting or stubbing the random source.
### Deliverables
- Expanded login-headline vocabulary in the browser application.
- Random headline selection with a bounded fallback.
- Browser tests for selection, fallback behavior, and visible Russian copy.
### Acceptance criteria
1. The login screen has a substantially expanded set of humorous Russian
headlines, all appropriate for player-facing UI.
2. On separate page initializations, the application selects a headline from
the configured set using browser-provided randomness rather than a fixed
sequence.
3. If the preferred random source is unavailable or fails, the login screen
still displays a valid configured headline.
4. The selected headline remains readable and does not break the login layout
at the responsive sizes defined in `docs/UI_STYLE.md`.
5. Targeted browser tests and `bash scripts/run_host_tests.sh` pass.
6. The regenerated web asset is deterministic for a given source tree and
remains within the LittleFS payload budget.
### Evidence
- The browser test `login headline selection is randomizable, Russian, and has
a fallback` passed. It verifies eight locally authored, appropriate Russian
two-line headlines, deterministic injected selection, fallback to the first
valid headline for an invalid source, and initialization-time rendering into
the existing heading structure.
- `node --check web/player.js` and `bash scripts/run_host_tests.sh` passed. The
full host suite reported ten browser tests and web-asset reproducibility with
SHA-256 `ca5a637087748707367bd6ccec7885d0ff6ad20bd4713df528403e13f26fa540`
using 20,281 of 1,572,864 allowed compressed bytes.
- `pio run -e esp32-c6-devkitm-1 --target buildfs` passed; its LittleFS image
uses 2,031,616 of 2,031,616 partition bytes. The layout preserves the
existing `h1`, line break, and highlighted span, so no responsive or
accessibility structure changed.
- Each login-page initialization selects its headline through the bounded
browser-random helper. Strong browser randomness is preferred, and the
helper falls back safely when unavailable. This satisfies every Milestone 013
acceptance criterion. Milestone 013 is `COMPLETE`; Milestone 014 remains
`NOT_STARTED`.
## Milestone 014 — Compact square maze and gameplay rebalance
**Status:** `COMPLETE`
### Objective
Reduce the default maze from `128 x 64` (8,192 cells) to a square `32 x 32`
maze (1,024 cells), exactly one eighth of the original area, and rebalance the
game so that its exploration, competition, and fairness requirements remain
meaningful at the smaller scale.
### Scope
- Change the canonical and implementation-default maze dimensions to
`32 x 32` while retaining central symmetry and bounded generation.
- Recalculate cycle density, exit separation, key-placement distance,
landmarks, artifacts, and compass placement for the reduced area.
- Preserve the rule that finding the key is no easier than the subsequent
search for an exit under the project's deterministic balance metric.
- Update player and observer presentation requirements for a square map.
- Update protocol bounds, tests, diagnostics, and balance documentation that
depend on the former `128 x 64` default.
- Revalidate memory, static-asset, and firmware partition budgets.
### Deliverables
- `32 x 32` authoritative maze generation and gameplay configuration.
- Rebalanced object placement and deterministic balance thresholds.
- Updated canonical gameplay, architecture, UI, generation, balance, and MVP
acceptance documentation.
- Updated host and browser tests for the compact square map.
### Acceptance criteria
1. The default authoritative map is exactly `32 x 32`, containing 1,024 cells,
and no production default remains dependent on `128 x 64` dimensions.
2. A 1,024-seed deterministic sweep produces connected, centrally symmetric
maps with valid starts, exits, objects, and no generation failure.
3. Rebalanced key and exit placement preserves the invariant that key
discovery is no easier than the subsequent exit search under the documented
deterministic metric.
4. Object counts and cycle density are scaled for the smaller map and remain
bounded, symmetric where required, reachable, and free of collisions.
5. Player and observer views render the complete square map correctly at the
responsive sizes defined in `docs/UI_STYLE.md`.
6. All host and browser tests pass, and firmware plus web images build within
their partition budgets.
7. The project owner confirms through representative two-player testing that
the compact map remains fair, understandable, and free of gameplay-blocking
defects, or explicitly approves an evidence deviation.
### Evidence
Implementation is complete and host-verified; representative two-player
confirmation remains pending.
- On 2026-09-08, `bash scripts/run_host_tests.sh` passed every health, Wi-Fi,
NVS, provisioning, lobby/protocol, maze, game, disconnect-recovery, browser,
and web-asset suite. The deterministic 1,024-seed sweep generated connected,
centrally symmetric `32 x 32` maps without failure, reported cycles
`19..19`, dead ends `74..128`, start-to-key distances `93..422`, key-to-exit
distances `84..352`, start-to-exit distances `26..242`, at most two
generation attempts, and preserved `start_to_key >= key_to_exit` for every
seed. Fixed maze plus workspace storage measured 5,596 of 65,536 bytes.
- The default configuration now uses 1,024 cells, two cross-half connection
pairs, eight extra cycle pairs, 22-cell minimum exit separation, two
landmark pairs, and one vision-artifact pair. The three-cell base view,
ten-successful-move artifact effect, 150 ms movement cooldown, compass rule,
and 200 ms draw window remain unchanged. Observer serialization passed with
1,024 cells, 12 objects, 88 bounded chunks, and a 490-byte largest chunk.
- The browser tests passed with explicit `32 x 32` dimension bounds and a
square-map fit scale. Web-asset reproduction reported SHA-256
`3461d20742e1999c1489465d7f3a68317d56b3acfc5ac5003341e1cf2d478eed`
using 20,314 of 1,572,864 allowed compressed bytes.
- A clean `pio run -e esp32-c6-devkitm-1` build passed for application version
`0.14.0`, using 58,868 of 327,680 RAM bytes and 1,027,934 of 2,097,152
application bytes. `firmware.bin` measured 1,028,560 bytes with SHA-256
`428e7690bed50d30caf61aca174d838f00d7de1a603f14ea669e996e3d167123`.
- `pio run -e esp32-c6-devkitm-1 --target buildfs` passed. The fixed LittleFS
image measured 2,031,616 of 2,031,616 partition bytes with SHA-256
`467f0a07413c83bff5ea246b5063289eb61f8bf40b95d16d639b3241ceda8a9d`.
- Acceptance criteria 1--6 have concrete evidence. Criterion 7 requires the
project owner's representative two-player confirmation or an explicitly
approved deviation, so Milestone 014 remains `IN_PROGRESS`.
- On 2026-09-08, the project owner confirmed representative two-player testing
of the compact `32 x 32` map: gameplay is fair and understandable, all
expected flows work as intended, and no gameplay-blocking defect was found.
Together with the deterministic sweep, browser, host, firmware, and
filesystem evidence above, this satisfies every Milestone 014 acceptance
criterion. Milestone 014 is `COMPLETE`; Milestone 015 remains `NOT_STARTED`.
## Milestone 015 — Pre-reader child-friendly game experience
**Status:** `COMPLETE`
### Objective
Redesign the mobile player experience so that children of early primary-school
age and younger can understand and enjoy the game without needing to read,
while preserving server authority, hidden-state protection, and all gameplay
rules.
### Scope
- Replace text-dependent status comprehension with large, distinct pictograms,
shapes, colors, motion, and optional local sound cues.
- Give each player a friendly visual character and color identity instead of
relying on the labels `P1` and `P2`.
- Enlarge and dynamically frame the visible maze so that the playable area is
the dominant mobile-screen element, including when the player is near a map
edge and receives fewer visible cells.
- Add a large on-screen joystick designed for one-thumb control. Its movable
circular handle follows the thumb within a bounded base, includes a forgiving
dead zone, and returns visibly to the center when released.
- Map joystick direction to the four existing orthogonal movement commands and
map displacement magnitude to a bounded command-repeat rate: a small
intentional displacement walks slowly, while a larger displacement moves at
the fastest server-approved rate. The browser never bypasses the
authoritative movement cooldown or predicts movement locally.
- Retain swipe and keyboard input as equivalent optional alternatives without
displaying the existing directional-arrow buttons during normal mobile play.
- Introduce a short, replayable, wordless onboarding sequence that demonstrates
movement, walls, keys, exits, compasses, vision artifacts, and meeting the
other player through animation and guided interaction.
- Communicate pickup, blocked movement, compass direction, enhanced vision,
opened exits, victory, defeat, draw, pause, and reconnection with immediate
icon-first animation and sound feedback.
- Keep concise Russian text as an optional secondary aid for readers and
accessibility technology, never as the only way to understand a required
action or game state.
- Provide visible mute and reduced-motion controls, avoid rapid flashes, and
ensure that meaning never depends on color alone.
- Preserve bounded local assets, offline operation, responsive behavior, and
the existing authoritative command and projection contracts.
### Deliverables
- Updated child-oriented visual language and mobile layout specification in
`docs/UI_STYLE.md`.
- Large icon-first player HUD, maze viewport, and touch-control design.
- Friendly player characters and locally stored visual/audio feedback assets.
- Wordless interactive onboarding that demonstrates dragging, holding, and
releasing the joystick, plus a replay control.
- Deterministic browser tests and viewport-based visual regression evidence.
- Facilitated usability-test guide for pre-readers and early readers.
### Acceptance criteria
1. At the canonical `412 x 916` mobile viewport, the visible maze is the
dominant element and uses the available play area without a large inactive
void around the current projection.
2. Movement is possible through an always-visible on-screen joystick with a
touch area of at least `160 x 160` CSS pixels. Its handle tracks a mouse,
touch, or pointer drag, remains bounded within the base, and snaps to the
center on release, cancellation, pause, disconnect, or loss of focus.
3. Joystick displacement maps monotonically to a documented bounded repeat
rate: increasing displacement never produces slower movement, the dead zone
sends no command, and maximum displacement cannot exceed the authoritative
server movement limit. Direction is resolved to exactly one of the four
existing orthogonal commands, with hysteresis preventing rapid direction
flicker near diagonal boundaries.
4. A child can distinguish their character, carried key, enhanced vision,
compass direction, exit state, blocked movement, and match result without
reading text or recognizing `P1`/`P2`.
5. First-time onboarding demonstrates joystick drag distance, direction,
release-to-stop, walls, and every required interaction through animation and
guided input; it can be replayed and can be skipped by an adult.
6. Essential state uses at least two visual channels such as icon plus shape,
motion, or pattern; color is never the sole signal.
7. Optional sound feedback has an obvious icon-only mute control, and reduced
motion preserves all information without rapid flashing or disorienting
effects.
8. Player-facing text remains Russian, semantic labels remain available to
assistive technology, and the interface does not require reading for normal
play.
9. Browser tests cover joystick dead-zone, displacement-to-rate mapping,
direction hysteresis, release and cancellation, icon-driven state
transitions, onboarding, mute, reduced motion, and the supported mobile
viewport; all existing host tests continue to pass.
10. The compressed local web payload remains within the 1.5 MiB asset budget
and the LittleFS partition, without internet or third-party runtime assets.
11. Facilitated observation with representative pre-readers confirms that they
can discover the joystick, move slowly and quickly, stop reliably, react to
a wall, recognize a key objective, and follow a compass cue without spoken
reading instructions, or the project owner explicitly approves a documented
evidence deviation.
### Evidence
The implementation and automated verification are complete. The project owner
approved the required evidence deviation after manual gameplay, as recorded
below.
- The player screen now uses circle and diamond character shapes instead of
`P1`/`P2`, icon-and-shape ownership markers for keys and exits, dynamic
projection sizing from 32 to 72 CSS pixels per cell, a 180 CSS-pixel
joystick (172 on phones), and an icon-only visible mute, reduced-motion, and
onboarding-replay control group. The mobile directional-button cluster stays
hidden while keyboard and swipe input remain available.
- The joystick clamps its handle to the circular base, has a 20-pixel dead
zone and 18% axis hysteresis, emits only cardinal movement intentions, and
schedules repeats from 360 to 170 ms. It resets on pointer release or
cancellation, pause, disconnect, browser blur, and hidden-document events;
the fastest local repeat remains above the 150 ms authoritative cooldown.
- First active player projection now opens an eight-step, icon-first Russian
walkthrough for drag, release, walls, key, vision, compass, exit, and the
other player. It can be replayed or skipped locally. The new
`docs/PRE_READER_USABILITY.md` records the final privacy-safe observation
protocol without collecting child-identifying data.
- On 2026-09-09, `bash scripts/run_host_tests.sh` passed every host suite,
including 15 browser tests. The focused browser coverage verifies joystick
cardinal direction and bounded travel, dead zone, monotonic repeat rate,
hysteresis, dynamic mobile cell sizing, onboarding bounds, reset paths,
mute and reduced-motion controls, local assets, Russian UI, and existing
protocol/observer constraints. Web asset reproducibility reported SHA-256
`13f95418169fb38d277d86f3324a4dbc3fa8b5e5e2e9fb7bde9439aa2f7b75e4`
using 23,950 of 1,572,864 allowed compressed bytes.
- On 2026-09-09, `pio run -e esp32-c6-devkitm-1` passed with 58,868 of
327,680 RAM bytes and 1,027,934 of 2,097,152 application bytes. Its
`firmware.bin` SHA-256 was
`87cfffaaa9b119794707c9caedc3da7f3f015d540fc1149d74e7cc0a1ecd2841`.
`pio run -e esp32-c6-devkitm-1 --target buildfs` passed; the fixed LittleFS
image used 2,031,616 of 2,031,616 bytes and SHA-256
`ea6813e603d883a1f6b79a9f9efbd20e7d02f010052210716a89d7e863716415`.
- On 2026-09-09, the project owner manually exercised the gameplay and
confirmed that all host, firmware, and filesystem checks above pass. The
owner also found the separate on-screen joystick imitation uncomfortable and
insufficiently responsive for real play. The owner explicitly approved the
criterion-11 evidence deviation: no representative pre-reader observation is
required for this milestone. Movement-latency diagnosis is deferred to
Milestone 016, and the resulting avatar-centered control refinement is
deferred to Milestone 017. Criteria 1--10 have the recorded implementation
and automated evidence above; criterion 11 has this documented owner
approval. Milestone 015 is therefore `COMPLETE`.
## Milestone 016 — End-to-end movement latency diagnosis
**Status:** `COMPLETE`
### Objective
Measure and attribute the delay from a player's touch to the corresponding
authoritative map update, determine whether the unacceptable delays originate
in the local Wi-Fi network, browser input and rendering, ESP firmware, or their
interaction under load, and produce evidence that selects the remediation for
Milestone 017.
### Scope
- Define separate latency intervals for local pointer feedback, browser command
scheduling, WebSocket uplink, ESP receive/parse/game processing, outbound
queueing and serialization, WebSocket downlink, complete-view assembly, DOM
update, and the next painted frame.
- Add bounded diagnostic correlation data and timing counters where existing
metrics cannot isolate an interval. Diagnostics must not make browser time
authoritative, expose hidden maze state, or create unbounded logs, queues,
allocations, or protocol messages.
- Measure ICMP and application-level round-trip baselines on the same local
Wi-Fi path, including distribution and jitter rather than only an average.
- Exercise accepted movement, blocked movement, direction changes, sustained
movement, release, and cooldown rejection with two active players, first
without observers and then at the six-session limit with four observers.
- Compare WebSocket acknowledgement time with the time at which the matching
authoritative view has been completely assembled and painted. Record view
chunk count, socket buffered bytes, gateway queue depth, send duration, and
browser parse, layout, and paint cost for the same test intervals.
- Profile the current sequential per-session view broadcast and the browser's
complete maze DOM replacement without changing their behavior during the
baseline run.
- Classify each observed delay by its dominant interval, document reproducible
test conditions, and select bounded frontend, protocol, firmware, and/or
network remediations for Milestone 017.
### Deliverables
- A documented pointer-to-paint latency model with unambiguous measurement
points and command/view correlation.
- A repeatable local-device measurement procedure and bounded diagnostic
instrumentation for browser and firmware.
- Results for baseline and maximum supported session load, including latency
percentiles, maxima, network jitter, queue high-water marks, view sizes, and
browser rendering costs.
- A root-cause report that ranks measured contributors and records the selected
remediation scope and performance budget for Milestone 017.
- Automated tests for correlation, bounded diagnostics, counter reset/wrap
behavior, and preservation of authoritative and hidden-state contracts.
### Acceptance criteria
1. Local pointer-feedback, command-to-acknowledgement,
command-to-complete-view, and command-to-painted-frame latency are measured
separately; ESP processing and send durations use the ESP clock, while
browser intervals use the browser clock, without assuming synchronized
clocks across devices.
2. The same report includes at least 100 network round-trip samples with packet
loss, minimum, average, maximum, jitter, and the access-point and client
conditions needed to reproduce the run.
3. Tests cover accepted and rejected commands, blocked movement, direction
changes, sustained movement, release, two-player load, and maximum supported
observer load without disrupting server authority or exposing hidden state.
4. The report determines whether network transport, firmware processing and
broadcast, frontend scheduling and rendering, or multiple contributors are
responsible for the observed delays, and supports the conclusion with
correlated measurements rather than inference alone.
5. Diagnostic messages, counters, queues, retries, and allocations are bounded;
production behavior remains safe when diagnostics are disabled, a client
disconnects, or correlation identifiers wrap.
6. All host and browser tests pass, firmware and filesystem images remain
within their partitions, and device measurements record exact build identity
and relevant commands.
### Evidence
Implementation and initial device validation are complete. The following
planning-time observations justify the milestone but do not satisfy its
controlled measurement criteria:
- On 2026-09-09, the project owner reported intermittent input delays up to
approximately 500 ms and approved active browser and WebSocket measurements
against the device at `192.168.1.50`.
- A live `/diagnostics` sample with two connections and 24 accepted game
commands reported no dropped, failed, malformed, oversized, or rate-limited
messages; maximum handler time was 7,800 microseconds and maximum send time
was 1,571 microseconds. This low-load sample does not measure end-to-end
latency or rule out sequential broadcast delays under session load.
- A 20-packet ICMP sample to `192.168.1.50` had zero loss but measured
`60.673 / 147.866 / 246.995 ms` minimum/average/maximum round-trip time with
`49.350 ms` deviation. The network alone therefore already exceeds the ideal
100 ms target in this setup, but correlation is still required before
assigning the reported 500 ms delays.
- Static inspection found that each accepted movement synchronously broadcasts
authoritative view chunks to sessions, while the player browser waits for a
complete view and replaces the maze DOM before the updated frame is visible.
These are candidate contributors to measure, not yet established root causes.
- The browser now stores at most 256 local movement samples and correlates each
sent movement request with its acknowledgement's authoritative
`stateSequence`, the complete matching view, and the next painted frame.
The gateway includes that state sequence in accepted game acknowledgements;
no browser timestamp is sent to the ESP and no hidden state is added to a
player projection. `docs/M016_LATENCY_PROCEDURE.md` specifies the required
controlled runs and interval boundaries.
- On 2026-09-09, the focused browser test plus `bash scripts/run_host_tests.sh`
passed with 16 browser tests, including bounded latency correlation. Firmware
and filesystem builds passed: application usage was 1,027,966 of 2,097,152
bytes and the fixed LittleFS image used 2,031,616 of 2,031,616 bytes.
- On 2026-09-09, the diagnostics firmware and filesystem image were flashed to
the confirmed ESP32-C6FH4 on `/dev/ttyACM0`; the health endpoint reported
`serving`. A six-session smoke run recorded 10 accepted commands. Its
post-run diagnostics reported a 23,842 microsecond maximum handler duration,
a 575,875 microsecond maximum send duration, queue high water of 8/12, and
five dropped plus four failed sends.
- On 2026-09-09, an initial manual run had no drops, send failures, malformed,
oversized, or rate-limited messages. Before the run, diagnostics reported
8 accepted commands, a 10,779 microsecond maximum handler duration, and a
1,591 microsecond maximum send duration. Afterwards, it reported 165
accepted commands, a 9,800 microsecond maximum handler duration, a
1,572 microsecond maximum send duration, queue high water of 4/12, and no
message drop or failure. One browser retained 65 correlated samples: its
command-to-acknowledgement values ranged from 19 to 548 ms and its
command-to-painted-view values ranged from 30 to 551 ms. An interrupted
18-packet ICMP sample had zero loss but measured
`35.904 / 121.461 / 219.597 ms` minimum/average/maximum round trip with
`51.715 ms` deviation. These partial data point to Wi-Fi transport and/or
browser scheduling rather than the measured ESP handler or send path.
- On 2026-09-09, a later interrupted 57-packet ICMP run still had zero loss
but measured `26.403 / 114.039 / 343.368 ms` minimum/average/maximum round
trip with `54.637 ms` deviation. A complete 100-request `/health`
application-round-trip run measured 7.5 ms minimum, 186.7 ms average,
508.0 ms p95, 713.3 ms p99, and 985.4 ms maximum. The M010 six-session
script again timed out waiting for the first player `join` response, so it
produced no valid application-load samples. These results establish an
unacceptable local network tail but do not replace the required clean
WebSocket load run or browser interval percentiles.
- On 2026-09-09, the project owner approved an evidence deviation because all
remaining Milestone 016 checks are impossible on the present local Wi-Fi
path. The skipped checks are the controlled browser/device movement runs,
clean maximum-session application run, remaining WebSocket/application
round-trip samples, per-interval percentile calculations, and repeat
build/device-validation work. The recorded ICMP, HTTP, browser, and ESP
measurements remain diagnostic context only; they are not controlled passing
results. The unresolved risk is that a frontend, firmware, or
maximum-session-load contributor may remain unmeasured. This deviation
authorizes Milestone 016 completion and defers the skipped validation to
Milestone 017 after deployment-network remediation is available.
## Milestone 017 — Avatar-centered drag steering and latency remediation
**Status:** `COMPLETE`
### Objective
Replace the separate on-screen joystick with direct dragging of the centered
player avatar, keep the avatar's small local deflection as immediate wordless
direction and speed feedback while the authoritative map moves underneath it,
and apply the measured Milestone 016 remediations so normal local play feels
immediate and never approaches the observed 500 ms delay.
### Scope
- Make the visible centered player avatar the movement handle, backed by a
generous transparent touch target that does not obscure nearby maze cells.
- Keep the avatar visually near the viewport center. During a drag, offset it
only within a small bounded radius in the drag direction; on release or any
reset condition, animate it back to the exact center.
- Convert drag angle to exactly one of the four existing cardinal movement
intentions with a dead zone and diagonal hysteresis. Map displacement
magnitude monotonically to a bounded movement cadence: a small intentional
offset moves slowly and a larger offset approaches, but never exceeds, the
authoritative 150 ms cooldown.
- Move the maze beneath the centered avatar only when server-approved state is
received. Local avatar deflection is input feedback and must never predict a
successful cell transition, inventory change, visibility change, or result.
- Coalesce rapidly changing movement intent and bound in-flight commands so a
slow socket, delayed acknowledgement, direction reversal, or release cannot
leave stale movement queued in the browser or gateway.
- Apply the frontend, protocol, firmware, and/or deployment-network changes
selected by Milestone 016. Optimize the measured bottlenecks, including view
assembly, rendering, or broadcast only when supported by its evidence.
- Preserve keyboard and swipe alternatives, observer read-only behavior,
hidden-state protection, Russian accessibility labels, mute and reduced
motion behavior, and all pause, disconnect, and focus-loss reset paths.
- Update the canonical UI and pre-reader usability documents and validate the
interaction on representative mobile touch hardware.
### Deliverables
- Avatar-centered drag control with bounded deflection, direction hysteresis,
displacement-based speed, release-to-center animation, and safe reset paths.
- Authoritative map-follow behavior and bounded latest-intent command flow.
- The evidence-selected latency optimizations across browser, protocol,
firmware, and/or documented network setup.
- Updated `docs/UI_STYLE.md`, `docs/PRE_READER_USABILITY.md`, Russian semantic
labels, deterministic browser tests, host tests, and device test procedure.
- Before-and-after latency report using the Milestone 016 measurement method.
### Acceptance criteria
1. At the canonical `412 x 916` viewport, touching the player avatar or its
transparent target starts control without a separate joystick. The target is
at least `96 x 96` CSS pixels, the visible avatar remains unobscured, and its
center cannot move more than the documented small deflection radius.
2. Avatar deflection follows the pointer by the next rendered frame during
normal operation, indicates the resolved direction and relative speed
without text, and returns to the exact center on release, cancellation,
pause, disconnect, blur, hidden-document events, and lost pointer capture.
3. The map moves beneath the near-centered avatar only from server-approved
projections. The browser never predicts maze position, collision outcome,
inventory, visibility, timers, or match results.
4. Deflection magnitude maps monotonically to a documented bounded cadence,
the dead zone sends no movement, diagonal hysteresis prevents direction
flicker, and the fastest cadence cannot bypass the 150 ms server cooldown.
5. At most one bounded current movement intention is awaiting completion or
replacement per player. Direction changes and release discard stale local
intent so delayed network delivery cannot create a long client-side command
backlog.
6. In the controlled local Wi-Fi test conditions established by Milestone 016,
accepted input-to-authoritative-painted-update latency is at most 100 ms at
`p95` and at most 150 ms for the maximum sample in both the two-player case
and the two-player/four-observer case. No accepted sample reaches 500 ms.
7. If the deployment network cannot meet criterion 6 before application work,
the documented network remediation is applied and the full test is repeated;
application latency is reported separately so network improvements cannot
conceal a frontend or firmware regression.
8. Browser tests cover pointer capture, touch targeting, bounded offset,
dead-zone and speed mapping, diagonal hysteresis, stale-intent coalescing,
every reset path, authoritative map motion, keyboard and swipe alternatives,
reduced motion, and supported mobile viewports.
9. All host tests pass, firmware and web images remain within partition budgets,
and the latency instrumentation records no dropped messages, unbounded queue
growth, hidden-state disclosure, or observer mutation under maximum supported
session load.
10. The project owner confirms in representative mobile gameplay that a child
can discover movement by dragging the avatar, understand direction and
relative speed from its deflection, stop reliably, and experience responsive
control, or explicitly approves a documented evidence deviation.
### Evidence
Implementation and automated validation are in progress. This milestone records
the project owner's 2026-09-09 decisions: the child drags the centered player
icon; the icon deflects slightly as the control handle while the authoritative
map moves below it; displacement controls speed; approximately 100 ms is ideal,
150 ms is acceptable, and delays near 500 ms are unacceptable.
- On 2026-09-09, the browser control was changed from a separate joystick to a
96 by 96 CSS-pixel transparent target around the centered avatar. The visible
avatar deflects at most 28 CSS pixels, uses a 12-pixel dead zone and diagonal
hysteresis, and schedules cardinal repeats from 360 ms to 170 ms. It resets
on release, cancellation, lost pointer capture, pause, disconnect, blur, and
hidden-document events. `LatestIntentFlow` keeps one sent move in flight and
retains at most one replacement intention; release discards the replacement.
The map continues to render only complete server-authoritative projections.
- On 2026-09-09, `node --test test/web/test_player_app.js` passed all 17 tests,
including avatar bounds, cadence, direction hysteresis, latest-intent
coalescing, reset paths, authoritative projections, keyboard/swipe
alternatives, observer immutability, and bounded diagnostics. `bash
scripts/run_host_tests.sh` passed all host tests and deterministic web-asset
verification (`2ca96e65b3aa06385028c420c7e605b2e422f7d0c57cae2d81e277a53ecdf6b3`,
25,121 of 1,572,864 allowed compressed bytes).
- On 2026-09-09, `/home/sasa/.platformio/penv/bin/pio run -e
esp32-c6-devkitm-1` passed with 58,868 of 327,680 RAM bytes and 1,027,966 of
2,097,152 application bytes. `/home/sasa/.platformio/penv/bin/pio run -e
esp32-c6-devkitm-1 --target buildfs` passed and reported a fixed LittleFS
image of 2,031,616 of 2,031,616 bytes.
- On 2026-09-09, an edge-projection correction translated the received maze so
the authoritative `self` cell remains below the centered avatar. Drag
displacement now starts at pointer-down, preventing a touch near the
transparent target edge from selecting an unintended direction. `bash
scripts/run_host_tests.sh` passed with 18 browser tests and deterministic
web-asset verification (`907d0323bd48120dd51034014df6bb21de375631adf9559af88d16a52f9958cf`,
25,346 of 1,572,864 allowed compressed bytes). Firmware and filesystem
rebuilds passed with 58,868 of 327,680 RAM bytes, 1,027,966 of 2,097,152
application bytes, and a 2,031,616-byte LittleFS image.
## Milestone 018 — Mobile directional controls and viewport refinement
**Status:** `COMPLETE`
### Objective
Add a familiar, always-visible four-direction control cluster to the mobile
player view and refine the compact layout so a player can move without covering
the centered avatar or the most useful part of the maze with their finger.
### Scope
- Before implementation, reconcile `MVP.md`, `ARCHITECTURE.md`, and
`docs/UI_STYLE.md` with the project owner's decision to show directional
buttons during normal phone play. This explicitly supersedes the current
phone-specific rule that hides the directional-button cluster.
- Present four separate up, down, left, and right buttons as a conventional
cross-shaped directional pad in the lower-right control zone. Keep the
center of the cross inactive so diagonal input cannot be implied.
- Give every direction a large, distinct touch target, Russian accessible
label, and visible normal, pressed, disabled, and pending states. Prevent
browser scrolling, text selection, double-tap zoom, and stuck input during
normal button use.
- Send exactly one of the four existing orthogonal movement intentions from
each button interaction. Reuse the bounded latest-intent flow and server
cooldown; the browser must not predict movement or create a second movement
authority path.
- Retain avatar drag, directional swipe, and keyboard movement as optional
equivalent inputs. Directional buttons become the obvious default touch
control without removing the alternatives completed in earlier milestones.
- Rebalance the compact player HUD, maze viewport, key-drop action, secondary
controls, and transient notices so the map remains dominant and the player,
nearby maze cells, and important feedback remain visible while a thumb rests
on the directional pad.
- Keep the directional pad and key-drop action in separate lower-screen zones,
respect display safe areas and handed reach, and avoid overlap between
movement, drop-key, mute, reduced-motion, onboarding, or connection controls.
- Eliminate page-level scrolling and accidental overflow in supported portrait
and landscape player layouts, including the live approximately `412 x 915`
phone viewport as well as the canonical `412 x 916` validation viewport.
- Preserve Russian player-facing copy, observer read-only behavior,
hidden-state protection, local assets, reduced-motion behavior, and the
existing firmware and LittleFS budgets.
### Deliverables
- Updated mobile-control and responsive-layout contracts in `MVP.md`,
`ARCHITECTURE.md`, and `docs/UI_STYLE.md`.
- Responsive four-button directional pad integrated with the existing bounded
movement-intent path.
- Refined compact HUD, maze, key-drop, utility-control, and notice layout for
portrait and landscape mobile play.
- Russian accessible names and complete visual interaction states for all four
direction buttons.
- Deterministic browser tests and representative mobile-browser visual and
touch verification evidence.
### Acceptance criteria
1. At `412 x 915` and `412 x 916` CSS pixels in portrait orientation, all four
directional buttons, the key-drop action, essential player status, and the
maze are visible without page-level horizontal or vertical scrolling.
2. The directional buttons form an immediately recognizable cross with a
non-interactive center. Each visible button has a touch target of at least
`48 x 48` CSS pixels, remains inside the display safe area, and does not
overlap another control at any supported viewport.
3. A thumb operating any directional button does not cover the centered player
avatar. The current cell and enough adjacent maze context to understand the
next orthogonal move remain visible during button use.
4. Pressing or holding up, down, left, or right produces only the matching
existing cardinal movement intention through the bounded latest-intent
flow. Release, cancellation, pause, disconnect, blur, hidden-document
events, and lost pointer capture stop or clear button-generated intent.
5. Button input obeys the authoritative movement cooldown, never predicts a
successful move, and remains behaviorally equivalent to avatar drag, swipe,
and keyboard input for accepted and rejected movement.
6. Normal, pressed, disabled, and pending states are visually distinct without
relying on color alone. The buttons expose the Russian accessible labels
`Двигаться вверх`, `Двигаться вниз`, `Двигаться влево`, and
`Двигаться вправо`.
7. Button use does not cause page scrolling, text selection, double-tap zoom,
a context menu, diagonal commands, duplicate command streams, or movement
that continues after release.
8. The refined HUD and overlays keep the map as the dominant screen region;
key-drop, mute, reduced-motion, onboarding, connection state, errors, and
transient notices remain reachable and do not obscure one another in
portrait or landscape orientation.
9. Browser tests cover all four directions, tap and hold behavior, pointer and
touch cancellation, reset paths, cooldown and latest-intent bounds,
coexistence with drag/swipe/keyboard input, accessibility labels, safe-area
layout, control overlap, and zero page overflow at supported viewports.
10. All host tests pass, compressed web assets remain within the 1.5 MiB budget
and LittleFS partition, firmware remains within the factory partition, and
no player projection or observer-authority contract changes.
11. The project owner confirms on a representative mobile browser that the
four buttons are comfortable and familiar, the central play area stays
readable while moving, every essential control fits on screen, and normal
play no longer requires dragging the avatar, or explicitly approves a
documented evidence deviation.
### Evidence
Implementation and automated validation are in progress. The following
planning-time observation motivates the milestone:
- On 2026-09-10, the project owner reported that dragging the centered player
is unfamiliar and uncomfortable because the finger hides useful maze space,
and requested visible up, down, left, and right buttons plus a better-fitting
mobile layout. Read-only inspection of the live player page at approximately
`412 x 915` CSS pixels found a `96 x 96` centered avatar drag target, four
directional buttons present but hidden, and a document height of 923 CSS
pixels, slightly exceeding the 915 CSS-pixel viewport. Implementation and
completion verification are deferred until Milestone 017 is `COMPLETE`.
- On 2026-09-10, `MVP.md`, `ARCHITECTURE.md`, and `docs/UI_STYLE.md` were
reconciled to make the four-button lower-right directional pad the primary
phone control while preserving avatar drag, swipe, and keyboard alternatives.
The inactive centre prevents diagonal input. Each button is 62 by 62 CSS
pixels on compact phones (and at least 52 by 52 CSS pixels in compact
landscape), has Russian accessible text, and uses pointer capture plus a
170 ms bounded hold repeat through the existing latest-intent flow. The
compact application viewport is explicitly clipped to `100dvh` to prevent
page scrolling; the lower-left key action, lower-right pad, top-right utility
controls, and raised notices occupy separate zones.
- On 2026-09-10, `node --test test/web/test_player_app.js` passed all 20
browser tests. `bash scripts/run_host_tests.sh` passed all host tests and
deterministic web-asset verification
(`ecf9429fd873e27df67774616ec68684df19d88cce6913d53b0764b53e8e95cd`,
25,939 of 1,572,864 allowed compressed bytes). `/home/sasa/.platformio/penv/bin/pio
run -e esp32-c6-devkitm-1` passed with 58,868 of 327,680 RAM bytes and
1,027,966 of 2,097,152 application bytes. The matching `--target buildfs`
command passed and produced the fixed 2,031,616-byte LittleFS image.
## Milestone 019 — Reference-derived SVG icon system
**Status:** `COMPLETE`
### Objective
Replace the current player and gameplay pictograms with a coherent local SVG
set derived from the project owner's supplied visual reference, and establish
yellow and blue as the two unmistakable player identities across players,
matching keys, and matching exits.
### Scope
- Before changing application markup, styling, or rendering, manually recreate
the supplied raster-reference silhouettes as clean, editable SVG artwork.
Do not embed, trace at runtime, or ship the raster reference itself.
- Create SVG symbols for the six supplied concepts: running player, key,
closed arched exit, vision crystal, opened arched exit with outward arrow,
and blocked-movement spikes.
- Treat the reference as visual direction rather than a source of product
requirements. Map its artwork only to existing maze32 gameplay states; the
spikes communicate a rejected move or wall collision and must not introduce
a trap, damage, or any other new mechanic.
- Review and approve the standalone SVG set before using it to replace icons in
the browser application. Keep source paths understandable, view boxes
consistent, silhouettes legible at gameplay sizes, and all colors controlled
through project-owned CSS or explicit documented SVG tokens.
- Reconcile `MVP.md`, `ARCHITECTURE.md`, `docs/UI_STYLE.md`, and supporting UI
documentation before implementation. Replace the current blue/pink identity
contract with Player 1 yellow and Player 2 blue across every player, key,
exit, status, map, onboarding, result, and observer presentation.
- Give each player's matching key and exit the same yellow or blue ownership
identity as its owner. Preserve different silhouettes and state treatments
for players, keys, closed exits, and opened exits so meaning never depends on
color alone.
- Replace the applicable current pictograms in the player HUD, maze, key-drop
control, onboarding, transient feedback, results, and observer view with the
approved SVG symbols. Retain or restyle icons not represented by the supplied
sample only as needed to keep the complete icon family visually coherent.
- Preserve Russian accessible labels, icon-first comprehension, reduced-motion
behavior, server authority, hidden-state rules, offline operation, and the
existing compressed-asset and Flash budgets.
- Verify that yellow remains distinguishable from warning states and blue from
pause states by pairing semantic colors with shape, outline, animation, or
labels, as appropriate.
### Deliverables
- A project-owned, editable SVG source set for the player, key, closed exit,
vision crystal, opened exit, and blocked-movement symbols.
- A documented mapping from each reference-derived SVG symbol and yellow/blue
variant to existing maze32 entities and UI states.
- Updated color and icon contracts in `MVP.md`, `ARCHITECTURE.md`,
`docs/UI_STYLE.md`, and affected supporting UI documentation.
- Replacement icons in player, observer, onboarding, HUD, feedback, and result
presentations without embedded raster images or external dependencies.
- Deterministic SVG validation, browser tests, asset-budget checks, and
representative mobile and desktop visual-verification evidence.
### Acceptance criteria
1. Before application icon replacement begins, the six reference-derived SVG
sources exist as editable vector paths, pass XML validation, have consistent
documented view boxes, contain no embedded raster data, and are approved by
the project owner against the supplied visual reference.
2. The running figure, key, closed arched exit, faceted vision crystal, opened
arched exit with outward arrow, and spikes remain recognizable at every
rendered application size without relying on accompanying text.
3. Player 1 is yellow and Player 2 is blue in every player and observer view.
Each player's key and exit carry the matching ownership color consistently,
including ground, inventory, closed, opened, onboarding, and result states.
4. Player, key, closed exit, and opened exit remain distinguishable by both
silhouette and state treatment in monochrome inspection; color is never the
only way to identify object type, ownership, or exit state.
5. The spikes appear only as blocked-movement or wall-collision feedback. No
trap object, damage rule, hidden-state field, protocol message, or server
state is added.
6. Yellow player identity cannot be confused with warnings, and blue player
identity cannot be confused with pause state: each conflicting semantic role
has an additional icon, outline, pattern, motion, or Russian label.
7. All applicable current player, key, exit, vision-artifact, and
blocked-movement pictograms are replaced across the maze, HUD, key-drop
action, onboarding, transient feedback, results, and observer presentation;
no obsolete duplicate icon remains visible in a reachable state.
8. SVG symbols render without clipping, distortion, unexpected inherited
color, or loss of contrast at the canonical `412 x 916` player viewport,
compact landscape, representative tablet and desktop player viewports, and
observer viewports of `1024 x 768` or larger.
9. Player-facing accessible labels remain in Russian, the icon-first onboarding
remains understandable, reduced motion preserves all state information, and
the application loads every icon locally without an internet dependency.
10. Automated checks validate SVG structure, required symbol identifiers,
yellow/blue ownership mapping, all closed/open and carried/ground variants,
absence of embedded raster content, blocked-feedback semantics, and the
removal of obsolete reachable icons.
11. All host and browser tests pass, the compressed web payload remains within
the 1.5 MiB budget and LittleFS partition, and firmware remains within the
factory partition without changing authoritative game or projection data.
12. The project owner confirms on representative mobile and desktop browsers
that the new icons match the supplied examples closely enough, yellow and
blue ownership is immediately understandable, keys and doors are assigned
to the correct player, and every icon is clear at normal gameplay size, or
explicitly approves a documented evidence deviation.
### Evidence
Implementation and automated verification are in progress. The following
evidence satisfies the source, integration, and automated acceptance criteria;
the project-owner visual confirmation remains outstanding:
- On 2026-09-10, the project owner supplied a raster visual reference containing
a yellow running figure, yellow key, red closed arched door, cyan faceted
crystal, green opened arched door with an outward arrow, and red spikes. The
owner requested that local SVG artwork be created from these examples before
icon replacement, and selected yellow and blue as the two player colors.
- On 2026-09-10, `web/icons.svg` was replaced with project-owned editable SVG
symbols `player`, `key`, `exit-closed`, `crystal`, `exit-open`, and `spikes`.
Each reference-derived symbol uses the documented `64 x 64` view box and
`currentColor`; the sprite contains no `<image>`, `foreignObject`, or embedded
raster data. `xmllint --noout web/icons.svg` passed.
- The player, key, closed/opened exit, vision-artifact, blocked-movement,
onboarding, avatar, lobby, observer, and result renderers now use the new
SVG symbols where the supplied reference applies. Player 1 is yellow
(`#ffd33d`) and Player 2 is blue (`#41b6ff`); their keys and exits share the
matching ownership color, while dashed secondary treatments and distinct
silhouettes preserve non-color identification. Amber warnings and
periwinkle pause semantics remain distinct from player identities. Spikes
are used only in the client-side blocked-movement notice.
- `node --test test/web/test_player_app.js` passed all 21 tests, including the
new deterministic SVG-structure, ownership-color, local-asset, and
blocked-feedback checks. `bash scripts/run_host_tests.sh` passed all host
suites and deterministic asset verification
(`c7f3bd8217e51e81b46eaeaf2636f5cd1cf85a7d8e27b07cb5293ea97a1f3f41`,
26,510 of 1,572,864 allowed compressed bytes).
- `/home/sasa/.platformio/penv/bin/pio run -e esp32-c6-devkitm-1` passed with
58,868 of 327,680 RAM bytes and 1,027,966 of 2,097,152 application bytes.
The matching `--target buildfs` command passed and reported the fixed
2,031,616-byte LittleFS image within its partition.
- On 2026-09-10, `pio run -e esp32-c6-devkitm-1 --target uploadfs
--upload-port /dev/ttyACM0` wrote and hash-verified the LittleFS image to the
confirmed ESP32-C6FH4. A read-only live-page check at `412 x 916` CSS pixels
confirmed a `412 x 916` document with no overflow and references to the
local `player` and `spikes` symbols. It did not create or alter a player
session, so full active-match visual confirmation remains for the project
owner.
- On 2026-09-10, the project owner confirmed after representative player
testing that the new icons are satisfactory. This closes the remaining
visual-confirmation criterion; Milestone 019 is `COMPLETE`.
## Milestone 020 — Playful visual feedback and top message rail
**Status:** `COMPLETE`
### Objective
Replace distracting gameplay warning text with immediate visual feedback and
move the remaining player-facing notices into a playful top-of-screen message
rail that keeps children engaged without obscuring the maze or controls.
### Scope
- Before implementation, reconcile `MVP.md`, `ARCHITECTURE.md`,
`docs/UI_STYLE.md`, and supporting player-UI documentation with the new
feedback hierarchy and copy rules.
- When the server rejects a movement intention because of a wall or maze
boundary, shake the visible maze briefly in the attempted cardinal direction.
Do not show a toast, tooltip, spike notice, or any textual warning for the
blocked move.
- Keep blocked-movement feedback local and presentation-only. It must not move
the authoritative player position, reveal a hidden wall, infer unseen maze
state, or change the existing `blocked` protocol response.
- When movement is rejected with `rate_limited`, briefly darken and blur only
the maze image so the player understands that input is arriving too quickly.
Do not display the current server-speed warning or any replacement text.
- Bound, coalesce, and safely clear shake and blur feedback. Repeated blocked or
rate-limited responses may restart or extend one current effect only within a
documented maximum duration; they must not create animation queues, timers,
DOM nodes, or a permanently blurred maze.
- Move connection loss, reconnection, session, lobby, protocol, and other
messages that still require text to a dedicated rail at the top of the
screen, above the application top bar and the player status panel. Respect
display safe areas and do not cover essential status or controls.
- Rewrite player-facing messages as short, varied, child-friendly Russian
jokes or playful reactions. Remove visible engineering language such as
`WebSocket`, protocol packets, request limits, and internal server details;
retain technical detail only in diagnostics or the secondary connection
panel where appropriate.
- Define priority, deduplication, replacement, persistence, and dismissal rules
for the top message rail. Connection loss remains visible until recovery;
lower-priority messages are bounded and cannot flood or indefinitely delay a
more important connection state.
- Keep the rail legible but lively through icon, color, and restrained motion.
Avoid rapid flashing, excessive movement, or overlapping announcements, and
preserve Russian live-region semantics for assistive technology.
- In reduced-motion mode, replace maze shaking with a single non-animated
directional border pulse or equivalent static wall-impact cue. The
rate-limit darkening and blur may remain because they do not imply movement,
but their transitions must be effectively immediate.
- Preserve optional sound and haptic feedback, authoritative server behavior,
observer read-only behavior, hidden-state protection, offline assets, and the
existing firmware and LittleFS budgets.
### Deliverables
- Updated feedback, message-placement, reduced-motion, and humorous-copy
contracts in the canonical and supporting UI documentation.
- Direction-aware maze-shake feedback for blocked movement with no visible
blocked-move text.
- Bounded maze darkening and blur for `rate_limited` movement with no visible
rate-limit text.
- Responsive top message rail with safe-area placement, bounded prioritization,
deduplication, persistent connection state, and playful Russian copy.
- Deterministic browser tests plus representative mobile and desktop visual,
motion, reduced-motion, and accessibility verification evidence.
### Acceptance criteria
1. A `blocked` response causes one brief shake of the maze in the attempted
direction without moving the authoritative map position. No blocked-move
text, toast, tooltip, spike notice, or generic error notification becomes
visible.
2. The blocked effect uses only the direction of the matching outstanding
movement intention and the server rejection. It does not inspect, retain,
infer, or expose hidden maze geometry.
3. A `rate_limited` movement response darkens and blurs only the maze region for
a documented bounded duration. The HUD, direction pad, key action, top bar,
and message rail remain readable, and no rate-limit warning text appears.
4. Repeated `blocked` or `rate_limited` responses coalesce into at most one
active effect of each type, use a bounded number of timers and DOM nodes,
and restore the exact normal maze presentation after the documented maximum
duration, disconnect, pause, result, screen change, or application reset.
5. With reduced motion enabled, blocked movement produces a single static or
non-animated directional border cue instead of shaking. Rate-limit feedback
remains understandable without animation, sound, vibration, or text.
6. Every remaining player-facing transient or persistent system message appears
in the top message rail above the application top bar and player status
panel. No ordinary notice remains anchored near the bottom of the maze or
behind mobile controls.
7. At `412 x 915`, `412 x 916`, compact landscape, representative tablet and
desktop player viewports, and observer viewports of `1024 x 768` or larger,
the message rail respects safe areas, does not create page overflow, and
does not cover essential status, map controls, dialogs, or observer tools.
8. Visible messages are concise Russian, child-friendly, non-offensive, and
playful. Reachable normal-operation copy contains no `WebSocket`, packet,
protocol, queue, rate-limit, request-processing, or similar engineering
terminology.
9. Connection loss produces a persistent high-priority playful message until
reconnection. Recovery replaces it with a short positive message; repeated
reconnect attempts do not stack duplicates or hide the current connection
state.
10. The rail maintains a documented bounded message count and priority order,
deduplicates repeated events, exposes appropriate Russian live-region
semantics, and never rapidly flashes or animates indefinitely.
11. Browser tests cover directional wall shake, absence of blocked text,
rate-limit blur and absence of rate-limit text, effect coalescing and reset
paths, reduced motion, top placement, safe areas, message priority and
deduplication, connection persistence, playful-copy constraints, supported
viewports, and observer presentation.
12. All host tests pass, compressed web assets remain within the 1.5 MiB budget
and LittleFS partition, firmware remains within the factory partition, and
no protocol, game-authority, projection, or hidden-state contract changes.
13. The project owner confirms on a representative mobile browser that wall
collisions and excessive input are understandable without warning text,
the top messages do not obstruct play, and the humorous copy keeps the
experience lively without becoming repetitive or distracting, or explicitly
approves a documented evidence deviation.
### Evidence
The following planning-time observation motivated the milestone:
- On 2026-09-10, the project owner requested removal of the visible
`Там стена. Очень убедительная.` blocked-movement warning in favor of shaking
the maze, replacement of the server-speed warning with temporary maze
darkening and blur, and relocation of connection and other necessary messages
above the top panel. The owner also required playful, non-engineering Russian
copy intended to retain children's attention. Static inspection found the
current blocked and `rate_limited` responses feeding the shared notice path,
while `.notice` is positioned near the bottom of the viewport. Implementation
and completion verification are deferred to this milestone.
- On 2026-09-10, the feedback and rail contracts were reconciled in `MVP.md`,
`ARCHITECTURE.md`, `docs/UI_STYLE.md`, and `docs/PLAYER_UI.md`. The browser
records each outstanding move direction, uses it only after the matching
`blocked` response to restart one 340 ms directional maze effect, and uses
one restartable 520 ms maze-only `rate_limited` effect. Both timers are
cleared by control reset, disconnect, pause, result, and reset paths. The
map projection offset is held in CSS variables during the effect, preserving
the centered authoritative player position at board edges.
- `node --test test/web/test_player_app.js` passed: 22 tests, 0 failures. The
focused deterministic checks include the message rail placement and live
semantics, direction correlation, no blocked/rate-limit text, maze-only
blur, and reduced-motion behavior.
- `bash scripts/run_host_tests.sh` passed all portable host suites, the same
22 browser tests, and the deterministic web-asset check:
`web asset reproducibility: PASS (..., 27068/1572864 bytes)`.
- `/home/sasa/.platformio/penv/bin/pio run` passed for `esp32-c6-devkitm-1`:
RAM `58868/327680` bytes and factory flash `1027966/2097152` bytes.
`/home/sasa/.platformio/penv/bin/pio run -t buildfs` passed and produced the
configured `2031616/2031616` byte LittleFS image.
- On 2026-09-10, the project owner completed a live UI check and confirmed that
the feedback, message rail, and animations work as expected. This satisfies
the remaining visual-confirmation criterion; Milestone 020 is `COMPLETE`.
## Milestone 021 — Mobile player focal-point repositioning
**Status:** `COMPLETE`
### Objective
Move the player avatar away from the geometric center of the mobile maze into
a responsive, unobstructed focal position so hands operating the lower controls
do not hide the player or the most useful nearby maze context.
### Scope
- Before implementation, reconcile `MVP.md`, `ARCHITECTURE.md`,
`docs/UI_STYLE.md`, and supporting player-UI documentation with the new
mobile player-anchor contract. This supersedes the current requirement to
keep the player at the exact center of the map viewport.
- Define a responsive safe play area that excludes the fixed player-status
panel, Milestone 020 top message rail, directional pad, key-drop action,
utility controls, safe-area insets, and other persistent overlays.
- In portrait mobile layouts, place the authoritative player focal point above
the maze's geometric center while retaining useful visible context on every
side. At the canonical compact viewport, target approximately 30% of the map
region's height from its top rather than 50%.
- In compact landscape layouts, choose the player focal point from the largest
unobstructed map region and bias it away from the directional-pad hand zone.
Do not apply the portrait offset blindly when it would reduce usable context.
- Translate only the server-provided projection so its authoritative `self`
cell appears beneath the responsive focal point. Do not request, synthesize,
retain, or infer extra maze cells to fill space created by the offset.
- Keep the visible player icon aligned exactly with the authoritative `self`
cell after every projection, resize, orientation change, safe-area change,
and projection-edge case.
- Preserve the four-button directional pad as the primary phone input. Avatar
drag remains an optional compatibility input, but normal mobile play must not
require touching or covering the player icon.
- Keep the optional avatar-drag deflection relative to the new focal point and
return it there on release, cancellation, pause, disconnect, blur, hidden
document, lost pointer capture, and application reset.
- Integrate the new focal point with Milestone 020 maze shake and blur effects.
Temporary feedback transforms must compose with the projection offset and
must not cause the player or map to jump when an effect begins or ends.
- Preserve observer full-map positioning, desktop behavior where appropriate,
reduced-motion behavior, authoritative server ownership, hidden-state
protection, offline assets, and existing memory and Flash budgets.
### Deliverables
- Updated player-anchor, responsive-layout, input, and projection-presentation
contracts in canonical and supporting UI documentation.
- A bounded responsive focal-point calculation for portrait and compact
landscape mobile layouts.
- Player and server-projection rendering aligned to the new focal point,
including edge projections and orientation changes.
- Compatibility with the directional pad, optional avatar drag, top message
rail, blocked-movement shake, and rate-limit blur.
- Deterministic layout tests plus representative mobile portrait and landscape
visual and touch verification evidence.
### Acceptance criteria
1. At `412 x 915` and `412 x 916` CSS pixels in portrait orientation, the
resting player center is horizontally centered and positioned at
approximately 30% of the usable map region's height from its top, with a
documented tolerance of no more than five percentage points. It is not at
the map's 50% geometric center.
2. The player focal point lies completely outside the directional pad,
key-drop, utility-control, top-message, and status-panel exclusion zones,
including safe-area insets. The visible player icon and its outline do not
overlap any persistent control.
3. While a representative thumb operates every directional-pad button, the
player icon and its current cell remain visible and enough adjacent
server-provided maze context remains unobstructed to understand the next
orthogonal move.
4. Compact landscape orientation computes a stable focal point within the
largest unobstructed map region and away from the directional-pad hand zone.
Rotating between portrait and landscape does not leave the player beneath a
control or outside the visible map shell.
5. The rendered player stays aligned with the authoritative `self` cell to
within one CSS pixel after movement projections, viewport resize,
orientation changes, dynamic safe-area changes, and projections touching
each maze edge or corner.
6. Repositioning translates only cells contained in the current authoritative
projection. The browser does not expose, retain, infer, decorate, or fill
hidden maze geometry, and no protocol or server projection changes.
7. Direction-pad, swipe, keyboard, and optional avatar-drag inputs continue to
send the same bounded cardinal intentions. Avatar drag deflects relative to
the new focal point and every existing reset path returns it exactly there.
8. Milestone 020 directional shake and rate-limit blur compose with the focal
offset without changing the underlying authoritative position, accumulating
transforms, exposing hidden cells, or producing a jump when feedback clears.
9. The player layout requires no horizontal or vertical page scrolling at the
supported mobile viewports. The maze remains the dominant visual region and
the shifted projection does not create a distracting inactive void.
10. Browser tests cover focal-point calculation, exclusion-zone avoidance,
portrait and landscape placement, safe areas, resize and rotation, all map
edges and corners, one-pixel player/self alignment, input compatibility,
drag reset paths, Milestone 020 transform composition, zero overflow, and
absence of hidden-state expansion.
11. All host tests pass, compressed web assets remain within the 1.5 MiB budget
and LittleFS partition, firmware remains within the factory partition, and
observer layout and authority remain unchanged.
12. The project owner confirms on a representative mobile browser that the new
player position is comfortable, the operating hand no longer hides the
player or critical nearby maze context during directional-button play, and
portrait and landscape layouts remain easy to understand, or explicitly
approves a documented evidence deviation.
### Evidence
The following planning-time observation motivated the milestone:
- On 2026-09-10, the project owner reported that the exactly centered mobile
player is uncomfortable because fingers hide the maze and the player. A
read-only inspection of the open player page at `412 x 915` CSS pixels found
the `96 x 96` avatar target centered at `(206, 525.5)` and the map shell also
centered at `(206, 525.5)`, confirming exact geometric overlap. The player
requested a planning milestone only; implementation and completion
verification were deferred until Milestone 020 was `COMPLETE`.
- On 2026-09-10, `MVP.md`, `ARCHITECTURE.md`, `docs/UI_STYLE.md`, and
`docs/PLAYER_UI.md` were reconciled to define a 30%-height portrait focal
point and a compact-landscape left-biased unobstructed focal point. The
browser now composes the received-projection translation with that focal
offset, places the optional avatar drag target at the same point, and
recalculates both after resize and orientation change. It continues to render
only received projection cells; the Milestone 020 feedback transform uses
these composed map-offset variables unchanged.
- `node --test test/web/test_player_app.js` passed: 23 tests, 0 failures.
The added deterministic focal-point checks cover portrait and compact
landscape placement plus projection-offset composition at a projection edge.
- `bash scripts/run_host_tests.sh` passed all portable host suites, all 23
browser tests, and the deterministic asset check:
`web asset reproducibility: PASS (..., 27367/1572864 bytes)`.
- `/home/sasa/.platformio/penv/bin/pio run` passed for `esp32-c6-devkitm-1`:
RAM `58868/327680` bytes and factory flash `1027966/2097152` bytes.
`/home/sasa/.platformio/penv/bin/pio run -t buildfs` passed and produced the
configured `2031616/2031616` byte LittleFS image.
- On 2026-09-10, a project-owner live UI check found that the original 40%
map-region target still appeared centered on the full phone screen because
the map begins below the status area. The owner requested a more elevated
player position, so the active milestone target and implementation were
adjusted to 30% of map height; mobile confirmation remains required.
- On 2026-09-10, the project owner manually checked the revised mobile layout
and confirmed that the raised, transparent player handle no longer obscures
nearby maze passages and that the layout works as expected. This satisfies
the remaining manual confirmation criterion; Milestone 021 is `COMPLETE`.
## Milestone 022 — Persistent bush-wall breakthrough
**Status:** `COMPLETE`
### Objective
Add an always-available perseverance mechanic for both players: five deliberate
attempts against the same interior wall permanently open a passage through its
bushes, with convincing staged vegetation reactions and a final breakthrough
effect, while preserving ESP authority, hidden-state protection, bounded
resources, and fair rules.
### Scope
- Do not start implementation until every preceding milestone is `COMPLETE`.
- Before implementation, reconcile `MVP.md`, `ARCHITECTURE.md`,
`docs/UI_STYLE.md`, and supporting player-UI documentation with mutable
in-match maze topology. The new contract supersedes the current rule that a
rejected wall movement can never change position.
- Keep the ESP authoritative for impact counting, timeout handling, passage
mutation, player movement, projection updates, and all breakthrough events.
Browsers continue to send ordinary bounded cardinal movement intentions and
never decide that a wall has weakened or opened.
- Track one bounded perseverance streak per active player. A qualifying streak
consists of five server-accepted movement attempts made from the same cell in
the same cardinal direction against the same still-closed interior wall.
- Reset that player's streak when 3,000 ms or more elapse after the previous
qualifying impact, the player makes a successful move, the player attempts a
different direction, the player is paused or disconnected, or the match is
reset or completed. Rejected `rate_limited`, malformed, stale, duplicate, and
unauthorized commands neither increment nor complete a streak.
- On the fifth qualifying impact, atomically open the wall in both adjacent
cells and complete the originally requested move into the neighboring cell.
The passage remains open for the rest of the active match and can be used by
either player from either side without further impacts.
- Never allow a breakthrough through the maze's outer perimeter or into an
invalid cell. Key, artifact, exit, result, visibility, and occupancy rules
apply to the fifth-attempt movement exactly as they do to an ordinary move.
- Do not combine progress from two players or from opposite sides of a wall.
Each player has an independent streak, but a completed opening is one shared
authoritative topology change visible and usable according to normal game
rules.
- Represent permanent opened-wall state with a bounded structure sized from
the configured maximum maze dimensions. Clear it when the active match is
discarded; persistence across reboot is not required.
- Extend the protocol only as needed to deliver server-approved impact stages
to the initiating player and topology changes to affected projections.
Sequence or revision data must make duplicate, reordered, or delayed events
harmless. A player must never receive coordinates, wall state, impact
progress, or decorations outside that player's current allowed projection;
observers remain read-only and may receive the full authoritative state.
- Reconcile the mechanic with Milestone 020 feedback: impacts one through four
replace the generic blocked shake with increasingly strong but restrained
bush bending, rustling, and leaf-shedding cues; the fifth uses a short parting
or flattening animation, a bounded leaf burst, and immediate movement through
the new gap. No warning toast or engineering-style text is shown.
- Make the permanent opening visually read as a trampled bush passage when it
is inside the current server-approved projection. Use offline CSS/SVG assets,
bounded DOM nodes or canvas work, no runtime downloads, and no unbounded
particle allocation.
- Coalesce rapid feedback and cancel transient effects on pause, disconnect,
result, screen change, projection replacement, and application reset. Sound
and vibration remain optional enhancements and cannot be the only feedback.
- In reduced-motion mode, show server-approved static deformation stages and a
final non-animated opened-bush state without shaking, swaying, or flying
particles. Preserve clear color-independent silhouettes and contrast.
- Keep the mechanic equally available to both player roles with identical
thresholds and timing. Add deterministic checks that it introduces no
role-specific rule or information advantage even though player actions may
make the live topology asymmetric after match start.
- Preserve the existing firmware, compressed web-asset, and LittleFS budgets.
### Deliverables
- Updated gameplay, authority, projection, protocol, accessibility, and visual
contracts in canonical and supporting documentation.
- Bounded authoritative per-player perseverance tracking with an exact
five-impact threshold and 3,000 ms expiry.
- Atomic permanent interior-wall opening and fifth-attempt movement, including
shared bidirectional use and safe handling of game objects and results.
- Server-approved staged bush-impact feedback, final breakthrough animation,
permanent trampled-passage presentation, and reduced-motion equivalents.
- Deterministic host and browser tests plus representative two-player,
observer, mobile, reduced-motion, and physical-device verification evidence.
### Acceptance criteria
1. Four qualifying attempts against the same closed interior wall do not
change authoritative topology or player position. The fifth qualifying
attempt atomically opens that wall in both adjacent cells and moves the
initiating player exactly one cell through the new passage.
2. A perseverance streak increments only for server-accepted attempts from the
same player, cell, and cardinal direction against the same closed interior
wall. Tests cover exact threshold boundaries and every non-qualifying input
class.
3. The streak resets after a gap of 3,000 ms or more, any successful move, or
an attempted direction change, and on pause, disconnect, result, match
reset, and application reset. Tests cover just-inside, exact-boundary, and
just-outside timeout behavior using a deterministic clock.
4. Rate-limited, malformed, stale, duplicate, observer, and otherwise
unauthorized commands never weaken or open a wall. Duplicate or reordered
protocol events cannot replay an impact, animation, topology mutation, or
movement.
5. Outer-perimeter walls and invalid destinations can never be opened. The
fifth-attempt move obeys the same occupancy, object pickup, exit, visibility,
result, sequencing, and broadcast contracts as an ordinary successful move.
6. Once opened, the passage remains open bidirectionally until the active match
ends and either player can traverse it normally. Progress is not pooled
between players or opposite approaches, while completed topology is shared.
7. Player projections reveal only currently allowed cells and passage state.
Impact progress is sent only to the initiating player when permitted, and no
client can infer an unseen wall, remote impact, opening, object, or route;
observer sessions remain read-only.
8. Impacts one through four produce distinct, restrained server-approved bush
deformation cues whose intensity communicates progress without text. The
fifth produces a convincing short vegetation-parting or flattening effect,
a bounded leaf effect, and synchronized movement through the gap.
9. A breached wall inside the received projection is recognizable as a
permanent trampled-bush passage from either side. It remains understandable
without sound, vibration, text, or color alone and does not obscure the
player, objects, adjacent passages, controls, or top message rail.
10. Feedback uses a documented fixed maximum of timers, generated elements,
and particles per client. Repeated input coalesces safely, and all transient
effects clear on pause, disconnect, result, screen change, projection
replacement, and application reset without leaving stale transformations.
11. With reduced motion enabled, the same authoritative progress is clear from
static deformation stages and the final opened state; no shake, sway,
travel animation, or flying particle is required to understand the event.
12. Deterministic host tests cover counting, reset rules, timeout boundaries,
atomic topology mutation and movement, perimeter protection, two-player
independence and shared passage use, object/result interactions, message
ordering, bounded storage, symmetry of rules, and hidden-state protection.
13. Browser tests cover all five visual stages, event deduplication and
ordering, permanent passage rendering from both sides, effect cleanup,
supported mobile and desktop viewports, observer presentation, and
reduced-motion and non-color cues.
14. All host tests pass, compressed web assets remain within the 1.5 MiB budget
and LittleFS partition, firmware remains within the factory partition, and
physical-device testing confirms stable two-player play under repeated and
simultaneous wall impacts.
15. The project owner confirms on a representative mobile browser that five
impacts feel deliberate but not tedious, the bushes visibly resist before
giving way, the final breakthrough and movement feel synchronized, and the
permanent gap is obvious and enjoyable, or explicitly approves a
documented evidence deviation.
### Evidence
The following planning-time decisions define this milestone:
- On 2026-09-10, the project owner selected a permanent breakthrough rather
than a one-time squeeze or temporary opening. The owner accepted five hits
against one wall from one cell and one direction, with progress reset after
3 seconds, movement, or a direction change, and required the mechanic to be
always available to both players.
- Implementation, automated verification, physical-device checks, and owner
acceptance are deferred until all preceding milestones are `COMPLETE`.
- 2026-09-10 implementation evidence: `bash scripts/run_host_tests.sh` passed,
including `game persistent bush-wall breakthrough: PASS (five impacts, shared
passage)`. `node --test test/web/test_player_app.js` passed all 23 browser
tests and confirmed the bounded impact and trampled-passage presentation.
## Milestone 023 — Top-down living hedge SVG sample library
**Status:** `NOT_STARTED`
### Objective
Design and obtain project-owner approval for a lightweight SVG sample library
that makes maze walls read as varied living hedges viewed from above, with
recognizable leaves, twigs, depth, and matching damaged and permanently
breached variants for perseverance mode, without integrating the artwork into
the running game in this milestone.
### Scope
- Do not start this milestone until every preceding milestone is `COMPLETE`.
- Treat this as a visual exploration and asset-approval milestone only. Do not
replace the current maze renderer, change gameplay, modify the protocol, or
ship the samples in browser or firmware assets during this milestone.
- Before drawing, add a concise hedge-art contract to `docs/UI_STYLE.md` that
defines top-down perspective, scale, palette, outline weight, contrast,
organic variation, tiling rules, and the relationship between intact,
impacted, and permanently breached bushes.
- Create editable, hand-reviewable SVG source artwork with semantic groups and
stable symbol identifiers. Do not embed raster images, external fonts,
remote resources, scripts, generated metadata, or tool-specific binary data.
- Explore at least six visibly different intact straight-hedge samples. Vary
leaf clusters, branch paths, gaps, highlights, and silhouette rhythm while
keeping every sample unmistakably part of one visual family.
- Include representative joins and boundary situations needed by a cell-based
maze: continuous straight runs, perpendicular corners, crossings of adjacent
wall segments, exposed ends, and shared edges. Samples must rotate or mirror
cleanly for all four cardinal directions without directional lighting errors.
- Make individual leaves and small branch forks distinguishable at the actual
supported maze scales of 34, 40, and 46 CSS pixels per cell. Avoid both flat
green bars and noisy texture that turns into visual static on a phone.
- Use at least three vegetation tones plus restrained branch and shadow tones
to create depth. Maintain an identifiable hedge silhouette without relying
on green hue alone and keep players, keys, doors, passages, and interactive
feedback visually dominant.
- Draw a coherent perseverance sequence for the same representative wall:
intact, four increasing impact stages, the moment of breakthrough, and the
final permanent trampled opening. Branch bending, displaced leaves, broken
twigs, compressed foliage, and the cleared center must evolve consistently
rather than appearing as unrelated pictures.
- Provide at least three final breached-hedge samples with different organic
edge shapes. Each must clearly show a traversable gap from a top-down view,
retain torn or flattened bushes on both sides, and remain recognizable when
rotated or mirrored.
- Keep openings visually aligned to the authoritative cell passage and wide
enough for the player icon without implying a larger traversable area. The
samples must not encode coordinates, hidden passages, objects, ownership, or
other game state.
- Present all concepts together on one neutral comparison sheet and also as
isolated SVG symbols on representative dark maze cells. Include previews at
1x and high-density mobile rendering scales, with no smoothing assumptions
that are unavailable in ordinary browsers.
- Record approximate raw and compressed byte costs for each candidate family
so later integration can select variety without threatening the 1.5 MiB web
asset budget or LittleFS partition. Prefer reusable leaf, twig, shadow, and
damage components over repeated paths where that improves size and clarity.
- Check color-vision robustness, grayscale silhouette, normal contrast, and
reduced-motion suitability. This milestone defines static frames only;
animation timing and runtime particle behavior remain governed by Milestone
022 and are outside this milestone's implementation scope.
- End with an explicit project-owner selection of the preferred intact hedge
family, damage progression, breached variants, density, and palette. Runtime
integration requires a separately planned later milestone after approval.
### Deliverables
- Updated hedge-art and sample-review contract in `docs/UI_STYLE.md`.
- Editable SVG symbol sheet containing intact straight segments, joins, ends,
four impact stages, breakthrough frame, and permanent breached variants.
- Neutral comparison sheet plus representative dark-cell previews at 34, 40,
and 46 CSS pixel cell sizes and normal and high-density mobile scales.
- Brief asset manifest describing symbol identifiers, reuse rules, rotation and
mirroring constraints, accessibility checks, and raw and compressed sizes.
- Recorded project-owner selection and any requested revisions; no browser or
firmware integration changes.
### Acceptance criteria
1. The sample library contains at least six intact straight-hedge variants,
representative continuous runs, corners, shared-edge joins, and exposed
ends, all drawn as editable vector artwork with stable semantic identifiers.
2. Every intact sample resembles a living hedge viewed directly from above and
contains clearly distinguishable leaf clusters and branching structure. No
candidate is merely a solid, gradient, or textured green bar.
3. The candidates form one coherent visual family while showing enough
silhouette, foliage, branch, highlight, and gap variation to prevent obvious
repetition across a representative maze preview.
4. Leaves and primary branch forks remain recognizable at 34, 40, and 46 CSS
pixel cell sizes at 1x rendering. At high density they gain clarity without
changing geometry, producing seams, or becoming disproportionately detailed.
5. Straight segments, corners, joins, and ends connect without visible gaps,
doubled borders, accidental knots, or discontinuous lighting when rotated or
mirrored into every cardinal orientation.
6. At least three vegetation tones plus restrained branch and shadow tones
provide top-down depth. Grayscale and representative color-vision checks
preserve the hedge silhouette and do not reduce the legibility of players,
keys, doors, passages, or feedback.
7. One representative wall has a visually continuous set of intact, impact
stages one through four, breakthrough, and permanent-open frames. Each stage
shows plausible cumulative bending or damage and is distinguishable from
adjacent stages without explanatory text.
8. At least three permanent breached variants show an unambiguous traversable
center with torn, bent, or flattened vegetation on both sides. They remain
understandable from either side after rotation or mirroring and do not look
like an intact or merely shadowed wall.
9. Every breached opening aligns to one authoritative wall passage, is visually
wide enough for the existing player icon, and does not suggest access to an
adjacent closed cell or reveal any hidden maze state.
10. SVG files contain no raster payloads, external dependencies, scripts,
unnecessary editor metadata, or unbounded duplication. The manifest records
raw and compressed size for each candidate family and identifies reusable
components.
11. The comparison sheet shows all candidates on a neutral background and the
preferred candidates on representative dark maze cells at all target sizes.
Review does not require running or modifying the game.
12. No runtime HTML, CSS, JavaScript, firmware, protocol, generated web bundle,
or LittleFS image changes are part of this milestone. Automated checks
confirm SVG validity, local-only dependencies, stable identifiers, and
deterministic optimization output.
13. The project owner selects or explicitly rejects the intact hedge family,
impact sequence, breached variants, density, and palette after reviewing the
sample sheet at representative mobile scale. Any requested revisions are
recorded before the milestone can become `COMPLETE`.
### Evidence
The following planning-time request defines this milestone:
- On 2026-09-10, the project owner requested a future sample-drawing milestone
because maze walls should look like varied living hedges from above rather
than simple green sticks. The owner specifically required visible leaves and
branches plus matching broken-bush samples for permanent breakthroughs in
perseverance mode, and requested planning only with no immediate artwork or
runtime implementation.
- Artwork creation, review previews, validation, owner approval, and any later
runtime integration are deferred. Milestone 022 remains the sole active
milestone at planning time.