Files
mazegame/projects/kissaten_yugure/CLAUDE.md
T
jurjen.ladeniusandClaude Opus 5 45749775fa Scaffold Kissaten Yugure; add M2 day spine, plan and script
Stage 1 (shop scene, customer, siphon brew minigame, serve loop) and the
M2 day spine: day counter, three-phase day, event-driven clock, journal
line, and a SaveState with magic/version/checksum.

Saving to disk is NOT working yet. A cartridge ROM can reach the Disk ROM
only from RAM (both PHYDIO and CALSLT switch page 1 out from under the
caller), and ROMDelayBoot is required so the Disk ROM's INIT runs at all.
With both in place the disk is detected, but sector I/O returns carry-clear
without transferring — proven with a sentinel. Probe now verifies the
buffer actually changed, so a non-working disk degrades to "no save"
rather than corrupting anything. Full findings in the project CLAUDE.md.

Also documented: keyboard reads need interrupt protection (the BIOS ISR
scans the matrix too), and BankedCall must stay off until banked data
exists.

Docs: PLAN.md (ten milestones, per-stage requirements, decision queue),
SCRIPT.md (all four character arcs, 60 beats, checked against the
26-character box), and amendment notes in DESIGN.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 21:58:22 +02:00

145 lines
8.2 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 — status: UNRESOLVED
M2 wants saves on a real `.dsk`. The game is a cartridge MegaROM, which makes
this awkward. What is established so far, all verified on
`Philips_NMS_8250` + `-carta` + `-diska`:
- **A cartridge ROM does boot fine on a disk machine.** Not a problem.
- **`ROMDelayBoot = true` is required.** 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, `NMBDRV` stays 0 and the disk hooks are never
patched. This option installs an H.STKE hook and returns to the scan
instead. **Boot becomes much slower** — the game appears at ~40-90s of
emulated time, so early screenshots will show a blue BASIC screen and look
like a hang when they are not.
- **Any call into the disk system must be made from RAM.** Both PHYDIO and
CALSLT reach the Disk ROM at 4000h — the same page as this cartridge — so
the calling instruction is switched out mid-call unless it lives elsewhere.
`Disk_InitStub()` copies a small stub to RAM for exactly this. Calling
directly from ROM black-screens the machine.
- With the above in place, `NMBDRV` reads 2 and `MASTER` is non-zero, so the
Disk ROM *has* initialised by the time the game runs.
**The remaining blocker:** both `PHYDIO` (Main ROM 0144h) and a direct
`CALSLT` to `DSKIO` (Disk ROM 4010h) return **carry clear — "success" — while
never touching the destination buffer.** Proven with a 0xAA sentinel that
survives the call intact. Page 0 is confirmed to be the Main ROM at call time
(0x002D reads 1 = MSX2, 0x0144 holds 0xC3 = `JP`), so the entry points
themselves are addressable.
Because a clean error code is *not* proof that a read happened, `Disk_Probe()`
now verifies the buffer actually changed and treats "no change" as no disk.
The game degrades gracefully: no disk means day 1, nothing persists, and the
evening card says so. Nothing crashes on either C-BIOS or a real-BIOS machine.
Next things to try: check whether the H.PHYD RAM hook is genuinely patched at
that moment; try a different disk machine profile; or reconsider the medium
(see PLAN.md §8 item 1).
**RTC CMOS is not a viable fallback**, despite `RTC_USE_SAVEDATA` being TRUE
in `msxgl_config.h`. Block 3 of the RP-5C01 is 13 nibbles, and MSXgl's
`RTC_SaveData()` (`engine/src/clock.c`) stores exactly **6 bytes** — against a
~40-byte `SaveState`. `RTC_SaveDataSigned()` is tighter still. Remaining
options are cartridge SRAM, the FM-PAC SRAM (MSXgl has a `PAC` module), or
shrinking the save to a 6-byte fingerprint — which would cost per-character
affinity and money, so it is a design decision, not just a storage one.
Reference: MSX2 Technical Handbook ch.5, CLOCK-IC section.
## Architecture conventions
- VRAM page 0: visible shop scene bitmap. Page 1: asset warehouse (sprite
frames, portraits, UI tiles) blitted with VDP commands (HMMM/HMMV).
- Seasons and time-of-day are palette swaps only (32-byte tables); never
duplicate background art per season.
- Sprite mode 2, two layered 16×16 sprites per customer (outline + fill).
- Portraits are 48×48, three expressions per character max.
- Game state lives in a single `SaveState` struct (~40 bytes); see DESIGN.md.
Implemented in `kissaten.c` with a magic word, version byte and checksum;
a failed or absent load is not an error, it just means day one.
- The day is a three-phase machine (morning / open / evening) and the clock is
**event-driven** — customers are the clock. `CUSTOMERS_PER_DAY` is the
single pacing dial.
- Dialogue scenes are data, not code: trigger conditions → portrait,
expression, text, optional choice, effects.
## Code style
- C99, MSXgl idioms (u8/u16 types, `msxgl_` module prefixes)
- Keep ISR/VBlank work minimal; game logic in main loop
- Comment bank-switching boundaries explicitly
- Tools are Node.js (plain JS, no framework), living in `tools/`
## Scope ladder (build in this order; every stage must be playable)
1. ✅ Shop scene + one customer + brew minigame + serve loop (no story)
2. ◐ Day/night cycle + save + money — day counter, three phases and the
journal line work; **persistence is blocked** (see the disk section below).
Money deliberately deferred to stage 5, since nothing gates on it before.
3. Dialogue engine + two regulars with short arcs ← real milestone
4. Seasons/weather/palette system
5. Remaining cast, upgrades, endings
`docs/PLAN.md` expands this into ten milestones with per-stage requirements
and a decision queue. `docs/SCRIPT.md` holds the full first-draft script.