docs: add initial project documentation
This commit is contained in:
+399
@@ -0,0 +1,399 @@
|
||||
# maze32 Architecture
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
This document defines the implementation boundaries and technical contracts for
|
||||
the maze32 MVP described in [MVP.md](MVP.md). It explains how the firmware,
|
||||
browser application, network transport, game simulation, and persistent
|
||||
configuration fit together.
|
||||
|
||||
Product behavior belongs in `MVP.md`. Implementation sequencing and milestone
|
||||
evidence belong in `PLANS.md`.
|
||||
|
||||
## 2. Architectural drivers
|
||||
|
||||
The design is constrained by the following facts:
|
||||
|
||||
- One ESP32-C6FH4 hosts the entire application.
|
||||
- The board has 4 MB Flash and must not require PSRAM.
|
||||
- Two players and up to four observers connect through browsers.
|
||||
- HTTP serves static assets and configuration endpoints.
|
||||
- WebSocket carries live lobby, control, and match traffic.
|
||||
- The ESP is the only authoritative source of game state.
|
||||
- Player clients must not receive information hidden by fog of war.
|
||||
- The same firmware supports normal Wi-Fi station operation and fallback
|
||||
access-point provisioning.
|
||||
- The MVP has no OTA partition and no requirement to recover an active match
|
||||
after device reboot.
|
||||
|
||||
## 3. System context
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
P1["Player 1 browser"] -->|"HTTP + WebSocket"| ESP["ESP32-C6 maze32 server"]
|
||||
P2["Player 2 browser"] -->|"HTTP + WebSocket"| ESP
|
||||
O["Up to 4 observer browsers"] -->|"HTTP + WebSocket"| ESP
|
||||
ESP --> NVS["NVS: Wi-Fi and device settings"]
|
||||
ESP --> LFS["LittleFS: compressed web assets"]
|
||||
ESP --> WIFI["Existing Wi-Fi network"]
|
||||
ESP -. "fallback" .-> AP["Provisioning access point"]
|
||||
```
|
||||
|
||||
The browser is a presentation and input client. It may animate confirmed state,
|
||||
but it must not decide whether an action succeeded.
|
||||
|
||||
## 4. Firmware component boundaries
|
||||
|
||||
### 4.1 Platform layer
|
||||
|
||||
Owns ESP-IDF startup, logging, monotonic time, random seed acquisition, reboot
|
||||
behavior, and error reporting. Code above this layer must not depend on an
|
||||
unverified GPIO, LED, or PSRAM.
|
||||
|
||||
### 4.2 Persistent configuration
|
||||
|
||||
Owns NVS access for:
|
||||
|
||||
- Wi-Fi SSID and credentials;
|
||||
- provisioning state;
|
||||
- tunable device and gameplay settings that are intentionally persisted;
|
||||
- schema version for safe future migration.
|
||||
|
||||
Session tokens, lobby membership, maze state, and active matches live in RAM
|
||||
and are cleared on reboot.
|
||||
|
||||
### 4.3 Network manager
|
||||
|
||||
Implements this boot policy:
|
||||
|
||||
1. Load saved station credentials from NVS.
|
||||
2. Attempt to connect in station mode.
|
||||
3. If credentials are missing or the connection attempt fails, start the
|
||||
fallback access point and provisioning interface.
|
||||
4. Accept new network settings, save them to NVS, and retry station mode.
|
||||
|
||||
The exact connection timeout and access-point naming convention are tunable
|
||||
implementation settings. Network reconfiguration must not silently continue an
|
||||
active match.
|
||||
|
||||
### 4.4 HTTP and static asset service
|
||||
|
||||
Serves:
|
||||
|
||||
- the player/lobby application;
|
||||
- the observer view;
|
||||
- the fallback provisioning interface;
|
||||
- minimal health and version information needed for diagnostics.
|
||||
|
||||
Production web assets are stored in LittleFS and should be precompressed during
|
||||
the build. The asset compression script is not part of the current baseline and
|
||||
must be introduced and tested in its own implementation milestone.
|
||||
|
||||
### 4.5 WebSocket gateway
|
||||
|
||||
Owns connections, role assignment, temporary session tokens, message parsing,
|
||||
schema validation, rate limiting, reconnect attachment, and outgoing queues.
|
||||
|
||||
It translates accepted client messages into commands for the authoritative
|
||||
game service. It never mutates match state directly.
|
||||
|
||||
### 4.6 Lobby service
|
||||
|
||||
Owns:
|
||||
|
||||
- two player slots;
|
||||
- up to four observer slots;
|
||||
- unique display-name validation;
|
||||
- optional humorous name suggestions;
|
||||
- identification of Player 1 as lobby owner;
|
||||
- readiness and start authorization;
|
||||
- reconnect reservations for player sessions.
|
||||
|
||||
Only Player 1 may start, and only when both player slots are connected.
|
||||
|
||||
### 4.7 Maze generator and validator
|
||||
|
||||
Generates a configurable rectangular maze and all initial placements. Generation
|
||||
is complete only after the validator confirms:
|
||||
|
||||
- all required cells and objects are reachable;
|
||||
- 180-degree rotational symmetry;
|
||||
- symmetric player-relative starts, keys, exits, compasses, landmarks, and
|
||||
vision artifacts;
|
||||
- cycles and alternative routes;
|
||||
- multiple connections between the conceptual halves;
|
||||
- keys outside the initial owner views;
|
||||
- equivalent placement and route metrics for both players;
|
||||
- no placement collision that violates object rules.
|
||||
|
||||
Generation uses a server-owned seed recorded with the in-memory match for
|
||||
diagnostics and reproducible tests. Clients do not generate the maze.
|
||||
|
||||
Search difficulty cannot be proven solely by geometric distance. The generator
|
||||
therefore exposes route and branching metrics, while the final requirement that
|
||||
key discovery be at least as challenging as subsequent exit discovery is
|
||||
validated through gameplay telemetry and playtesting.
|
||||
|
||||
### 4.8 Authoritative game service
|
||||
|
||||
Owns the complete mutable match state:
|
||||
|
||||
- maze and generated-object state;
|
||||
- player positions and connection states;
|
||||
- inventories and ground keys;
|
||||
- opened exits;
|
||||
- active vision effects;
|
||||
- countdown, pause, and result state;
|
||||
- monotonic timestamps and accepted-action sequence numbers.
|
||||
|
||||
All commands are processed serially through one logical command queue. This
|
||||
prevents concurrent WebSocket callbacks from producing inconsistent key,
|
||||
collision, or victory results.
|
||||
|
||||
### 4.9 View projector
|
||||
|
||||
Builds role-specific snapshots and events from authoritative state:
|
||||
|
||||
- Player 1 receives only Player 1's allowed state.
|
||||
- Player 2 receives only Player 2's allowed state.
|
||||
- Observers receive the complete match view.
|
||||
|
||||
The projector is the security boundary for fog of war. Hidden cells and objects
|
||||
must be omitted from serialized player payloads, not sent with a client-side
|
||||
visibility flag.
|
||||
|
||||
## 5. Runtime state machines
|
||||
|
||||
### 5.1 Device lifecycle
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Booting
|
||||
Booting --> StationConnecting: saved credentials
|
||||
Booting --> ProvisioningAP: no credentials
|
||||
StationConnecting --> Serving: connected
|
||||
StationConnecting --> ProvisioningAP: connection failed
|
||||
ProvisioningAP --> StationConnecting: settings saved
|
||||
Serving --> [*]: reboot
|
||||
```
|
||||
|
||||
### 5.2 Match lifecycle
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Lobby
|
||||
Lobby --> Countdown: owner starts with 2 players
|
||||
Countdown --> Running: countdown completes
|
||||
Running --> Paused: player disconnects
|
||||
Paused --> Running: player reconnects within 60 seconds
|
||||
Paused --> Finished: reconnect timeout; draw
|
||||
Running --> FinishPending: first outward exit
|
||||
FinishPending --> Finished: 200 ms result window closes
|
||||
Finished --> Lobby: return to lobby
|
||||
```
|
||||
|
||||
During `Paused`, movement commands are rejected and move-counted effects do not
|
||||
advance. During `FinishPending`, the first exit is retained while the server
|
||||
waits long enough to determine whether the second exit creates a draw.
|
||||
|
||||
## 6. Authoritative command processing
|
||||
|
||||
Every gameplay command follows the same pipeline:
|
||||
|
||||
1. Authenticate the session token and resolve its role.
|
||||
2. Verify that the role is allowed to issue the command.
|
||||
3. Validate the match state and client command schema.
|
||||
4. Enforce the server-side movement cooldown or other action constraints.
|
||||
5. Apply the command to authoritative state.
|
||||
6. Resolve cell entry, artifact activation, pickup, player meeting, key return,
|
||||
exit opening, outward exit, and result transitions in a documented order.
|
||||
7. Increment the authoritative sequence number.
|
||||
8. Project and send the resulting role-specific state updates.
|
||||
|
||||
Observer sessions never reach mutation steps.
|
||||
|
||||
## 7. Game-state model
|
||||
|
||||
The implementation should favor fixed-size or bounded containers. Required
|
||||
conceptual records include:
|
||||
|
||||
- `MazeConfig`: width, height, movement cooldown, object-density settings, and
|
||||
balance thresholds;
|
||||
- `Maze`: compact cell topology plus landmark identifiers;
|
||||
- `PlayerState`: slot, display name, session token reference, position,
|
||||
inventory, vision effect, connection state, and last accepted move time;
|
||||
- `KeyState`: owner and either a ground position or carrier;
|
||||
- `ExitState`: owner, position, and open state;
|
||||
- `CompassState`: owner association and position;
|
||||
- `VisionArtifactState`: position and consumed state;
|
||||
- `MatchState`: lifecycle, seed, sequence, timers, players, objects, and result;
|
||||
- `ConnectionState`: WebSocket handle, role, session association, and bounded
|
||||
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
|
||||
allocation.
|
||||
|
||||
## 8. Visibility projection
|
||||
|
||||
For a player at `(px, py)` with radius `r`, a cell `(x, y)` is visible when:
|
||||
|
||||
```text
|
||||
max(abs(x - px), abs(y - py)) <= r
|
||||
```
|
||||
|
||||
The normal radius is three and the boosted radius is six. Map boundaries clip
|
||||
the square. Walls do not occlude cells.
|
||||
|
||||
The projected player view contains only:
|
||||
|
||||
- visible maze cells and landmarks;
|
||||
- visible ground objects;
|
||||
- the player's own position, inventory, effect counter, and exit/compass hint
|
||||
state;
|
||||
- the opponent only when the opponent is currently visible, including the
|
||||
identity of a carried key;
|
||||
- public lobby, pause, countdown, and result information.
|
||||
|
||||
Previously visible map state is not retained by the server for client display.
|
||||
|
||||
## 9. WebSocket protocol contract
|
||||
|
||||
Messages use a versioned envelope. The initial logical shape is:
|
||||
|
||||
```json
|
||||
{
|
||||
"v": 1,
|
||||
"type": "message_type",
|
||||
"seq": 42,
|
||||
"requestId": "optional-client-id",
|
||||
"payload": {}
|
||||
}
|
||||
```
|
||||
|
||||
Protocol requirements:
|
||||
|
||||
- unknown versions or message types are rejected safely;
|
||||
- malformed payloads never reach game logic;
|
||||
- client request identifiers support acknowledgement without granting
|
||||
authority;
|
||||
- server sequence numbers let clients discard stale updates;
|
||||
- reconnect produces a fresh role-appropriate snapshot;
|
||||
- payload and queue sizes are bounded;
|
||||
- slow observers may lose intermediate updates and receive a newer snapshot,
|
||||
but must not block player command processing.
|
||||
|
||||
Initial client-to-server message families are:
|
||||
|
||||
- session creation or restoration;
|
||||
- role request and display-name selection;
|
||||
- lobby start;
|
||||
- move intent;
|
||||
- drop-key intent;
|
||||
- heartbeat.
|
||||
|
||||
Initial server-to-client message families are:
|
||||
|
||||
- session and role result;
|
||||
- lobby snapshot;
|
||||
- countdown;
|
||||
- player-view snapshot or update;
|
||||
- observer snapshot or update;
|
||||
- action acknowledgement or rejection;
|
||||
- pause/reconnect status;
|
||||
- match result;
|
||||
- protocol error.
|
||||
|
||||
Exact field schemas belong beside the implementation and require protocol tests.
|
||||
|
||||
## 10. Browser application
|
||||
|
||||
The browser application has four presentation modes:
|
||||
|
||||
1. network provisioning;
|
||||
2. name selection and lobby;
|
||||
3. player match view;
|
||||
4. observer full-map view.
|
||||
|
||||
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
|
||||
changing server authority.
|
||||
|
||||
A temporary session token is stored in browser-local storage so page refresh
|
||||
can reattach to the existing slot. It is not a permanent account credential and
|
||||
does not need to survive an ESP reboot.
|
||||
|
||||
## 11. Memory, storage, and concurrency
|
||||
|
||||
The locked Flash layout is defined in `partitions.csv`:
|
||||
|
||||
- 24 KiB NVS;
|
||||
- 4 KiB PHY data;
|
||||
- 2 MiB factory application;
|
||||
- 1,984 KiB LittleFS.
|
||||
|
||||
The design must work without PSRAM. Memory rules are:
|
||||
|
||||
- no per-cell heap allocation;
|
||||
- bounded WebSocket connections and outgoing queues;
|
||||
- reuse serialized buffers where practical;
|
||||
- prefer compact maze and object representations;
|
||||
- do not retain separate full maze copies for each player;
|
||||
- compute or serialize role projections from authoritative state;
|
||||
- measure free heap and largest free block under the six-client acceptance
|
||||
load.
|
||||
|
||||
`CONFIG_LWIP_MAX_SOCKETS=16` is an initial ceiling, not proof that six browser
|
||||
clients fit in RAM. Hardware load evidence is required before the relevant
|
||||
milestone can be completed.
|
||||
|
||||
## 12. Failure handling
|
||||
|
||||
- Invalid or rate-limited client actions receive a rejection and do not mutate
|
||||
state.
|
||||
- A player WebSocket loss pauses the match and starts a 60-second monotonic
|
||||
reconnect deadline.
|
||||
- Session restoration within the deadline resumes the same player state.
|
||||
- Deadline expiry ends the match in a draw.
|
||||
- Observer loss has no gameplay effect.
|
||||
- A device reboot clears the active lobby and match.
|
||||
- Failure to mount required production web assets is a startup error exposed in
|
||||
logs and health state.
|
||||
- A maze that fails validation is discarded and regenerated with a bounded
|
||||
retry policy; exhausting that policy returns to the lobby with a visible
|
||||
error rather than starting an invalid match.
|
||||
|
||||
## 13. Security and trust boundaries
|
||||
|
||||
The MVP is intended for a trusted local network, but the server still enforces:
|
||||
|
||||
- server-side role authorization;
|
||||
- observer read-only behavior;
|
||||
- unique player slots;
|
||||
- bounded input lengths and message sizes;
|
||||
- movement and message rate limits;
|
||||
- no hidden-state disclosure to player payloads;
|
||||
- no acceptance of client positions, timers, inventory, or results;
|
||||
- restricted network-credential handling through the provisioning flow.
|
||||
|
||||
Password-protected accounts, internet exposure, TLS termination, and remote
|
||||
matchmaking are outside MVP scope.
|
||||
|
||||
## 14. Verification strategy
|
||||
|
||||
Verification is layered:
|
||||
|
||||
- host-side unit tests for symmetry, reachability, visibility, movement, items,
|
||||
collision resolution, timers, and state transitions;
|
||||
- deterministic generator tests over many seeds;
|
||||
- protocol tests for malformed messages, authorization, stale sequence handling,
|
||||
and reconnect snapshots;
|
||||
- browser tests for keyboard, swipe, drop-key, lobby, player, and observer flows;
|
||||
- firmware build and partition-size checks;
|
||||
- on-device tests for Wi-Fi fallback, NVS persistence, six simultaneous
|
||||
WebSocket clients, disconnect recovery, heap use, and representative match
|
||||
duration.
|
||||
|
||||
No milestone is complete merely because code exists. Completion requires the
|
||||
evidence specified for that milestone in `PLANS.md`.
|
||||
Reference in New Issue
Block a user