# v2 — manual, quick mode, browser build, and what is still missing

Written after the v1 game was complete and playing on hardware. This covers the
four things added in this round and an honest list of what the concept in
[`goal.md`](goal.md) still asks for that the game does not do.

---

## 1. The manual

[`docs/manual.html`](docs/manual.html) → [`docs/manual.pdf`](docs/manual.pdf), 16
pages, A4.

Every screenshot in it is a **real frame captured off the device**, not a mockup.
The firmware dumps its own framebuffer over serial (`src/core/Remote.cpp`), and
`tools/screenshot.py` drives the game with injected button presses and saves the
result — so the manual could be produced by scripting the actual game:

```bash
python3 tools/screenshot.py docs/img/schedule.png --keys "aaT13c" --scale 2
```

The gameplay-element art (animation strips, the icon sheet, the object sheet,
the eight member portraits) is decoded straight out of the generated RGB565 C
arrays in `src/assets/gen/` and re-expanded to PNG with the colour key turned
back into transparency. So the manual's art is literally the bytes the device
draws from.

Rebuild it with:

```bash
weasyprint docs/manual.html docs/manual.pdf
```

### Layout notes

Print CSS with `@page`, running page numbers, `page-break-inside: avoid` on
figures, tables and minigame blocks. The cover is a full-bleed gradient panel.
Body text is a serif; anything that represents something on the device — button
names, stat names, screen names — is monospace, so the two never get confused.

---

## 2. QUICK MODE and reset

### The problem

The game keys everything off the real-time clock. That is the point of it — the
pet lives while the screen is off, birthdays land on real dates, and a comeback
week is a real week. It also means *seeing* the end of a cycle took seven real
days.

### QUICK MODE

A new **SETTINGS** screen (`src/scenes/SettingsScene.cpp`), reached by pressing
**C** four times from the dorm:

| Setting | |
| --- | --- |
| `SOUND` | on/off, starts off |
| `MODE` | `NORMAL` / `QUICK` |
| `RESET GAME` | erases everything, asks first |

In `QUICK` mode **a shake skips an hour**. A seven-day week becomes a few minutes
of shaking. The whole simulation runs normally against the moved clock — needs
drift, the schedule regenerates, days roll over, the concert arms on day 7.

Two details that matter:

- **Shake only skips time on the calm screens.** `scene::isIdleScreen()` gates it
  to dorm / schedule / status / collection / settings. Shaking is a real input
  inside `WAKE UP!` and `CHOREO!`, and stealing it there would break both.
- **Clock changes go through an offset, not the system clock.** `wallclock::now()`
  returns `time(nullptr) + offset`. On hardware the offset is folded into
  `settimeofday` plus the RTC so it survives a reboot; in the browser the offset
  is all there is, because Emscripten has no `settimeofday`. This refactor is
  what lets quick mode work identically on both targets.

### Reset

`RESET GAME` in settings, with a confirmation, in both modes. The old shortcut
(hold **A** for 2.5 s on the status screen) still works.

Fixing this surfaced a real bug: `save::wipe()` only removed the NVS key, but the
scene that requested the wipe still finished its frame, and its `storeIfDue()`
wrote the state straight back — so a wipe appeared to do nothing. Writes are now
suppressed until the next explicit `store()`.

---

## 3. The browser build

`./web/build.sh` → WebAssembly, playable with no device at all.

**Everything under `src/` compiles unchanged.** The game was already written
against a small slice of the M5 API, so the port is a compatibility layer rather
than a fork:

```
web/
├── shim/M5Unified.h    the ~40 calls the game actually uses
├── shim/shim.cpp       canvas, buttons, IMU, mic, speaker, clock
├── shim/Preferences.h  NVS -> localStorage
├── shim/glcdfont.h     the 5x7 glyph table, lifted from M5GFX
├── main_web.cpp        drives the same setup()/loop() from rAF
└── index.html          canvas, on-screen controls, Web Audio
```

| Device | Browser |
| --- | --- |
| Buttons A/B/C | `A` `B` `C` keys, or on-screen buttons |
| IMU tilt | arrow keys, or the on-screen pad |
| Shake | spacebar, or the SHAKE button |
| Microphone | `getUserMedia` if allowed, else hold `M` |
| Buzzer | one square-wave Web Audio oscillator |
| NVS save | `localStorage` |
| RTC | system clock plus the same offset |

### Things worth recording

