Replaces the hand-rolled PHYDIO/DSKIO sector writer, which never worked,
with engine/src/tool/disk_save.h — added in MSXgl v1.3.0 for exactly this
("save to disk from a ROM application"). Saves are now a real FAT file,
KISSAT00.SAV, on the disk in drive A.
Four non-obvious requirements, all documented in CLAUDE.md:
- LibModules needs both "tool/disk_save" and "dos"
- ROMDelayBoot = true, or the Disk ROM's INIT never runs
- boot then takes 40-90s of emulated time (use `set throttle off`)
- DiskSave_Check() returns SAVEDATA_UNSIGNED for good files: with APPSIGN
it wants the first 4 bytes to be g_AppSignature, but DiskSave_Save()
writes the payload raw and never adds it
Also fixes a save-timing bug: the write now happens in AdvanceDay() after
the day increments, so the file describes the morning the player wakes to.
Saving during the evening made a reload replay that day and double-count
its cups.
Verified: play a full day, cold boot, resume at day 2 with served intact.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
115 lines
6.3 KiB
Markdown
115 lines
6.3 KiB
Markdown
# 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 <s> {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.)
|