From f043b63732ba7e385ab2c36024d9d358a0832298 Mon Sep 17 00:00:00 2001 From: sasa Date: Tue, 8 Sep 2026 22:14:13 +0300 Subject: [PATCH] feat(maze): shrink map and rebalance compact gameplay --- ARCHITECTURE.md | 4 +- MVP.md | 4 +- PLANS.md | 202 +++++++++++++++++++ components/maze32_maze/include/maze32_maze.h | 8 +- components/maze32_maze/maze32_maze.c | 10 +- docs/BALANCE_REPORT.md | 45 +++-- docs/INTEGRATION_TESTING.md | 4 +- docs/MAZE_GENERATION.md | 12 +- docs/MVP_ACCEPTANCE.md | 19 +- docs/OBSERVER_UI.md | 4 +- docs/PLAYTEST_REPORT.md | 10 + docs/UI_STYLE.md | 2 +- scripts/ws_protocol_client.py | 6 +- test/host/test_game_core.c | 4 +- test/host/test_maze_generation.c | 5 + test/web/test_player_app.js | 7 +- version.txt | 2 +- web/player.js | 10 +- 18 files changed, 297 insertions(+), 61 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 96b926a..64ee639 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -231,7 +231,7 @@ conceptual records include: outgoing queue. 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. ## 8. Visibility projection @@ -317,7 +317,7 @@ The browser application has four presentation modes: Player controls support keyboard movement, touch swipes, and an explicit 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. `docs/UI_STYLE.md` is the canonical browser presentation contract. The player diff --git a/MVP.md b/MVP.md index 4bd3239..ab23df2 100644 --- a/MVP.md +++ b/MVP.md @@ -59,7 +59,7 @@ approximately five minutes. There is no match time limit. ### 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 into game logic. - 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. 4. Four additional browsers can observe the complete match simultaneously 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 player-relative placement metrics. 6. Player clients never receive cells or dynamic objects outside their current diff --git a/PLANS.md b/PLANS.md index 1966e78..c4cdd6f 100644 --- a/PLANS.md +++ b/PLANS.md @@ -1173,3 +1173,205 @@ headline at random whenever a player opens the page. 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:** `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. diff --git a/components/maze32_maze/include/maze32_maze.h b/components/maze32_maze/include/maze32_maze.h index ea73103..8709dd1 100644 --- a/components/maze32_maze/include/maze32_maze.h +++ b/components/maze32_maze/include/maze32_maze.h @@ -8,11 +8,11 @@ extern "C" { #endif -#define MAZE32_MAZE_MAX_WIDTH 128U -#define MAZE32_MAZE_MAX_HEIGHT 64U +#define MAZE32_MAZE_MAX_WIDTH 32U +#define MAZE32_MAZE_MAX_HEIGHT 32U #define MAZE32_MAZE_MAX_CELLS (MAZE32_MAZE_MAX_WIDTH * MAZE32_MAZE_MAX_HEIGHT) -#define MAZE32_MAZE_DEFAULT_WIDTH 128U -#define MAZE32_MAZE_DEFAULT_HEIGHT 64U +#define MAZE32_MAZE_DEFAULT_WIDTH 32U +#define MAZE32_MAZE_DEFAULT_HEIGHT 32U #define MAZE32_MAZE_MAX_LANDMARK_PAIRS 16U #define MAZE32_MAZE_MAX_ARTIFACT_PAIRS 16U #define MAZE32_MAZE_RAM_BUDGET_BYTES (64U * 1024U) diff --git a/components/maze32_maze/maze32_maze.c b/components/maze32_maze/maze32_maze.c index 606826d..d0229eb 100644 --- a/components/maze32_maze/maze32_maze.c +++ b/components/maze32_maze/maze32_maze.c @@ -571,11 +571,11 @@ maze32_maze_config_t maze32_maze_default_config(void) { .width = MAZE32_MAZE_DEFAULT_WIDTH, .height = MAZE32_MAZE_DEFAULT_HEIGHT, .initial_view_radius = 3U, - .cross_half_pairs = 3U, - .extra_cycle_pairs = 64U, - .minimum_exit_separation = 64U, - .landmark_pairs = 8U, - .artifact_pairs = 4U, + .cross_half_pairs = 2U, + .extra_cycle_pairs = 8U, + .minimum_exit_separation = 22U, + .landmark_pairs = 2U, + .artifact_pairs = 1U, .maximum_attempts = 8U, }; return config; diff --git a/docs/BALANCE_REPORT.md b/docs/BALANCE_REPORT.md index 00fa2e7..7f0c0d2 100644 --- a/docs/BALANCE_REPORT.md +++ b/docs/BALANCE_REPORT.md @@ -1,20 +1,20 @@ # MVP gameplay balance -This document locks the gameplay configuration evaluated for Milestone 011. -It is not a claim that the required human playtests have completed; their -results belong in `docs/PLAYTEST_REPORT.md`. +This document locks the compact gameplay configuration evaluated for Milestone +014. Human confirmation of the revised map belongs in +`docs/PLAYTEST_REPORT.md`. ## Locked configuration | 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 | -| Cross-half connection pairs | 3 | Multiple routes between conceptual halves | -| Extra cycle pairs | 64 | Alternative routes without removing dead ends | -| Minimum start-to-exit separation | 64 cells | Keeps exits distant from starts | -| Landmark pairs | 8 | Spatial-memory cues in symmetric positions | -| Vision-artifact pairs | 4 | Eight one-use artifacts; exceeds the MVP minimum of two | +| Cross-half connection pairs | 2 | Four seam crossings between conceptual halves | +| Extra cycle pairs | 8 | Proportional route density without removing dead ends | +| Minimum start-to-exit separation | 22 cells | Linear scaling by approximately the square root of eight | +| Landmark pairs | 2 | Four spatial-memory cues in symmetric positions | +| Vision-artifact pairs | 1 | Two one-use artifacts; preserves the MVP minimum | | Compass pairs | 1 | One compass for each player's own exit | | Movement cooldown | 150 ms | Server-enforced minimum accepted-move interval | | 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 | | --- | ---: | -| Cycles | 133..133 | -| Dead ends | 704..854 | -| Start to key | 416..1,442 cells | -| Key to exit | 346..1,244 cells | -| Start to exit | 102..992 cells | -| Generation attempts | 1 | +| Cycles | 19..19 | +| Dead ends | 74..128 | +| Start to key | 93..422 cells | +| Key to exit | 84..352 cells | +| Start to exit | 26..242 cells | +| Generation attempts | 1..2 | -At the 150 ms cooldown, an uninterrupted shortest 416-cell key route takes at -least 62.4 seconds of accepted-move time. Actual match duration also includes +At the 150 ms cooldown, an uninterrupted shortest 93-cell key route takes at +least 13.95 seconds of accepted-move time. Actual match duration also includes route discovery, current-view-only navigation, the exit search, detours, interactions, and player decisions. The approximately five-minute target must be confirmed by the representative human-playtest procedure; no hard timer is @@ -63,9 +63,10 @@ introduced to force that outcome. ## Tuning decision -No parameter change is justified before representative timing evidence exists. -Changing maze density or cooldown solely from shortest-path metrics would risk -making the target duration less representative of real fog-of-war play. If the -playtest median materially misses the five-minute target, change only the -smallest relevant locked value, rerun the 1,024-seed suite, record the new +The topology and object counts were reduced with the map area while retaining +the three-cell base view, ten-move artifact effect, 150 ms cooldown, and 200 ms +draw window. These interaction values remain unchanged until representative +playtesting of the compact map supplies evidence for a further adjustment. If +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. diff --git a/docs/INTEGRATION_TESTING.md b/docs/INTEGRATION_TESTING.md index c6f0620..8fa5d3f 100644 --- a/docs/INTEGRATION_TESTING.md +++ b/docs/INTEGRATION_TESTING.md @@ -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 -`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 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: 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. 3. Stop consuming one observer stream while Player 1 performs accepted moves; intermediate observer updates may be dropped, but player acknowledgements diff --git a/docs/MAZE_GENERATION.md b/docs/MAZE_GENERATION.md index bed3fd5..6c073ec 100644 --- a/docs/MAZE_GENERATION.md +++ b/docs/MAZE_GENERATION.md @@ -7,13 +7,13 @@ later milestones. ## Representation and limits - 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 - 8,192 cells. + even-height rectangles from `8 x 8` through `32 x 32` and contain at most + 1,024 cells. - Each cell uses one byte of four-direction passage flags. A fixed bit set, traversal queue, and distance array form the reusable generation workspace. - Starts, keys, boundary exits, compass cells, landmarks, and vision artifacts 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. - Candidate regeneration is capped by `maximum_attempts`; failure returns a 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 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 -views. The default places eight landmark pairs and four vision-artifact pairs, -with no placement collisions. +views. The default places two landmark pairs and one vision-artifact pair, with +no placement collisions. Validation independently checks reciprocal passages, rotational symmetry, full reachability, edge and cycle counts, dead ends, half crossings, placement diff --git a/docs/MVP_ACCEPTANCE.md b/docs/MVP_ACCEPTANCE.md index c42b8d9..69beb02 100644 --- a/docs/MVP_ACCEPTANCE.md +++ b/docs/MVP_ACCEPTANCE.md @@ -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. | | 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. | -| 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. | | 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. | | 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. | | 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. | ## 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 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. | diff --git a/docs/OBSERVER_UI.md b/docs/OBSERVER_UI.md index c21b291..f919603 100644 --- a/docs/OBSERVER_UI.md +++ b/docs/OBSERVER_UI.md @@ -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-approved static geometry on join, restoration, and match start. 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. The observer screen contains no movement, start, or drop-key control. This is @@ -25,7 +25,7 @@ pauses the game. ## 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 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 **Вся карта** diff --git a/docs/PLAYTEST_REPORT.md b/docs/PLAYTEST_REPORT.md index 96d6d56..4e55efd 100644 --- a/docs/PLAYTEST_REPORT.md +++ b/docs/PLAYTEST_REPORT.md @@ -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 measurement. The locked configuration remains unchanged because no issue was 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. diff --git a/docs/UI_STYLE.md b/docs/UI_STYLE.md index 4c38633..62a7f13 100644 --- a/docs/UI_STYLE.md +++ b/docs/UI_STYLE.md @@ -120,7 +120,7 @@ are not MVP requirements. pixels. Smaller viewports show a Russian message explaining that a larger screen is required. - 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 server authority. diff --git a/scripts/ws_protocol_client.py b/scripts/ws_protocol_client.py index 420aa99..823ec9d 100755 --- a/scripts/ws_protocol_client.py +++ b/scripts/ws_protocol_client.py @@ -226,7 +226,7 @@ def verify_m010(host: str, stress_moves: int, repeat_matches: int) -> None: ) player_1_view = player_views[0] 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) six_client_sample = diagnostics(host) @@ -422,8 +422,8 @@ def verify_m009(host: str) -> None: for reader in readers: reader.join(timeout=35.0) 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) - print("four-observer full-map delivery: PASS (8192 cells each)") + assert all(view is not None and len(view["cells"]) == 1024 for view in observer_views) + print("four-observer full-map delivery: PASS (1024 cells each)") observers[0].send("move", {"direction": "north"}, "observer-move") error = observers[0].receive_type("error") diff --git a/test/host/test_game_core.c b/test/host/test_game_core.c index efc2fdf..dbc7a94 100644 --- a/test/host/test_game_core.c +++ b/test/host/test_game_core.c @@ -303,9 +303,9 @@ static void test_default_map_chunk_bounds(void) { maze32_view_t view; assert(maze32_game_project_view(&game, MAZE32_GAME_ROLE_OBSERVER, &view) == 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); - assert(chunks == 688U); + assert(chunks == 88U); char payload[MAZE32_GAME_VIEW_CHUNK_BYTES]; size_t largest_chunk = 0U; for (size_t chunk = 0U; chunk < chunks; ++chunk) { diff --git a/test/host/test_maze_generation.c b/test/host/test_maze_generation.c index ad42616..064cfd6 100644 --- a/test/host/test_maze_generation.c +++ b/test/host/test_maze_generation.c @@ -93,6 +93,11 @@ static void test_bounded_regeneration_failure(void) { static void test_large_seed_set(void) { 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 maximum_cycles = 0U; uint16_t minimum_dead_ends = UINT16_MAX; diff --git a/test/web/test_player_app.js b/test/web/test_player_app.js index db8ed4f..7ba0ff9 100644 --- a/test/web/test_player_app.js +++ b/test/web/test_player_app.js @@ -82,8 +82,9 @@ test("chunk assembly replaces projection cells without explored-map memory", () assert.deepEqual(player.projectedCellIndices(replacement), [18]); 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(player.snapshotIsValid({width: 128, height: 64, cells: [[8191, 15]], objects: []}), true); - assert.equal(player.snapshotIsValid({width: 129, height: 64, cells: [], objects: []}), false); + assert.equal(player.snapshotIsValid({width: 32, height: 32, cells: [[1023, 15]], objects: []}), true); + 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", () => { @@ -117,7 +118,7 @@ test("player assets are local, Russian, accessible, and responsive", () => { }); 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(20), 6); const html = fs.readFileSync(path.join(project, "web/index.html"), "utf8"); diff --git a/version.txt b/version.txt index 78bc1ab..a803cc2 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.10.0 +0.14.0 diff --git a/web/player.js b/web/player.js index c2ce3f6..718f1be 100644 --- a/web/player.js +++ b/web/player.js @@ -2,9 +2,11 @@ "use strict"; const TOKEN_KEY = "maze32.session.v1"; - const MAX_VIEW_CHUNKS = 700; - const MAX_VIEW_CELLS = 8192; - const MAX_VIEW_OBJECTS = 30; + const MAX_VIEW_CHUNKS = 90; + const MAX_VIEW_WIDTH = 32; + const MAX_VIEW_HEIGHT = 32; + const MAX_VIEW_CELLS = 1024; + const MAX_VIEW_OBJECTS = 12; const OBSERVER_CELL_SIZE = 8; const NAME_ADJECTIVES = Object.freeze(["Бодрый", "Хитрый", "Тихий", "Смелый", "Ловкий", "Сонный", "Шустрый", "Лунный", "Яркий", "Дикий", "Вечный", "Храбрый"]); const NAME_NOUNS = Object.freeze(["Барсук", "Крот", "Енот", "Кабан", "Кактус", "Жук", "Тостер", "Тюлень", "Гриб", "Ежик", "Бублик", "Пингвин"]); @@ -91,7 +93,7 @@ } 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; } const cellLimit = snapshot.width * snapshot.height;