Add design.md
This commit is contained in:
208
DESIGN.md
Normal file
208
DESIGN.md
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
# Project Record: "Nine Hazes" (working title) — Celtic Idle RPG for Pebble Time 2
|
||||||
|
|
||||||
|
> v2.0 — post-architecture-revamp. The design was rebuilt on verified PebbleOS
|
||||||
|
> platform constraints (see §4.4). Game mechanics (§§6–9) carry over unchanged.
|
||||||
|
|
||||||
|
## 1. Concept
|
||||||
|
|
||||||
|
A Celtic-themed idle RPG ecosystem for Pebble Time 2 (emery), built as:
|
||||||
|
1. **A fully customizable watchface** — the flagship and first artifact. Ten
|
||||||
|
toggleable, movable, stylable widgets incl. a Current Task strip. Complete
|
||||||
|
and useful standalone, before any game exists.
|
||||||
|
2. **A companion game watchapp** — the idle RPG itself. Canonical save lives
|
||||||
|
on the watch. Entry via Quick Launch.
|
||||||
|
3. **A self-hostable bridge server** — lightweight mirror of game state so the
|
||||||
|
watchface can display the hero's current task. The developer's instance is
|
||||||
|
default; players may self-host. Later doubles as multiplayer backend.
|
||||||
|
|
||||||
|
The game is played by queuing 15-minute-chunk tasks and returning to collect
|
||||||
|
rewards. Battery impact of the watchface must be indistinguishable from a
|
||||||
|
normal watchface.
|
||||||
|
|
||||||
|
## 2. Reference Projects (inspiration only — clean-room rules)
|
||||||
|
|
||||||
|
| Reference | What we take | Boundary |
|
||||||
|
|---|---|---|
|
||||||
|
| IdleFantasy (tristinbaker) | Task/expedition loop, chunk queue | No code; repo is Android/Kotlin |
|
||||||
|
| Doors of Doom (msx80) | Combat structure, Range pattern, weighted loot, Entity/Run serialization, instance-vs-definition | Clean-room |
|
||||||
|
| Pixel8 watchface | AppMessage patterns, persist-key discipline | No code reuse; license unknown |
|
||||||
|
| My Vampire System | Leveling *system shape* only | No names/lore (IP risk) |
|
||||||
|
| **Muninn (C-D-Lewis)** | Mechanic of on-watch battery-days estimation: slow sampling, moving-average drain rate, charge-event recalibration, 3-sample cold-start ramp. Also: Wakeup-not-Worker, rolling-single-wakeup scheduling, wakeup-collision retry | Study only; no code copied (repo has no LICENSE file) |
|
||||||
|
| **freakified/pebble-calculator** | Existence proof that TouchService works on emery in the current SDK | API reference only |
|
||||||
|
|
||||||
|
## 3. Platform & Environment
|
||||||
|
|
||||||
|
- **Target: emery only** (Pebble Time 2, 200×228 color). `"targetPlatforms": ["emery"]`
|
||||||
|
- **Toolchain: Core Devices PebbleOS SDK (Apache-2.0)**, local builds: `pebble build --emery`
|
||||||
|
- **Docs live at developer.repebble.com** — the project verified against these
|
||||||
|
- Custom fonts: bundled OFL TTFs at compile time (system fonts proprietary)
|
||||||
|
- **License for our code: Apache-2.0**
|
||||||
|
- **Server: TypeScript, zero dependencies**, pure `node:http` + stdlib. TLS
|
||||||
|
terminates at a reverse proxy (documented deployment pattern). State is
|
||||||
|
inspectable JSON files, atomic write-temp-rename. No database.
|
||||||
|
- Repo topology, donations: unchanged from v1 (Gitea master, GitHub mirror at
|
||||||
|
v0.1.0; Liberapay/Ko-fi + FUNDING.yml)
|
||||||
|
|
||||||
|
## 4. Architecture — three artifacts, one protocol
|
||||||
|
|
||||||
|
### 4.1 Topology
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────┐ PUT state ┌─────────────┐ GET packet ┌──────────────┐
|
||||||
|
│ GAME WATCHAPP │ ───────────▶ │ SERVER │ ◀──────────── │ WATCHFACE │
|
||||||
|
│ (truth) │ (JS, when │ (mirror, │ (JS, throttled│ (display only)│
|
||||||
|
│ │ running) │ never │ poll) │ │
|
||||||
|
└──────────────┘ │ authority) │ └──────────────┘
|
||||||
|
└─────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Game watchapp** owns ALL game truth in its per-app persist storage.
|
||||||
|
Full buttons + TouchService. Entered via Quick Launch (can be a single
|
||||||
|
tap on current hardware). Presence via App Glance line and timeline pins.
|
||||||
|
- **Watchface** is pure display: no game logic, no input handling for game
|
||||||
|
purposes, fed by a cached server packet. End-timestamps are fixed at
|
||||||
|
scheduling, so the countdown is computed locally each minute from the
|
||||||
|
cached packet — zero radio needed to stay fresh.
|
||||||
|
- **Server** mirrors the small "face packet" (task name, end timestamp,
|
||||||
|
queue fill, sync_version). Game watch = source of truth, so last-write-
|
||||||
|
wins is correct, not a bug. Token in auth header, never query string.
|
||||||
|
|
||||||
|
### 4.2 Server (self-hostable by design)
|
||||||
|
|
||||||
|
- Endpoints: `GET /v1/state/:player_id`, `PUT /v1/state/:player_id`.
|
||||||
|
Token auth, body-size cap (~2 KB), 401 on bad token, silent throttle.
|
||||||
|
- `sync_version` in every packet, forward/backward tolerant — strangers run
|
||||||
|
servers, coordinated upgrades are impossible.
|
||||||
|
- Contract lives in-repo as `/docs/PROTOCOL.md` (public spec for self-hosters).
|
||||||
|
- Server-down semantics: watchface renders cached data or hides the strip;
|
||||||
|
game is fully playable. **Fail-dead-silent, always.**
|
||||||
|
- Self-hosting = the privacy pitch for this audience. Developer instance is
|
||||||
|
default; donor-funded upkeep.
|
||||||
|
|
||||||
|
### 4.3 Wake cycle (game app only)
|
||||||
|
|
||||||
|
- **No worker, no background process — ever.** Game logic is dormant until
|
||||||
|
invited (user opens app) or a user-enabled semantic wakeup fires.
|
||||||
|
- Wakeups launch the app to the FOREGROUND (visible blip ~1–2 s), it computes,
|
||||||
|
updates App Glance + pushes packet/pins via its JS, and exits cleanly
|
||||||
|
(AppExitReason). No invisible execution exists on PebbleOS.
|
||||||
|
- **Semantic events only** — whole-task completion, milestones worth pinning;
|
||||||
|
typically a handful per day. Never per-chunk. Never per-minute. The
|
||||||
|
API *permits* waking every minute via rebooking; the design forbids it
|
||||||
|
(violates Laws 1 and 3).
|
||||||
|
- **Rolling single wakeup**: exactly one pending event (next semantic event),
|
||||||
|
rebooked each cycle. Handle `E_OUT_OF_RESOURCES` and collision retry
|
||||||
|
gracefully (Muninn pattern). Max 8 pending / 1-minute spacing per docs.
|
||||||
|
- Wakeups are an *optimization over* lazy check-in, never a dependency.
|
||||||
|
Must tolerate "no wakeups ever fired."
|
||||||
|
- Default OFF or "pins only"; user opt-in. Missed-sample tolerance required.
|
||||||
|
|
||||||
|
### 4.4 Platform constraint ledger (verified — do not re-litigate)
|
||||||
|
|
||||||
|
1. Watchfaces receive NO button or touch input. UP/DOWN/BACK/SELECT are
|
||||||
|
OS-owned on the face (timeline/health/menu); watchfaces cannot push
|
||||||
|
interactive secondary screens into usability. Double-tap-accel entry was
|
||||||
|
considered and rejected as a user nightmare.
|
||||||
|
2. Persist storage is per-app sandboxed. No app↔app sharing on watch, in the
|
||||||
|
Pebble phone app, or anywhere else. The only sanctioned cross-binary
|
||||||
|
bridges: timeline pins (with openWatchApp launch args), AppMessage
|
||||||
|
(phone↔own binary only).
|
||||||
|
3. Timeline is write-only/push-only to apps; watchfaces cannot read pins.
|
||||||
|
4. Quick Launch assigns apps (and OS functions) only — not watchfaces.
|
||||||
|
5. PebbleKit JS lifecycle is tied to its watch binary: spawns at launch,
|
||||||
|
killed at exit. No persistent phone-side service via the Pebble app.
|
||||||
|
6. Wakeup ≠ Worker. (Muninn demonstrates both coexisting; we use neither
|
||||||
|
worker nor any background process.)
|
||||||
|
7. AppExitReason (SDK 4.0+) lets a wakeup-launched app exit back to the
|
||||||
|
watchface cleanly.
|
||||||
|
|
||||||
|
### 4.5 Input model (final)
|
||||||
|
|
||||||
|
- **Watchface**: no input. All interaction is customization via the phone
|
||||||
|
settings editor (drag-drop on a virtual 200×228 screen, inspector,
|
||||||
|
restore-defaults). Touch-to-wake backlight is a user system setting we
|
||||||
|
never fight.
|
||||||
|
- **Game app**: standard buttons; TouchService edge zones (upper-right/mid/
|
||||||
|
lower-right = UP/SELECT/DOWN analogues) plus the system gesture bridge on
|
||||||
|
MenuLayer/ScrollLayer where suitable; swipe-dismiss = BACK. Central input
|
||||||
|
dispatch; every screen declares its actions once.
|
||||||
|
- **Launch**: Quick Launch binds a button to the game app (single tap on
|
||||||
|
current hardware). `launch_reason()` detects QUICK_LAUNCH; timeline pin
|
||||||
|
taps deep-link via `launch_get_args()`.
|
||||||
|
|
||||||
|
## 5. Watchface Widgets (all toggleable, movable, font/color stylable)
|
||||||
|
|
||||||
|
Ten widgets: Time, Date, Watch Battery, Phone Battery, Steps, Distance,
|
||||||
|
Weather composite (temp + hi/lo, sub-elements hideable), Weather Advisory
|
||||||
|
(omens), Sleep Time, Current Task strip (game).
|
||||||
|
|
||||||
|
- Update frequencies user-configurable (health 1/5/15/30/60 min; weather
|
||||||
|
15/30/60 min) — battery lever. Clock ticks per-minute only. Redraw on
|
||||||
|
change only (e-paper).
|
||||||
|
- Metric/imperial toggle; conversion at render time only.
|
||||||
|
- **Current Task strip**: renders from cached server packet; countdown
|
||||||
|
computed locally from end timestamp per-minute; staleness is detectable
|
||||||
|
and shown honestly ("Return for your reward"). With no packet: hidden or
|
||||||
|
"No hero yet".
|
||||||
|
- **Watch Battery**: modes `percent | days | both` (default `both`).
|
||||||
|
Days-remaining computed ON-WATCH (Muninn-inspired mechanics, own code):
|
||||||
|
battery samples piggyback on wake-ups the face already performs;
|
||||||
|
moving-average drain rate; charge events recalibrate; honest 3-sample
|
||||||
|
cold start (percentage-only until trend exists). Gradient moss→brass→
|
||||||
|
oxblood applies to both display modes. **Muninn web ingestion rejected**
|
||||||
|
(unstable third-party surface; more code + radio than the math).
|
||||||
|
- Weather/phone-battery arrive via face's own pkjs; JS lifecycle = face
|
||||||
|
foreground (poll on JS wake, not fixed timers). Cache last-good in persist.
|
||||||
|
|
||||||
|
## 6.–9. Game Mechanics, Settlement, Theme
|
||||||
|
|
||||||
|
**Carried over verbatim from v1 — FINAL, locked.** Idle pillar (15-min
|
||||||
|
chunks, 36-chunk budget, lazy resolution, deterministic seeds, items-as-XP,
|
||||||
|
skills-only), combat pillar (barrows, gear-defined builds, agility-as-armor,
|
||||||
|
blackout/permadeath), settlement chain, Celtic theme. Unchanged.
|
||||||
|
|
||||||
|
## 10. Multiplayer & Server Evolution (deferred)
|
||||||
|
|
||||||
|
Solo-first. The bridge server's `player_id`+token becomes the account seed.
|
||||||
|
Phased: trade codes → shared-storage rivals → async competition /
|
||||||
|
leaderboards — all served by the same self-hostable TS server, which is
|
||||||
|
also the donation-sustainability story. No P2P on Pebble (established).
|
||||||
|
|
||||||
|
## 11. Design Laws (north stars)
|
||||||
|
|
||||||
|
1. **Casual glance game — benefit, not chore.** Nothing demands a session;
|
||||||
|
no interruptions; the watchface never buzzes for game reasons.
|
||||||
|
2. **Game logic is dormant until invited.** Math only at check-in / enqueue /
|
||||||
|
cancel / collect / opt-in semantic wakeups. **No background process.**
|
||||||
|
3. Fully freeform face; "restore default" escape hatch.
|
||||||
|
4. **Never load-bearing externals.** Server down, Muninn gone, phone away —
|
||||||
|
the game plays, the face degrades gracefully and silently.
|
||||||
|
5. Verified-platform-only features: if the docs don't permit it, we don't
|
||||||
|
ship it (see §4.4 ledger).
|
||||||
|
|
||||||
|
## 12. Status & Next Steps
|
||||||
|
|
||||||
|
**Done:** full design spec; architecture verified against current SDK docs;
|
||||||
|
server language (zero-dep TS) and self-hosting model locked; battery-days
|
||||||
|
decision (self-compute) locked.
|
||||||
|
|
||||||
|
**Build order:**
|
||||||
|
1. **Watchface skeleton** — `main.c`: widget table (10 types), renderer,
|
||||||
|
layout persist (own key, own version byte), placeholder task strip,
|
||||||
|
clock/date/battery(percent) live, stubs for the rest
|
||||||
|
2. **`/docs/PROTOCOL.md`** — packet/API contract (face-packet schema,
|
||||||
|
sync_version semantics, failure ladder)
|
||||||
|
3. **`server.ts`** — zero-dep TS mirror; curl-testable before any client
|
||||||
|
4. **`widgets.c` + font table** — health/weather wiring; battery days-
|
||||||
|
remaining estimator
|
||||||
|
5. **Settings editor** (phase 2, drag-drop virtual face)
|
||||||
|
6. **Game watchapp** — `game.c` in full (chunk resolver, enqueue validator,
|
||||||
|
XP routing, prorating, persist version byte); Play Mode menus; wake
|
||||||
|
cycle; App Glance + pins
|
||||||
|
7. **Content tables** — TASKS/RECIPES/ITEMS (xp_value), founding chain,
|
||||||
|
MONSTERS[]
|
||||||
|
8. PDC sprites, theme skinning, polish
|
||||||
|
|
||||||
|
**First build verification checklist:** `pebble build --emery` + emulator;
|
||||||
|
TouchService delivery + subscription lifecycle; AppMessage plumbing against
|
||||||
|
stub server endpoint.
|
||||||
Reference in New Issue
Block a user