Files
mazegame/projects/kissaten_yugure/CLAUDE.md
T
jurjen.ladeniusandClaude Opus 5 cbde457da5 M2 complete: disk saving works via MSXgl's tool/disk_save module
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>
2026-07-27 22:17:49 +02:00

115 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.)