**The framebuffer is stored byte-swapped in the shim too.** LovyanGFX keeps
16-bit sprites as `swap565` on the PLUS2, and `gfx::nightTint()` reads the raw
buffer and byte-swaps by hand. Matching that layout in the shim means the shared
code is genuinely shared — no `#ifdef` in the game's own rendering.

**A shim bug that ate every button press.** `input::update()` calls `M5.update()`
and *then* reads `wasPressed()`. On hardware `M5.update()` samples the pins and
sets the edge flags. My first shim cleared them there, so any press that arrived
between frames was wiped before the game saw it. Edges are now latched into the
frame that reads them.

**Two latent bugs in the game's own source.** `Input.cpp` used `fabsf`/`sqrtf`
and `Save.cpp` used `millis()` while relying on Arduino's transitive includes.
Both now include what they use, which is correct for the hardware build too.

**`emcc` vs `em++`.** Linking C++ with the C driver leaves out libc++/libc++abi
and fails on `operator delete` and the RTTI vtables.

### Fidelity gap

Text is drawn with the real GLCD 5×7 glyphs at whole-number scales (1×, 2×, 3×)
standing in for `Font0` / `Font2` / `Font4`. The hardware `Font2` and `Font4` are
proportional, so browser headings run a little wider. Everything else —
simulation, events, minigames, concert scoring — is the same code producing the
same results.

---

## 4. What is still missing

Measured against [`goal.md`](goal.md). This is the honest list.

### Not implemented

| Concept | Status | What it needs |
| --- | --- | --- |
| **STAY shop** (§26) | Currency is earned, banked and displayed; there is nothing to spend it on | Room-item and outfit art, a shop screen, and per-member unlock state in the save |
| **Outfits** (§26) | Not started | 8 outfits × 8 members of new sprite sheets — the single biggest art ask left |
| **Room customisation** (§26) | Not started | The dorm is one fixed background; it would need to become a tile composite so bought items can be placed. `env_dorm_tiles.png` is already sliced and unused |
| **Photocards / collectibles** (§36) | Not started | 64 card images, plus a gallery screen |
| **GROUP MODE / New Game+** (§27) | The 8/8 condition is tracked and announced, but the mode does not exist | Multi-sprite dorm logic, shared needs, and a reason for it to be different rather than just busier |
| **Notifications** (§33) | Not implemented | `BIP-BIP!` calls with NORMAL/QUIET/OFF, plus deep sleep so the device can wake itself |
| **Member signature games** (§20) | The archetypes are named in the roster and shown in the manual, but every member plays the same ten games | Per-member difficulty curves or one bespoke variant each |
| **SKZOO visual mode** (§2) | Not started | A second full set of character sheets |
| **Achievements** (§36) | Only style badges exist | An achievement table and a screen |

### Implemented but thin

- **Events** — 57 written, against the ~150 the concept asks for. The engine is a
  static table; adding more is writing, not engineering.
- **Songs** — six themes. The concept's adaptive layering is real but limited by
  the buzzer being monophonic: "layers" are note density, not voices.
- **The concert** — five segments drawn from four minigames. Six of the ten never
  appear on stage.
- **Care mistakes** — counted and penalised, but the concept's *forced rest day*
  consequence does not exist yet.

### Known rough edges

- **The microphone has never been verified against real sound.** The code path
  runs and the meter sits correctly at zero in a quiet room, but nobody has
  confirmed the peak mapping (`~4000 counts = a comfortable speaking peak`) is
  right. The browser build makes this much easier to test.
- **Tilt thresholds were tuned with the device flat on a desk.** `TILT_ON = 0.42g`
  may want adjusting for how it feels held in a hand.
- **`VAN DASH` collision is generous** — the 32×40 sprite is mostly transparent
  hair, and the box is a rough inset rather than per-pixel.
- **Two UI icons mis-slice.** Indices 29/30 in `uiIcons` split a bed sprite across
  two cells. Cosmetic, and only visible if those icons get used.
- **The device clock only sets itself at flash time.** There is no in-game clock
  setter, so if the RTC drifts or the coin cell dies, birthdays land on the wrong
  day until the next flash.

### Budget

Flash is at **1.55 MB of a 3.34 MB app partition**, with ~1.05 MB of that being
RGB565 art. Outfits and room items are what would eventually make indexed colour
worth doing — one shared 64-entry palette would cut the sprite sheets roughly 4×.
There is no pressure to do it yet.
