feat(maze): shrink map and rebalance compact gameplay

This commit is contained in:
2026-09-08 22:14:13 +03:00
parent 5346f9296f
commit f043b63732
18 changed files with 297 additions and 61 deletions
+2 -2
View File
@@ -231,7 +231,7 @@ conceptual records include:
outgoing queue. outgoing queue.
The maze representation must be sized from configured dimensions with checked The maze representation must be sized from configured dimensions with checked
arithmetic. The default 8,192-cell map must not require a per-cell heap arithmetic. The default 1,024-cell map must not require a per-cell heap
allocation. allocation.
## 8. Visibility projection ## 8. Visibility projection
@@ -317,7 +317,7 @@ The browser application has four presentation modes:
Player controls support keyboard movement, touch swipes, and an explicit Player controls support keyboard movement, touch swipes, and an explicit
drop-key action. The UI renders only server-provided visible state. The observer drop-key action. The UI renders only server-provided visible state. The observer
view supports fitting, panning, or zooming the complete `128 x 64` map without view supports fitting, panning, or zooming the complete `32 x 32` map without
changing server authority. changing server authority.
`docs/UI_STYLE.md` is the canonical browser presentation contract. The player `docs/UI_STYLE.md` is the canonical browser presentation contract. The player
+2 -2
View File
@@ -59,7 +59,7 @@ approximately five minutes. There is no match time limit.
### Dimensions and representation ### Dimensions and representation
- The initial configuration is `width = 128`, `height = 64`, or 8,192 cells. - The initial configuration is `width = 32`, `height = 32`, or 1,024 cells.
- Width and height are runtime configuration values and must not be hard-coded - Width and height are runtime configuration values and must not be hard-coded
into game logic. into game logic.
- Movement is orthogonal between cells; diagonal movement does not exist. - Movement is orthogonal between cells; diagonal movement does not exist.
@@ -274,7 +274,7 @@ board:
refresh, enter the lobby, and start through the owner-controlled countdown. refresh, enter the lobby, and start through the owner-controlled countdown.
4. Four additional browsers can observe the complete match simultaneously 4. Four additional browsers can observe the complete match simultaneously
without receiving control authority. without receiving control authority.
5. Repeatedly generated `128 x 64` maps are connected, centrally symmetric, 5. Repeatedly generated `32 x 32` maps are connected, centrally symmetric,
contain cycles and multiple cross-half passages, and preserve equal contain cycles and multiple cross-half passages, and preserve equal
player-relative placement metrics. player-relative placement metrics.
6. Player clients never receive cells or dynamic objects outside their current 6. Player clients never receive cells or dynamic objects outside their current
+202
View File
@@ -1173,3 +1173,205 @@ headline at random whenever a player opens the page.
helper falls back safely when unavailable. This satisfies every Milestone 013 helper falls back safely when unavailable. This satisfies every Milestone 013
acceptance criterion. Milestone 013 is `COMPLETE`; Milestone 014 remains acceptance criterion. Milestone 013 is `COMPLETE`; Milestone 014 remains
`NOT_STARTED`. `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:** `NOT_STARTED`
### 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
Completion evidence is not yet available because implementation has not
started. A baseline inspection of the live game at a `412 x 915` viewport on
2026-09-08 found that the visible maze occupied only `80 x 140` pixels inside a
`381 x 729` play area, status labels were approximately `8--10.56` pixels,
status icons were `23 x 23` pixels, the player was identified primarily as
`P1`, and no directional touch controls were visible in the game area. These
observations define the principal redesign targets. The planned joystick
replaces directional-arrow buttons rather than adding them. Expected completion
evidence includes before-and-after viewport captures, deterministic browser
tests, `bash scripts/run_host_tests.sh`, a reproducible `buildfs` size report,
and the facilitated child-usability confirmation or an explicitly approved
deviation.
+4 -4
View File
@@ -8,11 +8,11 @@
extern "C" { extern "C" {
#endif #endif
#define MAZE32_MAZE_MAX_WIDTH 128U #define MAZE32_MAZE_MAX_WIDTH 32U
#define MAZE32_MAZE_MAX_HEIGHT 64U #define MAZE32_MAZE_MAX_HEIGHT 32U
#define MAZE32_MAZE_MAX_CELLS (MAZE32_MAZE_MAX_WIDTH * MAZE32_MAZE_MAX_HEIGHT) #define MAZE32_MAZE_MAX_CELLS (MAZE32_MAZE_MAX_WIDTH * MAZE32_MAZE_MAX_HEIGHT)
#define MAZE32_MAZE_DEFAULT_WIDTH 128U #define MAZE32_MAZE_DEFAULT_WIDTH 32U
#define MAZE32_MAZE_DEFAULT_HEIGHT 64U #define MAZE32_MAZE_DEFAULT_HEIGHT 32U
#define MAZE32_MAZE_MAX_LANDMARK_PAIRS 16U #define MAZE32_MAZE_MAX_LANDMARK_PAIRS 16U
#define MAZE32_MAZE_MAX_ARTIFACT_PAIRS 16U #define MAZE32_MAZE_MAX_ARTIFACT_PAIRS 16U
#define MAZE32_MAZE_RAM_BUDGET_BYTES (64U * 1024U) #define MAZE32_MAZE_RAM_BUDGET_BYTES (64U * 1024U)
+5 -5
View File
@@ -571,11 +571,11 @@ maze32_maze_config_t maze32_maze_default_config(void) {
.width = MAZE32_MAZE_DEFAULT_WIDTH, .width = MAZE32_MAZE_DEFAULT_WIDTH,
.height = MAZE32_MAZE_DEFAULT_HEIGHT, .height = MAZE32_MAZE_DEFAULT_HEIGHT,
.initial_view_radius = 3U, .initial_view_radius = 3U,
.cross_half_pairs = 3U, .cross_half_pairs = 2U,
.extra_cycle_pairs = 64U, .extra_cycle_pairs = 8U,
.minimum_exit_separation = 64U, .minimum_exit_separation = 22U,
.landmark_pairs = 8U, .landmark_pairs = 2U,
.artifact_pairs = 4U, .artifact_pairs = 1U,
.maximum_attempts = 8U, .maximum_attempts = 8U,
}; };
return config; return config;
+23 -22
View File
@@ -1,20 +1,20 @@
# MVP gameplay balance # MVP gameplay balance
This document locks the gameplay configuration evaluated for Milestone 011. This document locks the compact gameplay configuration evaluated for Milestone
It is not a claim that the required human playtests have completed; their 014. Human confirmation of the revised map belongs in
results belong in `docs/PLAYTEST_REPORT.md`. `docs/PLAYTEST_REPORT.md`.
## Locked configuration ## Locked configuration
| Parameter | Locked value | Purpose | | Parameter | Locked value | Purpose |
| --- | ---: | --- | | --- | ---: | --- |
| Maze dimensions | 128 x 64 cells | MVP map size | | Maze dimensions | 32 x 32 cells | Square map with one eighth of the former area |
| Initial vision radius | 3 cells | Current-view-only exploration | | Initial vision radius | 3 cells | Current-view-only exploration |
| Cross-half connection pairs | 3 | Multiple routes between conceptual halves | | Cross-half connection pairs | 2 | Four seam crossings between conceptual halves |
| Extra cycle pairs | 64 | Alternative routes without removing dead ends | | Extra cycle pairs | 8 | Proportional route density without removing dead ends |
| Minimum start-to-exit separation | 64 cells | Keeps exits distant from starts | | Minimum start-to-exit separation | 22 cells | Linear scaling by approximately the square root of eight |
| Landmark pairs | 8 | Spatial-memory cues in symmetric positions | | Landmark pairs | 2 | Four spatial-memory cues in symmetric positions |
| Vision-artifact pairs | 4 | Eight one-use artifacts; exceeds the MVP minimum of two | | Vision-artifact pairs | 1 | Two one-use artifacts; preserves the MVP minimum |
| Compass pairs | 1 | One compass for each player's own exit | | Compass pairs | 1 | One compass for each player's own exit |
| Movement cooldown | 150 ms | Server-enforced minimum accepted-move interval | | Movement cooldown | 150 ms | Server-enforced minimum accepted-move interval |
| Enhanced-vision duration | 10 successful moves | MVP artifact duration | | Enhanced-vision duration | 10 successful moves | MVP artifact duration |
@@ -47,15 +47,15 @@ The most recent recorded sweep (2026-09-08) reported:
| Metric | Observed range across 1,024 seeds | | Metric | Observed range across 1,024 seeds |
| --- | ---: | | --- | ---: |
| Cycles | 133..133 | | Cycles | 19..19 |
| Dead ends | 704..854 | | Dead ends | 74..128 |
| Start to key | 416..1,442 cells | | Start to key | 93..422 cells |
| Key to exit | 346..1,244 cells | | Key to exit | 84..352 cells |
| Start to exit | 102..992 cells | | Start to exit | 26..242 cells |
| Generation attempts | 1 | | Generation attempts | 1..2 |
At the 150 ms cooldown, an uninterrupted shortest 416-cell key route takes at At the 150 ms cooldown, an uninterrupted shortest 93-cell key route takes at
least 62.4 seconds of accepted-move time. Actual match duration also includes least 13.95 seconds of accepted-move time. Actual match duration also includes
route discovery, current-view-only navigation, the exit search, detours, route discovery, current-view-only navigation, the exit search, detours,
interactions, and player decisions. The approximately five-minute target must interactions, and player decisions. The approximately five-minute target must
be confirmed by the representative human-playtest procedure; no hard timer is be confirmed by the representative human-playtest procedure; no hard timer is
@@ -63,9 +63,10 @@ introduced to force that outcome.
## Tuning decision ## Tuning decision
No parameter change is justified before representative timing evidence exists. The topology and object counts were reduced with the map area while retaining
Changing maze density or cooldown solely from shortest-path metrics would risk the three-cell base view, ten-move artifact effect, 150 ms cooldown, and 200 ms
making the target duration less representative of real fog-of-war play. If the draw window. These interaction values remain unchanged until representative
playtest median materially misses the five-minute target, change only the playtesting of the compact map supplies evidence for a further adjustment. If
smallest relevant locked value, rerun the 1,024-seed suite, record the new playtesting identifies a material duration or navigation problem, change only
the smallest relevant locked value, rerun the 1,024-seed suite, record the new
range here, and repeat the affected playtests. range here, and repeat the affected playtests.
+2 -2
View File
@@ -14,7 +14,7 @@ image. Start from a freshly booted device with no occupied lobby sessions.
``` ```
Monitor the USB serial log at 115200 baud. Confirm application version Monitor the USB serial log at 115200 baud. Confirm application version
`0.10.0`, the 4 MB partition table, successful LittleFS mount, station or `0.14.0`, the 4 MB partition table, successful LittleFS mount, station or
fallback network startup, registration of all HTTP/WebSocket routes, and fallback network startup, registration of all HTTP/WebSocket routes, and
health state `serving`. health state `serving`.
@@ -33,7 +33,7 @@ The scenario intentionally takes more than two minutes because both repeated
matches use the real 60-second reconnect deadline. It performs these checks: matches use the real 60-second reconnect deadline. It performs these checks:
1. Connect two players and four observers and assemble all four complete 1. Connect two players and four observers and assemble all four complete
8,192-cell observer projections. 1,024-cell observer projections.
2. Verify heartbeat round-trip latency for all six sessions. 2. Verify heartbeat round-trip latency for all six sessions.
3. Stop consuming one observer stream while Player 1 performs accepted moves; 3. Stop consuming one observer stream while Player 1 performs accepted moves;
intermediate observer updates may be dropped, but player acknowledgements intermediate observer updates may be dropped, but player acknowledgements
+6 -6
View File
@@ -7,13 +7,13 @@ later milestones.
## Representation and limits ## Representation and limits
- Dimensions are runtime configuration values. Supported maps are even-width, - Dimensions are runtime configuration values. Supported maps are even-width,
even-height rectangles from `8 x 8` through `128 x 64` and contain at most even-height rectangles from `8 x 8` through `32 x 32` and contain at most
8,192 cells. 1,024 cells.
- Each cell uses one byte of four-direction passage flags. A fixed bit set, - Each cell uses one byte of four-direction passage flags. A fixed bit set,
traversal queue, and distance array form the reusable generation workspace. traversal queue, and distance array form the reusable generation workspace.
- Starts, keys, boundary exits, compass cells, landmarks, and vision artifacts - Starts, keys, boundary exits, compass cells, landmarks, and vision artifacts
use bounded fixed arrays. No generation or validation path calls a heap API. use bounded fixed arrays. No generation or validation path calls a heap API.
- The maze plus workspace budget is 64 KiB. The default `128 x 64` - The maze plus workspace budget is 64 KiB. The default `32 x 32`
configuration targets generation within 250 ms on the ESP32-C6. configuration targets generation within 250 ms on the ESP32-C6.
- Candidate regeneration is capped by `maximum_attempts`; failure returns a - Candidate regeneration is capped by `maximum_attempts`; failure returns a
diagnostic result instead of retrying indefinitely. diagnostic result instead of retrying indefinitely.
@@ -35,10 +35,10 @@ topology, placements, metrics, and hash.
Player 2's start and every required object are the exact rotational counterpart Player 2's start and every required object are the exact rotational counterpart
of Player 1's placement. Starts and exits are boundary cells, and the default of Player 1's placement. Starts and exits are boundary cells, and the default
requires at least 64 cells of Manhattan separation between each start and its requires at least 22 cells of Manhattan separation between each start and its
exit. Keys are selected outside the owners' initial Chebyshev-radius-three exit. Keys are selected outside the owners' initial Chebyshev-radius-three
views. The default places eight landmark pairs and four vision-artifact pairs, views. The default places two landmark pairs and one vision-artifact pair, with
with no placement collisions. no placement collisions.
Validation independently checks reciprocal passages, rotational symmetry, Validation independently checks reciprocal passages, rotational symmetry,
full reachability, edge and cycle counts, dead ends, half crossings, placement full reachability, edge and cycle counts, dead ends, half crossings, placement
+17 -2
View File
@@ -10,14 +10,14 @@ unrecorded assumptions.
| 2. Persistent station and fallback provisioning | Milestone 010 owner confirmation: persisted station connection and bounded retry; fallback provisioning page worked. | Passing. | | 2. Persistent station and fallback provisioning | Milestone 010 owner confirmation: persisted station connection and bounded retry; fallback provisioning page worked. | Passing. |
| 3. Two-player lobby, countdown, and refresh | Host lobby/protocol and browser tests; Milestone 010 owner confirmation that both player tokens restored their slots. | Passing. | | 3. Two-player lobby, countdown, and refresh | Host lobby/protocol and browser tests; Milestone 010 owner confirmation that both player tokens restored their slots. | Passing. |
| 4. Four read-only observers | Milestone 010 six-client target run assembled four complete observer maps; Milestone 009 authorization tests. | Passing. | | 4. Four read-only observers | Milestone 010 six-client target run assembled four complete observer maps; Milestone 009 authorization tests. | Passing. |
| 5. Fair repeated 128 x 64 maps | 1,024-seed host sweep and maze validation; locked metrics in `docs/BALANCE_REPORT.md`. | Passing. | | 5. Fair repeated 32 x 32 maps | Milestone 014 1,024-seed host sweep and maze validation; locked metrics in `docs/BALANCE_REPORT.md`. | Passing at host level; target playtest pending. |
| 6. Projection security | Host game visibility and payload-security tests; player and observer browser-contract tests. | Passing. | | 6. Projection security | Host game visibility and payload-security tests; player and observer browser-contract tests. | Passing. |
| 7. Controls and cooldown | Host game-core and browser keyboard/swipe tests; 150 ms locked cooldown. | Passing. | | 7. Controls and cooldown | Host game-core and browser keyboard/swipe tests; 150 ms locked cooldown. | Passing. |
| 8. Key and inventory mechanics | Host game-mechanics tests covering pickup, drop, sabotage, meeting return, and exits. | Passing. | | 8. Key and inventory mechanics | Host game-mechanics tests covering pickup, drop, sabotage, meeting return, and exits. | Passing. |
| 9. Artifact and compass rules | Host game-mechanics tests covering ten successful moves and role-specific compass projections. | Passing. | | 9. Artifact and compass rules | Host game-mechanics tests covering ten successful moves and role-specific compass projections. | Passing. |
| 10. Victory and draw window | Host game-mechanics test covering outward exit and 200 ms draw boundary. | Passing. | | 10. Victory and draw window | Host game-mechanics test covering outward exit and 200 ms draw boundary. | Passing. |
| 11. Disconnect recovery | Host disconnect-recovery tests and Milestone 009 physical acceptance evidence. | Passing. | | 11. Disconnect recovery | Host disconnect-recovery tests and Milestone 009 physical acceptance evidence. | Passing. |
| 12. Approximately five-minute normal match | Project-owner-confirmed representative physical playtests; the owner explicitly approved omitting match counts and timing statistics, recorded in `docs/PLAYTEST_REPORT.md`. | Passing by approved deviation. | | 12. Approximately five-minute normal match | The Milestone 011 evidence applies to the superseded `128 x 64` configuration. Repeat representative testing for the compact map or approve a new deviation. | Pending Milestone 014 confirmation. |
| 13. Russian offline UI and browser coverage | Milestone 008 owner checks in Chrome 152, Firefox 155, Safari 26.6, and Edge 152.0.4191.53; asset verification enforces the offline and 1.5 MiB budget. | Passing; repeat asset check for release candidate. | | 13. Russian offline UI and browser coverage | Milestone 008 owner checks in Chrome 152, Firefox 155, Safari 26.6, and Edge 152.0.4191.53; asset verification enforces the offline and 1.5 MiB budget. | Passing; repeat asset check for release candidate. |
## Release-candidate record ## Release-candidate record
@@ -37,3 +37,18 @@ digest from the final candidate below. This record must match the tested image.
No pending criterion is treated as passing evidence. The only approved No pending criterion is treated as passing evidence. The only approved
deviation is recorded above. deviation is recorded above.
## Milestone 014 compact-map candidate
The following host-verified candidate supersedes the map configuration in the
Milestone 011 release record. It is not the final accepted release until the
representative compact-map playtest is confirmed.
| Field | Compact-map candidate value |
| --- | --- |
| Date | 2026-09-08 |
| Application version | 0.14.0 |
| Firmware build command and size | `pio run -e esp32-c6-devkitm-1`; 1,027,934 / 2,097,152 application bytes; `firmware.bin` 1,028,560 bytes, SHA-256 `428e7690bed50d30caf61aca174d838f00d7de1a603f14ea669e996e3d167123` |
| LittleFS build command and size | `pio run -e esp32-c6-devkitm-1 --target buildfs`; 2,031,616 / 2,031,616 bytes; SHA-256 `467f0a07413c83bff5ea246b5063289eb61f8bf40b95d16d639b3241ceda8a9d` |
| Web asset digest and compressed size | `3461d20742e1999c1489465d7f3a68317d56b3acfc5ac5003341e1cf2d478eed`; 20,314 / 1,572,864 bytes |
| Pending evidence | Representative two-player confirmation for the `32 x 32` configuration. |
+2 -2
View File
@@ -14,7 +14,7 @@ simulate movements or infer updates. Each player status also reflects the
server-owned WebSocket connection state. A complete projection establishes the server-owned WebSocket connection state. A complete projection establishes the
server-approved static geometry on join, restoration, and match start. server-approved static geometry on join, restoration, and match start.
Subsequent bounded projections replace all dynamic player and object state Subsequent bounded projections replace all dynamic player and object state
while reusing only that authoritative geometry, avoiding an 8,192-cell while reusing only that authoritative geometry, avoiding a 1,024-cell
retransmission after every move. retransmission after every move.
The observer screen contains no movement, start, or drop-key control. This is The observer screen contains no movement, start, or drop-key control. This is
@@ -25,7 +25,7 @@ pauses the game.
## Viewport and navigation ## Viewport and navigation
At `1024 x 768` CSS pixels and above, the complete `128 x 64` default map is At `1024 x 768` CSS pixels and above, the complete `32 x 32` default map is
initially scaled to fit the available stage. The observer can zoom with the initially scaled to fit the available stage. The observer can zoom with the
labelled buttons or mouse wheel and pan with pointer dragging. Zoom is bounded labelled buttons or mouse wheel and pan with pointer dragging. Zoom is bounded
between 0.25 and 6 times the eight-pixel base cell size; the **Вся карта** between 0.25 and 6 times the eight-pixel base cell size; the **Вся карта**
+10
View File
@@ -36,3 +36,13 @@ representative playtests above and approved the absence of a quantified timing
sample. This is an approved evidence deviation, not a claim of statistical sample. This is an approved evidence deviation, not a claim of statistical
measurement. The locked configuration remains unchanged because no issue was measurement. The locked configuration remains unchanged because no issue was
reported. reported.
## Milestone 014 compact-map revalidation
The earlier results apply to the superseded `128 x 64` configuration. Test the
new `32 x 32` configuration with two players after the Milestone 014 firmware
and web assets are installed. Confirm that navigation remains understandable,
both players consider the symmetric race fair, no gameplay-blocking defect is
observed, and the match duration remains acceptable without a hard timer.
Representative compact-map results have not yet been recorded.
+1 -1
View File
@@ -120,7 +120,7 @@ are not MVP requirements.
pixels. Smaller viewports show a Russian message explaining that a larger pixels. Smaller viewports show a Russian message explaining that a larger
screen is required. screen is required.
- Place player and match status blocks above the full maze. - Place player and match status blocks above the full maze.
- Initially fit the complete `128 x 64` map into the available map area. - Initially fit the complete `32 x 32` map into the available map area.
- Provide zoom and pan without granting any gameplay control or changing - Provide zoom and pan without granting any gameplay control or changing
server authority. server authority.
+3 -3
View File
@@ -226,7 +226,7 @@ def verify_m010(host: str, stress_moves: int, repeat_matches: int) -> None:
) )
player_1_view = player_views[0] player_1_view = player_views[0]
assert player_1_view is not None and player_views[1] is not None assert player_1_view is not None and player_views[1] is not None
assert all(view is not None and len(view["cells"]) == 8192 assert all(view is not None and len(view["cells"]) == 1024
for view in observer_views) for view in observer_views)
six_client_sample = diagnostics(host) six_client_sample = diagnostics(host)
@@ -422,8 +422,8 @@ def verify_m009(host: str) -> None:
for reader in readers: for reader in readers:
reader.join(timeout=35.0) reader.join(timeout=35.0)
assert not failures and all(not reader.is_alive() for reader in readers) assert not failures and all(not reader.is_alive() for reader in readers)
assert all(view is not None and len(view["cells"]) == 8192 for view in observer_views) assert all(view is not None and len(view["cells"]) == 1024 for view in observer_views)
print("four-observer full-map delivery: PASS (8192 cells each)") print("four-observer full-map delivery: PASS (1024 cells each)")
observers[0].send("move", {"direction": "north"}, "observer-move") observers[0].send("move", {"direction": "north"}, "observer-move")
error = observers[0].receive_type("error") error = observers[0].receive_type("error")
+2 -2
View File
@@ -303,9 +303,9 @@ static void test_default_map_chunk_bounds(void) {
maze32_view_t view; maze32_view_t view;
assert(maze32_game_project_view(&game, MAZE32_GAME_ROLE_OBSERVER, &view) == assert(maze32_game_project_view(&game, MAZE32_GAME_ROLE_OBSERVER, &view) ==
MAZE32_GAME_RESULT_ACCEPTED); MAZE32_GAME_RESULT_ACCEPTED);
assert(view.cell_count == 8192U && view.object_count == 30U); assert(view.cell_count == 1024U && view.object_count == 12U);
size_t chunks = maze32_game_view_chunk_count(&view); size_t chunks = maze32_game_view_chunk_count(&view);
assert(chunks == 688U); assert(chunks == 88U);
char payload[MAZE32_GAME_VIEW_CHUNK_BYTES]; char payload[MAZE32_GAME_VIEW_CHUNK_BYTES];
size_t largest_chunk = 0U; size_t largest_chunk = 0U;
for (size_t chunk = 0U; chunk < chunks; ++chunk) { for (size_t chunk = 0U; chunk < chunks; ++chunk) {
+5
View File
@@ -93,6 +93,11 @@ static void test_bounded_regeneration_failure(void) {
static void test_large_seed_set(void) { static void test_large_seed_set(void) {
maze32_maze_config_t config = maze32_maze_default_config(); maze32_maze_config_t config = maze32_maze_default_config();
assert(config.width == 32U && config.height == 32U);
assert((uint32_t)config.width * config.height == 1024U);
assert(config.cross_half_pairs == 2U && config.extra_cycle_pairs == 8U);
assert(config.minimum_exit_separation == 22U);
assert(config.landmark_pairs == 2U && config.artifact_pairs == 1U);
uint16_t minimum_cycles = UINT16_MAX; uint16_t minimum_cycles = UINT16_MAX;
uint16_t maximum_cycles = 0U; uint16_t maximum_cycles = 0U;
uint16_t minimum_dead_ends = UINT16_MAX; uint16_t minimum_dead_ends = UINT16_MAX;
+4 -3
View File
@@ -82,8 +82,9 @@ test("chunk assembly replaces projection cells without explored-map memory", ()
assert.deepEqual(player.projectedCellIndices(replacement), [18]); assert.deepEqual(player.projectedCellIndices(replacement), [18]);
assert.equal(replacement.cells.some(cell => cell[0] === 9 || cell[0] === 10), false); assert.equal(replacement.cells.some(cell => cell[0] === 9 || cell[0] === 10), false);
assert.equal(assembler.accept({v: 1, type: "view", seq: 22, payload: {chunk: 0, chunks: player.MAX_VIEW_CHUNKS + 1, cells: [], objects: []}}), null); assert.equal(assembler.accept({v: 1, type: "view", seq: 22, payload: {chunk: 0, chunks: player.MAX_VIEW_CHUNKS + 1, cells: [], objects: []}}), null);
assert.equal(player.snapshotIsValid({width: 128, height: 64, cells: [[8191, 15]], objects: []}), true); assert.equal(player.snapshotIsValid({width: 32, height: 32, cells: [[1023, 15]], objects: []}), true);
assert.equal(player.snapshotIsValid({width: 129, height: 64, cells: [], objects: []}), false); assert.equal(player.snapshotIsValid({width: 33, height: 32, cells: [], objects: []}), false);
assert.equal(player.snapshotIsValid({width: 32, height: 33, cells: [], objects: []}), false);
}); });
test("refresh contract restores only the token and waits for server projection", () => { test("refresh contract restores only the token and waits for server projection", () => {
@@ -117,7 +118,7 @@ test("player assets are local, Russian, accessible, and responsive", () => {
}); });
test("observer view fits large screens and bounds zoom", () => { test("observer view fits large screens and bounds zoom", () => {
assert.equal(player.observerFitScale(1024, 600, 128, 64), .96); assert.equal(player.observerFitScale(1024, 600, 32, 32), 2.25);
assert.equal(player.clampObserverZoom(.01), .25); assert.equal(player.clampObserverZoom(.01), .25);
assert.equal(player.clampObserverZoom(20), 6); assert.equal(player.clampObserverZoom(20), 6);
const html = fs.readFileSync(path.join(project, "web/index.html"), "utf8"); const html = fs.readFileSync(path.join(project, "web/index.html"), "utf8");
+1 -1
View File
@@ -1 +1 @@
0.10.0 0.14.0
+6 -4
View File
@@ -2,9 +2,11 @@
"use strict"; "use strict";
const TOKEN_KEY = "maze32.session.v1"; const TOKEN_KEY = "maze32.session.v1";
const MAX_VIEW_CHUNKS = 700; const MAX_VIEW_CHUNKS = 90;
const MAX_VIEW_CELLS = 8192; const MAX_VIEW_WIDTH = 32;
const MAX_VIEW_OBJECTS = 30; const MAX_VIEW_HEIGHT = 32;
const MAX_VIEW_CELLS = 1024;
const MAX_VIEW_OBJECTS = 12;
const OBSERVER_CELL_SIZE = 8; const OBSERVER_CELL_SIZE = 8;
const NAME_ADJECTIVES = Object.freeze(["Бодрый", "Хитрый", "Тихий", "Смелый", "Ловкий", "Сонный", "Шустрый", "Лунный", "Яркий", "Дикий", "Вечный", "Храбрый"]); const NAME_ADJECTIVES = Object.freeze(["Бодрый", "Хитрый", "Тихий", "Смелый", "Ловкий", "Сонный", "Шустрый", "Лунный", "Яркий", "Дикий", "Вечный", "Храбрый"]);
const NAME_NOUNS = Object.freeze(["Барсук", "Крот", "Енот", "Кабан", "Кактус", "Жук", "Тостер", "Тюлень", "Гриб", "Ежик", "Бублик", "Пингвин"]); const NAME_NOUNS = Object.freeze(["Барсук", "Крот", "Енот", "Кабан", "Кактус", "Жук", "Тостер", "Тюлень", "Гриб", "Ежик", "Бублик", "Пингвин"]);
@@ -91,7 +93,7 @@
} }
function snapshotIsValid(snapshot) { function snapshotIsValid(snapshot) {
if (!Number.isInteger(snapshot.width) || snapshot.width < 1 || !Number.isInteger(snapshot.height) || snapshot.height < 1 || snapshot.width * snapshot.height > MAX_VIEW_CELLS) { if (!Number.isInteger(snapshot.width) || snapshot.width < 1 || snapshot.width > MAX_VIEW_WIDTH || !Number.isInteger(snapshot.height) || snapshot.height < 1 || snapshot.height > MAX_VIEW_HEIGHT || snapshot.width * snapshot.height > MAX_VIEW_CELLS) {
return false; return false;
} }
const cellLimit = snapshot.width * snapshot.height; const cellLimit = snapshot.width * snapshot.height;