feat(maze): shrink map and rebalance compact gameplay
This commit is contained in:
+2
-2
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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 **Вся карта**
|
||||||
|
|||||||
@@ -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
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -1 +1 @@
|
|||||||
0.10.0
|
0.14.0
|
||||||
|
|||||||
+6
-4
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user