123 lines
6.8 KiB
Markdown
123 lines
6.8 KiB
Markdown
# SNAKE — Xiaomi Watch S4
|
||
|
||
Textured Snake on a **368px circular arena**, centered on the round 466px
|
||
screen. **32px tiles** (60% larger than 0.7) occupy 89 legal cells of an 11×11
|
||
bounding grid; every tile corner stays inside the actual circle. The square
|
||
board frame is gone. Directional sprite artwork and user icon are preserved. Package identity: `com.ilias.snake`.
|
||
|
||
**Version 0.9 (versionCode 9): crown control experiment, unit-tested, needs an
|
||
on-watch check.** See docs/CROWN.md for the firmware reasoning.
|
||
|
||
## Crown control (new in 0.9)
|
||
|
||
- The watch firmware exposes **no crown event to quickapps**; it routes crown
|
||
rotation into the scrollable container as scroll deltas. The page is now a
|
||
full-screen vertical scroll with the dial centered; `onscroll` fires for both
|
||
crown rotation and finger scroll.
|
||
- Scroll deltas accumulate; every **40 px of scroll = one 90° turn**,
|
||
clockwise = clockwise turn. Reversals are structurally impossible because
|
||
turns are relative 90° steps (`rotateDir`-style), never absolute directions.
|
||
- The scroller is snapped back to center after every event, so the board never
|
||
visibly moves, even though the scroll travel exists.
|
||
- Swipes still work exactly as before; both inputs share the two-slot queue.
|
||
|
||
## Play
|
||
|
||
- **Swipe** to steer. A two-turn queue accepts rapid corners, consumes one turn
|
||
per tick, and rejects duplicate directions and reversals against the last
|
||
queued direction. Excess inputs are ignored, not allowed to replace a turn.
|
||
- **Tap the board, snake or apple** to pause. Tap **RESUME** (or an exposed part
|
||
of the board) to resume. Pausing clears queued turns.
|
||
- In the pause or game-over panel, select **CLASSIC** (the circular boundary ends the run) or
|
||
**WRAP** (cross the circular boundary to appear at the opposite legal end
|
||
of the **same row or column**). Changing mode starts
|
||
a fresh, paused game; tap RESUME to begin. Selecting the current mode does not
|
||
reset the run. The current mode is shown below the board.
|
||
- Both modes end on self-collision. Moving into the vacating tail is legal.
|
||
- **PLAY AGAIN** is a large 216×56px retry target. Retry keeps the selected mode,
|
||
resets score/speed/queued turns, and starts playing. Filling the board displays
|
||
**YOU WIN** at 89 occupied cells (86 points) and hides the apple safely.
|
||
- Leaving the page pauses it and stops the timer. Returning requires an explicit
|
||
resume; game-over never resumes automatically. The system back action remains
|
||
untouched. No crown, rotary, side-button or back interception APIs are used.
|
||
- **Crown to steer** (0.9): rotate the crown; every 40px of scroll is one
|
||
90° turn, clockwise = clockwise. Crown and swipe feed the same queue; the
|
||
board stays visually fixed while you turn. If your firmware gives the crown
|
||
coarser steps than 40px, adjust `CROWN_STEP_PX` at the top of `index.ux`.
|
||
|
||
## Score, speed and persistence
|
||
|
||
Each apple is one point. The always-visible header shows score and best. One
|
||
best score is shared across Classic and Wrap, saved through documented
|
||
[`@system.storage`](https://iot.mi.com/vela/quickapp/en/features/data/storage.html)
|
||
under `snake.best`. Mode is session-only; a newly created page defaults to Classic.
|
||
|
||
The movement interval starts at **300ms**, decreases by **10ms per apple**, and
|
||
never goes below **120ms**. Only one movement timer runs; none runs while hidden,
|
||
paused, finished or destroyed. Old timer callbacks are invalidated.
|
||
|
||
Storage reads merge with the in-memory maximum; writes wait for the initial
|
||
read and are serialized/coalesced so slow callbacks cannot overwrite a newer
|
||
record. The coordinator survives page recreation within the module lifetime.
|
||
Old 0.7 records through 253 remain valid and are never reduced to the new
|
||
86-point maximum. Invalid saved values are ignored. Errors/exceptions do not interrupt gameplay:
|
||
failed reads/writes retry on later apples or page-show activity, without a retry
|
||
loop. A failed/uncompleted write or immediate app termination may lose the
|
||
unsaved record. Storage persistence still needs verification on the watch.
|
||
|
||
## Lightweight rendering
|
||
|
||
Directional PNG tiles use only `left`/`top` positioning, no transforms. Existing
|
||
20px sprite art is nearest-neighbor enlarged to native 32px assets (crisp pixel
|
||
art, not new high-resolution detail). The board is a newly generated circular
|
||
PNG with transparent corners and a subtle legal-cell checker pattern. Wrapped
|
||
texture connections handle each varying row/column length. All rows/columns
|
||
have at least three cells, avoiding ambiguous one-/two-cell seams.
|
||
Food scans only 89 legal cells, including a nearly full board. HUD/footer
|
||
rectangles never overlap legal play tiles; text/overlay fit the watch chord.
|
||
`tests/preview-0.8-static.png` is a static composition of actual geometry/art,
|
||
with a legal-cell overlay—not a runtime screenshot.
|
||
|
||
## Build and test
|
||
|
||
```bash
|
||
npm ci
|
||
npm test
|
||
npm run release
|
||
```
|
||
|
||
`npm test` runs the original core, piece and page tests, 14 upgrade
|
||
regressions, the numeric layout suite, and deterministic randomized property tests. Upgrade coverage includes rapid
|
||
swipes, lifecycle/timer races, storage failures and async ordering, mode changes,
|
||
all four Wrap edges, seam textures, full-board victory and bounded food spawning.
|
||
The page suite also drives the crown scroll path: one notch = one queued turn,
|
||
scroller re-centered, queue capped at two, no reversal, turn consumed on tick.
|
||
New behaviors were exercised through failing-then-passing test cycles.
|
||
|
||
Release output: `dist/com.ilias.snake.release.0.9.rpk`. The locked
|
||
`aiot-toolkit` 2.0.5 uses the existing `sign/private.pem` and `sign/certificate.pem`;
|
||
do not regenerate or commit those keys. Release uses plain JS (not JSC/protobuf),
|
||
matching the previously working toolchain. The dependency lock is unchanged;
|
||
`npm ci` currently reports 14 development-toolchain audit findings (3 low,
|
||
11 high). No forced toolkit upgrade was applied.
|
||
|
||
## Files
|
||
|
||
- `src/pages/index/index.ux`: round-screen UI, swipe/crown queue, game/lifecycle state.
|
||
- `src/common/snake.js`: movement, collision, Wrap piece classification, food.
|
||
- `src/common/best.js`: asynchronous storage coordinator.
|
||
- `src/common/tex/`: native 32px sprite artwork and circular board.
|
||
- `docs/CROWN.md`: firmware-derived rationale for the crown scroll path.
|
||
- `tests/{snake,pieces,page,upgrade,layout,properties,round,art,round-history}.cjs`: Node regression suites, including history-based circular seam orientation checks.
|
||
|
||
Install the signed RPK through AstroBox. Source/releases live at
|
||
https://gitea.oimcloud.myaddr.tools/oiwmiodev/vela-snake (Big Intelligence group).
|
||
|
||
## Earlier versions
|
||
|
||
- 0.2/0.3: board played on watch; designWidth 480→466 fixed undersizing.
|
||
- 0.6: directional textured tiles and preserved user-supplied icon.
|
||
- 0.7: queued swipes, live/persisted records, capped acceleration, pause/resume,
|
||
lifecycle safety, retry/win panels and selectable Classic/Wrap.
|
||
- 0.8: circular arena (no square frame), 32px tiles, 89 legal cells.
|