# Kissaten Yūgure — MSX2 cozy game ## Project overview A cozy coffee-shop (kissaten) simulation for MSX2, set in a provincial Japanese town in 1987. Target audience: adult retro gamers who love Japanese art and computers. Written in C using the MSXgl library. Working title: "Kissaten Yūgure" (Twilight Coffee Shop). Read `docs/DESIGN.md` before making any design or architecture decisions. Read `docs/CONVERSATION.md` for the original design discussion and rationale. ## Design pillars (never violate) - Cozy contract: no fail states, no bankruptcy, no time pressure outside the brew minigame. Progression is always forward. - Atmosphere over mechanics: palette work, music, and character writing carry the game. - Static-heavy rendering: no scrolling. Only sprites and small blits move. - Small, finishable scope. When in doubt, cut. ## Tech stack - Target: MSX2, SCREEN 5 (G4, 256×212, 16 colors from 512) - ROM format: ASCII16 MegaROM (banked) — plan bank layout from day one - Library: MSXgl (C) — https://github.com/aoineko-fr/MSXgl - Compiler: SDC C via MSXgl build system - Music: **unresolved — this line is stale.** It describes a PSG / MML → NDP pipeline, but the working pipeline used for `projects/mazegame` is the `msx-music-generator` repo emitting lVGM + ayFX, on MSX-Music (YM2413). This project targets plain MSX2, where FM is not standard hardware. Decision deferred to M9; see `docs/PLAN.md` §8 item 15. - Dialogue: authored in YAML/JSON, compiled to C arrays / binary banks by `tools/compile_dialogue.js` (Node.js) - Emulator for testing: openMSX (also verify on real hardware profiles) ## Build & test - Build: `./build.sh` (MSXgl build_tool; ROM → `out/` and `emul/rom/kissaten.rom`) - Run: `./build.sh run` → builds and launches openMSX - Requires `node` on PATH, else build.sh falls back to the bundled Windows/Linux binary and dies: `export PATH="/opt/homebrew/opt/node@24/bin:$PATH"` - Headless verification: drive openMSX with a Tcl script — `openmsx -machine C-BIOS_MSX2 -carta emul/rom/kissaten.rom -script test.tcl` using `after time {screenshot f.png}` and `keymatrixdown 8 1` (SPACE). Add `set throttle off` to the script: emulated time then runs many times faster than wall clock, so a 200-second play-through takes a couple of seconds. `after time` still counts emulated seconds, so timings hold. - **Boot is slow — budget for it.** `ROMDelayBoot = true` (needed for disk access, see below) means the game only appears at roughly 40-90s of emulated time. Screenshots before that show a blue BASIC screen, which looks exactly like a hang and is not one. Test with a disk machine: `openmsx -machine Philips_NMS_8250 -carta emul/rom/kissaten.rom -diska save.dsk` - Dialogue data (stage 3): `node tools/compile_dialogue.js data/dialogue/*.yaml` - Music data (stage 3+): `node tools/generate_mml.js` → NDP compiler → `data/music/` ## Platform gotchas (learned the hard way) - **Keyboard:** read the matrix row inside `DisableInterrupt()`/`EnableInterrupt()`. `Keyboard_Read()` is an `out` (row select) then `in`; the BIOS ISR scans the matrix too, and if it lands between the two you get phantom keypresses. `INPUT_KB_UPDATE` (buffered mode) is *not* a fix — it reads the BIOS NEWKEY/OLDKEY area, which C-BIOS does not maintain in the standard format. - **`BankedCall = true` is off until banked code actually exists.** With it on and nothing banked, RAM globals were corrupted at runtime (state machine ran wild, counters garbage). Re-enable deliberately at stage 3. - Erase blits must repaint the *actual* background at that spot — the counter area has three bands (wainscot / counter top highlight / counter top). ## Disk saving from a cartridge ROM — WORKING Saves go to a real file on a real disk: `KISSAT00.SAV` in the root of the disk in drive A. The disk stays a normal FAT disk you can inspect and copy. **Use `engine/src/tool/disk_save.h`.** MSXgl v1.3.0 added this module specifically for "save to disk from a ROM application". Do not hand-roll sector I/O — an earlier attempt using PHYDIO (Main ROM 0144h) and a direct CALSLT to DSKIO (4010h) had both entry points return carry-clear *while transferring nothing*, proven with a sentinel that survived the call intact. Four things are required, none of them obvious: 1. **`LibModules` must include both `"tool/disk_save"` and `"dos"`.** The module alone will not link. See `projects/samples/s_save.js` for the reference configuration, and `s_save.c` for usage. 2. **`ROMDelayBoot = true`.** A cartridge's INIT normally runs during the BIOS slot scan and MSXgl never returns from it, so the Disk ROM's own INIT never happens and there is no disk system to talk to. This option installs an H.STKE hook and returns to the scan instead. 3. **Boot becomes slow.** The game appears at roughly 40-90s of *emulated* time. Screenshots before that show a blue BASIC screen and look exactly like a hang. Use `set throttle off` in the test script so this costs seconds of wall clock, not minutes. 4. **`DiskSave_Check()` returns `SAVEDATA_UNSIGNED` for perfectly good files.** With `AppSignature = true` (`-DAPPSIGN`), `DiskSave_Check()` requires the file's first four bytes to equal `g_AppSignature` — but `DiskSave_Save()` writes the payload raw and never adds it. Treat `SAVEDATA_UNSIGNED` as success; `SaveState` carries its own magic, version and checksum, which is a stronger check regardless. **Save timing:** written in `AdvanceDay()` *after* the day counter increments, so the file always describes the morning the player wakes to. Saving during the evening instead makes a reload replay the day just finished and double-count its cups. Absent or unusable disk is not an error the player has to handle — it just means nothing persists, and the morning card says so. Test: ``` openmsx -machine Philips_NMS_8250 -carta emul/rom/kissaten.rom -diska save.dsk ``` Create a blank 720K disk with `tools/build/msxtar/msxtar -cf save.dsk --dos1 --size=720K`. **RTC CMOS is not an alternative** for a save this size, despite `RTC_USE_SAVEDATA` being TRUE in `msxgl_config.h`. Block 3 of the RP-5C01 is 13 nibbles and MSXgl's `RTC_SaveData()` stores exactly **6 bytes**, against a ~40-byte `SaveState`. (MSXgl has no cartridge-SRAM mapper target either; the `PAC` module's FM-PAC SRAM, 8 x 1024 bytes, is the other real option.)