diff --git a/.gitignore b/.gitignore index 989b7fc..0e62238 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ dos2 !/projects/template !/projects/template_msx2 !/projects/mazegame +!/projects/kissaten_yugure /projects/samples/datasrc diff --git a/projects/kissaten_yugure/CLAUDE.md b/projects/kissaten_yugure/CLAUDE.md new file mode 100644 index 0000000..0ace5e9 --- /dev/null +++ b/projects/kissaten_yugure/CLAUDE.md @@ -0,0 +1,144 @@ +# 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 — 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. diff --git a/projects/kissaten_yugure/build.bat b/projects/kissaten_yugure/build.bat new file mode 100644 index 0000000..2d693d8 --- /dev/null +++ b/projects/kissaten_yugure/build.bat @@ -0,0 +1,12 @@ +:: ____________________________ +:: ██▀▀█▀▀██▀▀▀▀▀▀▀█▀▀█ │ ▄▄▄ ▄▄ +:: ██ ▀ █▄ ▀██▄ ▀ ▄█ ▄▀▀ █ │ ▀█▄ ▄▀██ ▄█▄█ ██▀▄ ██ ▄███ +:: █ █ █ ▀▀ ▄█ █ █ ▀▄█ █▄ │ ▄▄█▀ ▀▄██ ██ █ ██▀ ▀█▄ ▀█▄▄ +:: ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀────────┘ ▀▀ +:: by Guillaume 'Aoineko' Blanchard under CC BY-SA license +::──────────────────────────────────────────────────────────────────── +@echo off + +cls + +..\..\tools\build\Node\node.exe ..\..\engine\script\js\build.js %1 %2 %3 %4 %5 %6 %7 %8 %9 \ No newline at end of file diff --git a/projects/kissaten_yugure/build.sh b/projects/kissaten_yugure/build.sh new file mode 100755 index 0000000..647201d --- /dev/null +++ b/projects/kissaten_yugure/build.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# ____________________________ +# ██▀▀█▀▀██▀▀▀▀▀▀▀█▀▀█ │ ▄▄▄ ▄▄ +# ██ ▀ █▄ ▀██▄ ▀ ▄█ ▄▀▀ █ │ ▀█▄ ▄▀██ ▄█▄█ ██▀▄ ██ ▄███ +# █ █ █ ▀▀ ▄█ █ █ ▀▄█ █▄ │ ▄▄█▀ ▀▄██ ██ █ ██▀ ▀█▄ ▀█▄▄ +# ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀────────┘ ▀▀ +# by Guillaume 'Aoineko' Blanchard under CC BY-SA license +#──────────────────────────────────────────────────────────────────── +clear + +if type -P node; then + node ../../engine/script/js/build.js $1 $2 $3 $4 $5 $6 $7 $8 $9 +else + ../../tools/build/Node/node ../../engine/script/js/build.js $1 $2 $3 $4 $5 $6 $7 $8 $9 +fi \ No newline at end of file diff --git a/projects/kissaten_yugure/docs/CONVERSATION.md b/projects/kissaten_yugure/docs/CONVERSATION.md new file mode 100644 index 0000000..e576d0b --- /dev/null +++ b/projects/kissaten_yugure/docs/CONVERSATION.md @@ -0,0 +1,115 @@ +# Design conversation record — Kissaten Yūgure + +A record of the original brainstorm and design discussion (Claude ↔ Jurjen, +July 2026) that led to this project. Kept for rationale and context; the +distilled result lives in `DESIGN.md`. + +--- + +## 1. The brief + +Jurjen wanted a new MSX2 game idea: **a cozy game targeted mainly at male +adults who love Japanese art and retro games**, built with MSXgl. + +Claude's read on the audience: nostalgia for late-80s Japan, quiet melancholy, +craftsmanship, and pixel art in the style of MSX magazine covers / Konami and +Compile titles. The MSX2 itself is a Japanese cultural artifact, so the +platform is part of the appeal. Reference vibe: *A Short Hike* energy, +rendered like an MSX2 classic. + +## 2. Four concepts pitched + +1. **Showa-era shopkeeper sim** — run a tiny kissaten, sento, or ramen stall + in a sleepy 1980s town. Regulars with small stories, seasons change the + palette (SCREEN 5 palette swaps per season = big atmosphere, low cost). + Simple state machines + dialogue, MSXgl-friendly; PSG/NDP pipeline fits + gentle city-pop-adjacent loops. Hits the demographic dead center. + +2. **Bonsai / garden tending game** — shape a tree or karesansui garden over + in-game years via a simple L-system-ish growth model. Minimal input, + maximal zen, sumi-e visuals, persistent growth via save. Smallest and most + distinctive option; nobody has made it on MSX. + +3. **Rural train journey game** — conductor/passenger on a one-car local + line; each station is a vignette. Landscapes are the star, but horizontal + scrolling is work on MSX2 (vertical scroll register is free), so it would + need screen-by-screen scenes or parallax-lite. Densha + inaka nostalgia. + +4. **Yokai postman / errand game** — cozy-spooky letter delivery between + friendly yokai at dusk. Iconic character material, light fetch-quest + structure, Ghibli / Mizuki Shigeru vibe. + +**Recommendation:** #1 or #2, because scope kills hobby projects. #1 had the +strongest emotional hook and marketing appeal ("run a coffee shop on real +MSX2 hardware"); #2 was the most achievable and distinctive. + +**Cross-cutting principles:** lean into palette work (MSX2's superpower), +keep text minimal or bilingual-friendly for the Japanese retro community, +and let the MML → NDP music pipeline carry the mood. + +## 3. Decision + +Jurjen picked **#1, the kissaten sim**. Working title: **Kissaten Yūgure** +("Twilight Coffee Shop") — you inherit a small kissaten in a fictional +provincial town, 1987. + +## 4. Design deep-dive (summary — full detail in DESIGN.md) + +- **Core loop:** one day ≈ 5–10 real minutes. Morning prep (menu focus, + radio weather, stock) → open hours (serve + siphon-brew timing minigame + + dialogue with regulars) → evening close (till, journal, save). +- **Cozy contract:** no fail states, no bankruptcy; money gates cosmetic and + menu upgrades (grinder, jazz records, a cat). Progression = story + seasons. +- **Regulars:** 6–8 characters (retired stationmaster, exam student, manga + artist, widow...), each with 12–20 short scenes triggered by day count, + season, weather, or what you serve. Right order at the right moment + advances arcs. +- **Screen layout (SCREEN 5, 256×212):** 16 px status bar (day/season, + clock, ¥) · 132 px shop scene (door, counter with customer sprites, + siphon rig) · 64 px dialogue window (48×48 portrait, 3 lines × 26 chars, + typewriter reveal). Static-heavy on purpose: only sprites move — no + scrolling — the biggest scope-saver in the design. +- **Hardware mapping:** VRAM page 0 = scene bitmap, page 1 = asset warehouse + blitted via HMMM/HMMV. Seasonal palette trick: one background authored + around palette slots (sky in 8–11, wood tones in 4–7); each season/time of + day is a 32-byte palette table, optionally lerped across the day. Sprite + mode 2, two layered 16×16 sprites per customer. Portraits 48×48 × 3 + expressions — where the pixel-art budget goes. +- **Data model:** tiny `SaveState` (~40 bytes) with day, season, weather, + money, unlock bitmask, and 8 `Regular` structs (arc_stage, affinity, + last_visit_day). Dialogue as data: YAML/JSON compiled by a Node.js tool to + C arrays / binary banks. ASCII16 MegaROM layout from day one. +- **Visitor logic:** weighted table per tick, filtered by season / weather / + time / days-since-last-visit — tunable in a spreadsheet, produces + noticeable patterns ("the stationmaster always comes when it rains"). +- **Music:** 6–8 short cozy PSG loops via the existing MML → NDP → + `ndp_player` pipeline (`generate_mml.js` for AI-assisted MML). Diegetic + jukebox upgrade lets the player pick the shop's track. +- **Scope ladder:** (1) scene + one customer + brew + serve → (2) day cycle + + save + money → (3) dialogue engine + two regulars ← real milestone → + (4) seasons/weather/palettes → (5) full cast, upgrades, endings. Every + stage playable. + +## 5. Moving to Claude Desktop / Claude Code + +Jurjen asked about continuing the project in Claude Desktop with context +files. Plan: + +- Put a lean **CLAUDE.md** in the project root — Claude Code reads it at the + start of every session. It holds build commands, toolchain quirks (SDCC, + MSXgl paths, openMSX), conventions, and design pillars; kept under ~200 + lines, referencing deeper docs instead of duplicating them. +- Full design lives in **docs/DESIGN.md**; this file (**CONVERSATION.md**) + preserves the original discussion and rationale. +- Claude Code's auto memory will additionally accumulate learned corrections + (e.g. bank layout details) over time. +- The dialogue compiler (`compile_dialogue.js`) will live in `tools/` next to + the existing `generate_mml.js`. + +## 6. Open threads (not yet designed) + +- Dialogue script format spec + compiler implementation +- Brew minigame detailed design +- Palette / day-cycle system implementation details +- Character roster finalization and story arcs +- Bank layout plan for the ASCII16 MegaROM diff --git a/projects/kissaten_yugure/docs/DESIGN.md b/projects/kissaten_yugure/docs/DESIGN.md new file mode 100644 index 0000000..1bbb91c --- /dev/null +++ b/projects/kissaten_yugure/docs/DESIGN.md @@ -0,0 +1,175 @@ +# Kissaten Yūgure — Design document + +Working title: **Kissaten Yūgure** ("Twilight Coffee Shop") +Platform: MSX2 · Library: MSXgl (C) · ROM: ASCII16 MegaROM + +> **Amendments.** This document still describes the game's intent and is the +> place to start. A few specifics have since been decided differently — where +> they conflict, the documents below win: +> +> - **Cast size: four, not six to eight** (§3). The four examples in §3 became +> the entire roster. Rationale in `PLAN.md` §6; full arcs in `SCRIPT.md`. +> - **Endings: one**, not several (§8 stage 5). `PLAN.md` §6. +> - **Saving: a real save disk** (`.dsk`), not yet working — see the disk +> section in `../CLAUDE.md`. Note the RP-5C01 CMOS holds only 6 bytes +> through MSXgl, so it is not an alternative for a ~40-byte `SaveState`. +> - **Music (§7) is out of date.** It describes a PSG / MML → NDP pipeline; +> the working pipeline is `msx-music-generator` → lVGM + ayFX. Which chip +> this game targets is still open — `PLAN.md` §8 item 15. +> - **The brew minigame is coffee-only.** Aki's cream soda is poured, not +> brewed, so her visits skip it — `SCRIPT.md` §8. +> +> Build order and per-stage requirements live in `PLAN.md`, which supersedes +> the scope ladder in §8 below with a finer ten-milestone breakdown. + +## 1. Concept + +You inherit a small kissaten (coffee shop) in a fictional provincial Japanese +town in 1987. A cozy, no-fail-state game about serving regulars, learning +their stories, and watching the town and seasons change. + +**Audience:** adult retro gamers with a love of Japanese art, computers, and +late-80s Japan nostalgia. The MSX2 itself is part of the fantasy — the game +should look like it came off an MSX magazine cover. + +**Tone references:** A Short Hike (pace and warmth), Konami/Compile-era MSX2 +pixel art, Showa-era kissaten culture, gentle city-pop-adjacent PSG music. + +**The cozy contract:** no fail states. You cannot go bankrupt. Money gates +cosmetic and menu upgrades (better grinder, jazz records, a cat), not +survival. Progression = unlocking regulars' stories and seasonal change. + +## 2. Core loop + +One in-game day ≈ 5–10 real minutes, in three phases: + +1. **Morning (prep):** choose today's menu focus (coffee blend, toast set, + curry), check the radio for weather (weather affects who visits), buy stock. +2. **Open hours (the heart):** customers arrive one or two at a time. Take + orders, brew/cook via a timing minigame, and — most importantly — talk. + Regulars have multi-day story arcs told in short dialogue beats. +3. **Evening (close):** count the till, journal entry summarizing the day, save. + +**Brew minigame:** a siphon coffee brew with a "release at the right moment" +mechanic. One-button friendly, thematic, satisfying to master. Quality result +feeds into customer affinity slightly (never punishingly). + +## 3. The regulars + +Six to eight recurring characters; this is where the game lives. Examples: + +- The retired stationmaster +- A high-schooler studying for entrance exams +- A manga artist missing deadlines +- A widow who orders the same thing daily + +Each has a story arc of ~12–20 short scenes, triggered by day count, season, +weather, or what you serve them. Serving the *right* thing at the right moment +advances arcs — the player learns preferences by paying attention, which +mechanically rewards the "regulars at my shop" fantasy. + +**Visitor logic per open-hours tick:** roll against a weighted table filtered +by (season, weather, time-of-day, days-since-last-visit). Simple, tunable in +a spreadsheet, and it naturally produces patterns players notice and love +("the stationmaster always comes when it rains"). + +## 4. Screen layout — SCREEN 5 (256×212) + +| Region | Height | Contents | +|---|---|---| +| Status bar | 16 px | Day + season · clock · money (¥) | +| Shop scene | 132 px | Tile/bitmap background: window, shelves, radio. Door zone (customer entry), counter zone (16×16 layered customer sprites), siphon rig zone (brew minigame). | +| Dialogue window | 64 px | 48×48 portrait left; 3 lines × 26 chars text with typewriter reveal; choice cursor on line 3 when needed. | + +Deliberately static-heavy: only sprites move, so the MSX2's weak scrolling is +sidestepped entirely. Status bar and dialogue window redraw rarely — only the +scene needs per-frame attention. This is the single biggest scope-saver. + +## 5. Hardware / MSXgl mapping + +**Video.** SCREEN 5 (G4), 16 colors from 512. +- VRAM page 0: visible shop background bitmap. +- VRAM page 1: asset warehouse — customer sprite frames, portraits, UI tiles, + blitted with VDP commands (HMMM); dialogue box open/close is HMMV rectangle + fill + font rendering, never touching the scene. + +**Seasonal palette trick.** Author the background once, designed around +palette slots: sky-through-window colors in slots 8–11, wood/warm tones in +4–7. Each season *and* time of day is then just a 32-byte palette table — +morning spring light vs. rainy autumn dusk on the same bitmap, basically +free. Optionally lerp between palettes across the in-game day for a slow +ambient shift. This is the most atmospheric feature in the game. + +**Sprites.** Sprite mode 2 (per-line colors on 16×16). Two layered sprites +per customer (outline + fill) for the classic MSX2 look. Max two customers +on screen plus player hands/steam effects — nowhere near the 8-per-line limit. + +**Portraits.** 48×48 with three expressions per character (neutral, happy, +troubled), copied from page 1 into the dialogue box. Portraits are where the +pixel-art budget goes; they sell the characters. + +**ROM.** ASCII16 MegaROM from day one — dialogue, portraits, and music banks +will not fit in 32KB. MSXgl supports banked ROMs well. + +## 6. Data model + +```c +typedef struct { + u8 id; + u8 arc_stage; // progress through their story + u8 affinity; // 0-255, raised by right orders + u8 last_visit_day; +} Regular; + +typedef struct { + u16 day; + u8 season; // derives palette set + u8 weather; // affects visitor table + u16 money; + u8 unlocked_items; // bitmask: grinder, records, cat... + Regular regulars[8]; +} SaveState; // ~40 bytes → trivial to save +``` + +**Dialogue** is the biggest data mass. Scenes stored in a flat script format: + +``` +(trigger conditions) → (portrait id, expression, text lines, + optional choice, effects) +``` + +Authored in YAML/JSON; a small Node.js compiler (`tools/compile_dialogue.js`) +emits C arrays or a binary blob for a MegaROM bank. Budget: ~8 characters × +~15 scenes plus ambient lines. + +## 7. Music + +PSG via the MML → NDP compiler → `ndp_player` pipeline, with AI-assisted MML +generation through `tools/generate_mml.js` (Anthropic API). + +Needs: 6–8 short cozy loops rather than driving action tracks — morning, +afternoon, evening, rain, plus one theme per major story beat. + +**Diegetic jukebox:** the record-player upgrade lets the player choose the +shop's background track — a feature and a home for all generated music in one. + +## 8. Scope ladder + +Build in this order so every stage is a playable game: + +1. Shop scene + one customer + brew minigame + serve loop, no story +2. Day/night + save + money +3. Dialogue engine + two regulars with short arcs ← **the real milestone** +4. Seasons/weather/palettes +5. Remaining cast, upgrades, endings + +If serving the exam student his usual and getting one new line of his story +feels good at stage 3, the game works. + +## 9. Other principles + +- Lean hard into palette work — the MSX2's superpower. +- Keep text minimal or bilingual-friendly; the Japanese retro community is a + big chunk of the real audience. +- Let the soundtrack carry the mood — cozy games live or die on music. +- Marketing hook: "run a coffee shop on real MSX2 hardware." diff --git a/projects/kissaten_yugure/docs/PLAN.md b/projects/kissaten_yugure/docs/PLAN.md new file mode 100644 index 0000000..d621dc0 --- /dev/null +++ b/projects/kissaten_yugure/docs/PLAN.md @@ -0,0 +1,518 @@ +# Kissaten Yūgure — Production plan + +Companion to `DESIGN.md` (what the game is) and `CONVERSATION.md` (why). +This document is **how it gets built**: milestone order, the cast, the story +architecture, and what has been deliberately cut. + +Status at time of writing: **M1 complete** (scope ladder stage 1). + +--- + +## 1. Organizing principle + +The code in this project is small. Stage 1 — shop scene, customer, brew +minigame, full serve loop — took one sitting and fits in 17KB of a 128KB ROM. +Every remaining *system* is comparably small. + +The risk is content. Four characters with real arcs is roughly 60 scenes of +writing and 12 portraits of pixel art. The failure mode for a project like +this is producing all of that before discovering the core moment doesn't land. + +So the plan is ordered by one rule: + +> **Reach the emotional test on the least content possible, then build the +> machinery around what you learned.** + +`DESIGN.md` §8 states the test directly: *if serving the exam student his +usual and getting one new line of his story feels good, the game works.* +Everything before M3 exists only to make that test possible. Everything after +M3 is scaling something already proven. + +--- + +## 2. Milestones + +Each milestone has an exit criterion. Don't advance without meeting it. + +Each also lists **what it requires** — split into what I need *from you* +(decisions, writing, art) and what must be *in place* technically. Anything +under "from you" is a hard block: I can't guess my way past it, and guessing +wrong is expensive to unwind later. + +### M1 — Serve loop ✅ *(complete)* + +Shop scene, one customer, siphon brew minigame, serve loop, no story. + +**Exit:** ✅ A customer arrives, orders, is served a brew of variable quality, +reacts, and leaves; the counter increments and the next arrives. + +### M2 — The day spine + +The thin half of scope-ladder stage 2. Day counter, three phase transitions +(morning prep → open hours → evening close), save/restore, clock and day in +the status bar. + +Money is deliberately **not** built here beyond a displayed number. It gates +nothing until M9, and a value that gates nothing isn't worth debugging yet. + +This milestone exists because the day counter is the spine every dialogue +trigger hangs from — arc pacing, `last_visit_day`, "he always comes when it +rains" patterns. + +**Requires — from you:** +- **Save medium.** My recommendation: **RTC CMOS**. Every MSX2 has it, it + holds ~50 bytes, the `SaveState` in `DESIGN.md` §6 is ~40, and + `msxgl_config.h` already enables `RTC_USE_SAVEDATA` + `RTC_USE_SAVESIGNED`. + Alternatives are cartridge SRAM (needs mapper support, better capacity) or + the FM-PAC's SRAM (needs the cartridge present). Say the word if you'd + rather not depend on CMOS. +- **Day length.** `DESIGN.md` says 5–10 real minutes. I need one number to + tune against, plus a rough customers-per-day count (I'd start at 4–6). +- **Clock model:** does time advance in real seconds, or one tick per customer + served? Event-driven is more forgiving of a player who walks away — which + suits the cozy contract — and it's what I'd pick. + +**Requires — in place:** nothing beyond M1. + +**Produces:** `SaveState` struct, phase state machine, save/load module. + +**Status:** day counter, three phases, journal line and `SaveState` are done +and playable. **Persistence is blocked** — sector I/O from a cartridge ROM +reports success without transferring data; full findings in the disk section +of `../CLAUDE.md`. + +**Pacing dial, revisited at the end.** `CUSTOMERS_PER_DAY` in `kissaten.c` is +the only number that sets day length. Worth reconsidering at M10 as a +player-facing choice — offered once at first boot ("how long should a day +be?"), or as a shelf option alongside the records. A game with no fail states +can afford to let the player set its own tempo, and it costs one saved byte. +Not worth building before the content exists: the right default only becomes +obvious once days have stories in them. + +### M3 — Vertical slice ← **the milestone that matters** + +One regular. Six beats. **Hardcoded in C.** No YAML, no compiler, no engine, +no data format. + +Build the exam student (§3 below) because their arc is the most legible: the +"usual" is visually distinct (cream soda, not coffee), the want is stated +early, and the change is a single order switch the player can *see*. + +Also needed: order recognition — the customer asks for something specific, and +serving it (vs. not) advances or holds the arc. + +**Requires — from you:** +- **Aki's six beats, as written text.** 6 × 3 lines × 26 chars. This is the + hard block on M3 — there is no version of this milestone without it. Either + you write them, or you tell me to draft them and review. If I draft: I need + a tone sample you like (a scene from a game or book that hits the register + you want) so the first pass isn't a guess. +- **The item list at this point.** Minimum coffee + cream soda, but tell me if + the toast set or curry from `DESIGN.md` §2 should exist yet. +- **A scratch portrait for Aki — 48×48, three expressions.** Crude is fine; + ugly is fine. But M3's exit criterion is an *emotional* judgement, and + judging it with a blank brown rectangle where a face belongs stacks the deck + against the test. This is the one place art work moves earlier than M6, and + it's worth it. + +**Requires — in place:** M2's day counter (arc beats are paced by day). + +**Produces:** hardcoded arc, order-recognition logic, "the usual" concept. + +**Exit:** The `DESIGN.md` test. Serve the student their usual, get the next +beat of their story, and want to come back tomorrow to see the following one. +If this is flat, stop and fix the *writing*, not the code. + +### M4 — Dialogue engine and compiler + +Now — and only now — extract the engine from what M3 taught. Scene data model, +trigger evaluation, `tools/compile_dialogue.js` emitting a binary bank, and +`BankedCall = true` re-enabled (see §7 hazards). + +Port the student's hardcoded arc to data and prove it plays identically. + +**Requires — from you:** +- **Sign-off on the scene schema before I write the compiler.** I'll propose + the YAML shape as a one-page sample; reviewing it costs ten minutes and + changing it after 60 scenes are authored costs a rewrite. +- **The string-ID decision** from §4 — whether to route text through IDs now + for a possible Japanese pass later. Cheap today, expensive to retrofit. + +**Requires — in place:** +- M3 proven (the schema should be derived from real content, not imagined). +- `node` on PATH for the compiler — already working, documented in + `../CLAUDE.md`. +- `BankedCall = true` re-enabled, carefully. See §7. + +**Produces:** `tools/compile_dialogue.js`, `data/dialogue/*.yaml`, the banked +dialogue blob, and the bank layout that everything after M4 slots into. + +**Exit:** Adding a scene requires editing a YAML file and rebuilding. Zero C +changes. + +### M5 — Second regular and visitor logic + +Add the stationmaster. Build the weighted arrival table from `DESIGN.md` §3 — +roll per open-hours tick, filtered by season, weather, time-of-day, and +days-since-last-visit. + +**Requires — from you:** +- **Ōta's full arc** (~15 beats). Same authoring question as M3 — yours or + mine to draft. +- **Initial arrival weights**, or approval of a table I propose. + `DESIGN.md` §3 wants this tunable in a spreadsheet; I'd rather hand you a + starting table to react to than ask you to invent numbers cold. +- **How visible the pattern should be.** "The stationmaster always comes when + it rains" only lands if it's *nearly* deterministic. My instinct is to make + weather-driven arrivals much stronger than feels statistically tasteful, + because the player needs to notice. + +**Requires — in place:** M4 engine and compiler. + +**Produces:** visitor weight table, arrival roll, multi-arc scheduling. + +**Exit:** Two arcs interleave over a week of play without colliding, and the +arrival pattern is legible enough that you can predict who's coming. + +### M6 — Art pass + +The real background bitmap on the palette-slot layout already reserved in +code (sky 8–11, wood/warm 4–7), plus 12 portraits (4 characters × 3 +expressions). Retire the placeholder `HMMV` rectangles. + +**Requires — from you:** *(this is the milestone with real lead time — start +it early, see §5)* +- **Who makes the art.** You, a commission, or an AI-assisted pipeline like + the music one. This answer determines whether M6 is a week or a quarter. +- **One background bitmap**, 256×212, 16 colors. +- **12 portraits**, 48×48, three expressions each (neutral / happy / + troubled) for the four regulars. +- **Critically: the palette spec goes to the artist *before* they draw.** Sky + tones must live in slots 8–11 and wood/warm tones in 4–7, because that + layout is what makes M7's seasons free. Art drawn in open color and + quantized afterwards will land in the wrong slots and silently destroy the + seasonal system. I can generate the exact palette table and a reference + swatch image to hand over. + +**Requires — in place:** +- A PNG → SC5 conversion path. `projects/mazegame` already has working + `convert_screens.py` / `convert_tiles.py` precedent to adapt, plus MSXtk. + +**Produces:** converted asset banks, page-1 warehouse layout, blit routines. + +**Exit:** No procedural placeholder fills remain in the shop scene. + +### M7 — Seasons, weather, palettes + +Scope-ladder stage 4. Palette tables per season × time-of-day, optional lerp +across the in-game day, weather affecting the arrival table. + +Deliberately placed *after* the art pass and *after* two working arcs. See §7. + +**Requires — from you:** +- **How many palette sets.** 4 seasons × 3 times of day = 12 tables at 32 + bytes each (384 bytes — trivial). But that's 12 moods to *art-direct*, which + is the real cost. Fewer, done well, beats twelve done mechanically. +- **Mood direction** for each: I can propose the RGB tables, but "rainy + autumn dusk" needs your eye to confirm it feels right, not mine. + +**Requires — in place:** **M6 must be done.** This milestone is a no-op +without a background authored on the palette-slot layout — that dependency is +the entire reason M7 sits after the art pass instead of before it. + +**Produces:** palette tables, time-of-day lerp, weather → arrival hookup. + +**Exit:** A spring morning and a rainy autumn dusk are visibly different moods +on identical bitmap data. + +### M8 — Remaining cast + +The manga artist and the widow, full arcs. This is the largest pure-writing +block in the project — roughly 30 scenes. + +**Requires — from you:** +- **Fujimoto's and Nakajima's arcs** — ~30 beats total, the single largest + writing block in the project. +- **A ruling on Nakajima's reveal.** Her second coffee is the best hook in the + cast precisely because the player solves it unaided. I need to know how far + you're willing to go without confirming it — my instinct is *very* far, and + that it should never be stated outright by anyone but her. + +**Requires — in place:** M4 engine, M6 portraits. + +**Exit:** All four arcs complete and reachable in one playthrough. + +### M9 — Music and upgrades + +6–8 cozy loops via the existing `msx-music-generator` pipeline. The +record-player upgrade turns the jukebox into both a feature and a home for +every generated track. Money finally gates something. + +**Requires — from you:** +- **Which sound chip.** This needs settling, because the docs and your actual + toolchain disagree: `DESIGN.md` §7 and `CLAUDE.md` describe a PSG / MML → + NDP pipeline, but the working pipeline you built for `mazegame` is + `msx-music-generator` → **lVGM + ayFX**, using MSX-Music (YM2413) on an + MSX2+. This project targets plain MSX2, where FM is *not* standard — it + needs an FM-PAC. Options: PSG-only (universal, thinner), FM with PSG + fallback (best sound, more work), or retarget to MSX2+. I'd take **PSG-only + through the lVGM pipeline you already have**, and treat FM as a bonus path. + Either way `DESIGN.md` §7 should be updated to match reality. +- **Briefs for 6–8 tracks:** mood, rough tempo, loop length. Morning, + afternoon, evening, rain, plus a theme or two for story beats. +- **Upgrade list and prices** — the point at which money finally means + something. + +**Requires — in place:** money economy from M2 promoted to actually gating. + +**Exit:** Music changes with time-of-day and weather; the player can choose a +record. + +### M10 — Ending, polish, hardware + +One quiet ending (see §6). Real-hardware verification, 50/60Hz check, timing +pass on the brew minigame. + +**Requires — from you:** +- **The ending text**, and what triggers it — my recommendation is a day count + reached *after* all four arcs resolve, so it can never cut a story short. +- **Real hardware, or the machine profiles you care about.** I can only verify + against openMSX here; C-BIOS has already shown behavioural differences from + real BIOS during M1, so emulator-passing is not hardware-passing. +- **50Hz or 60Hz as the primary target** (PAL vs NTSC timing for the brew + minigame's feel). + +--- + +## 3. The cast + +`DESIGN.md` §3 suggests six to eight regulars and offers four examples. **Take +the four examples as the entire cast.** Reasoning in §6. + +Each has a **want**, a **secret**, and a **change**. The want is stated early. +The secret is never announced — it's inferred from behaviour, then eventually +confirmed in one quiet line. The change is small and late. + +### Hoshino Aki — the exam student + +- **Usual:** cream soda float. A child's order they're faintly embarrassed by. +- **Rhythm:** weekday evenings, then scarcer as exams approach. +- **Want:** to pass the Tokyo entrance exams. +- **Secret:** the course is their parent's choice. They want to study + something else and haven't said so to anyone. +- **Change:** orders coffee instead — once, without comment. The player will + notice before they understand it. +- **Why first:** the arc is legible, the usual is visually distinct from every + other order, and the change is expressible entirely through the serve + mechanic. + +### Ōta-san — the retired stationmaster + +- **Usual:** black coffee, no sugar. Arrives at the same minute daily. +- **Rhythm:** early hours; rain makes him certain to appear. +- **Want:** to have somewhere to be at a fixed time. +- **Secret:** the line he worked was cut back before he retired. The precision + he still keeps is for a timetable that no longer exists. +- **Change:** stops apologising for lingering. + +### Fujimoto — the manga artist + +- **Usual:** strongest coffee available, endless refills, toast set when + reminded to eat. +- **Rhythm:** very late; a monthly deadline cycle drives their frequency and + their panic. +- **Want:** to finish the chapter. +- **Secret:** the series is ending, and they come here to be somewhere that + isn't the desk. +- **Change:** the shop appears in the manga. They show you the page. + +### Nakajima-san — the widow + +- **Usual:** two coffees. Always two. +- **Rhythm:** slow, seasonal, tied to specific weather and dates. +- **Want:** to keep a routine that belonged to two people. +- **Secret:** the second coffee is his order, and it goes cold every time. +- **Change:** one day she orders one. No speech about it. +- **Note:** her hook is the strongest in the cast because it's **visible in + the serve mechanic before it is ever explained**. The player solves it + themselves. Protect that — do not have anyone explain her early. + +--- + +## 4. Story architecture + +### Arc shape + +Twelve to eighteen beats per character. A beat is three lines of 26 +characters — a *moment*, not a scene. + +| Beats | Function | +|---|---| +| 1–3 | Establish the routine. Player learns the usual. | +| 4–8 | A crack. Mentioned in passing, never explained. | +| 9–14 | It develops. Serving choices nudge; they never gate. | +| 15+ | Resolution — quiet, undramatic, small. | + +Cozy resolves **gently**. No reveals, no tragedy delivered as a twist. Ōta +doesn't confess a catastrophe; he admits the house is too quiet now. Restraint +is the genre, and it's also cheaper to write well than melodrama is. + +### Authoring order + +Write **one arc completely** before starting the next. Writing everyone's +first three beats produces four strangers and no one you know. + +### Trigger vocabulary + +The scene format is `(conditions) → (portrait, expression, text, effects)`. +The condition language is the real design work — keep it minimal: + +``` +day >= N season == S +arc_stage == N weather == W +affinity >= N phase == P +served == ITEM absent >= N days +``` + +Eight condition types cover every behaviour described in `DESIGN.md`. +**Resist adding a ninth.** Every addition multiplies the authoring surface and +the testing burden, and the temptation will be strongest exactly when the +writing is hardest. + +### Effects + +Scenes may raise affinity, advance `arc_stage`, set a flag, or unlock an item. +Nothing else. No arbitrary state mutation — that path ends in a scripting +language nobody asked for. + +### Text and localisation + +Current font is ASCII at 26 characters per line. `DESIGN.md` §9 flags the +Japanese retro community as a real slice of the audience. + +**Recommendation:** don't build kana support now, but route every player-facing +string through an ID from the start. A Japanese pass then becomes a font and a +table swap rather than re-authoring the entire script. This costs almost +nothing today and is expensive to retrofit. + +--- + +## 5. Budgets + +### ROM — not a constraint + +| Item | Size | +|---|---| +| Stage 1 code | ~17 KB | +| All dialogue text (~8,000 chars) | ~8 KB | +| Portraits (4 × 3 × 48×48 @ 4bpp) | ~14 KB | +| Background bitmap (256×212 @ 4bpp) | ~27 KB | +| Music (6–8 lVGM loops) | ~20 KB | + +Comfortably inside 128 KB with room for a second background or seasonal +variants if they ever prove necessary. **ROM size will not be what limits this +project.** + +### Writing — the actual constraint + +Four characters × ~15 beats = **~60 scenes**. At three beats per sitting that +is twenty writing sessions. This is the single largest line item in the +project and it does not compress. + +**Status: a complete first draft exists** in `SCRIPT.md` — all 60 beats, plus +order lines, per-character brew reactions and ambient lines, every line +checked against the 26-character box. What remains is your edit pass, which is +a different and much smaller job than writing from nothing. + +### Art — the long pole + +12 portraits and one background. Tractable at four characters; at eight it +roughly doubles and becomes the thing that stalls the project. + +**Open question:** who draws these? The plan assumes 12 portraits exist by M6. +If that's commissioned or AI-assisted work, it needs starting well before M6, +since it's the only item here with external lead time. + +--- + +## 6. Deliberate cuts + +| `DESIGN.md` says | Plan says | Why | +|---|---|---| +| 6–8 regulars | **4** | 8 × 15 scenes and 24 portraits is where this project dies. Four in a small provincial kissaten is also *more* intimate — the cut serves the atmosphere pillar rather than fighting it. | +| Money economy | Minimal until M9 | Gates nothing before the record player. | +| "Endings" (plural) | **One** quiet ending | Per-character arc completions already provide the sense of closure. Multiple endings multiply testing for a game with no fail state to contrast against. | +| Morning menu planning | Deferred, possibly cut | Adds a decision phase before the loop that carries the game. Revisit only if open hours feel thin. | +| Branching dialogue choices | Defer to M4 | M3 will reveal whether they're needed at all. My expectation: rarely. | + +"Small, finishable scope. When in doubt, cut." — `CLAUDE.md`, design pillars. + +--- + +## 7. Risks and known hazards + +**Seasons are procrastination.** M7 is the most enjoyable thing on this list — +palette work produces beautiful results in minutes with no writing involved. +It will call loudly every time the script gets difficult. It also adds nothing +to whether the game is *good*. Keep it as the reward after two arcs work. + +**Engine-before-content.** The reason M3 is hardcoded. Dialogue systems get +over-built when designed before any dialogue exists; you end up supporting +features nothing uses and discovering the one thing you need is awkward. + +**Cast creep.** Four will feel thin around M5, when two arcs exist and the +shop seems empty. It won't feel thin at M8. Do not add a fifth regular before +all four are complete. + +**`BankedCall = true` is a known hazard.** Enabling it with nothing actually +banked corrupted RAM globals — the state machine ran wild. It's off, and M4 is +the deliberate point to turn it back on, with the banked data that justifies +it. See the platform gotchas in `../CLAUDE.md`. + +**Keyboard reads need interrupt protection.** Documented in `../CLAUDE.md`; +applies to every new input added. + +--- + +## 8. Decision queue + +Everything the milestones above need from you, in the order it becomes +blocking. Items marked **lead time** should be started well before the +milestone that consumes them. + +| # | Needed for | Decision | My recommendation | +|---|---|---|---| +| 1 | M2 | ~~Save medium~~ → **save disk (.dsk), asset supplied** | ⚠ decided, but **not working yet** — see the disk section in `../CLAUDE.md`. Sector I/O reports success without transferring. **The RTC CMOS fallback I previously suggested is not viable**: block 3 is 13 nibbles, and MSXgl's `RTC_SaveData()` stores **6 bytes**, against a ~40-byte `SaveState`. Real fallbacks are cartridge SRAM, the FM-PAC SRAM, or shrinking the save to a 6-byte fingerprint | +| 2 | M2 | ~~Day length~~ → **5 min / 5 customers** | ✅ done. One constant, `CUSTOMERS_PER_DAY` in `kissaten.c` | +| 3 | M2 | ~~Clock model~~ → **event-driven** | ✅ done. Customers are the clock | +| 4 | M3 | ~~Who writes the dialogue~~ → **drafted by me, you edit** | ✅ resolved | +| 5 | M3 | ~~Aki's six beats~~ | ✅ drafted — all four arcs are in `SCRIPT.md`, awaiting your edit | +| 6 | M3 | Scratch portrait for Aki | Crude is fine, but *something* — the test needs a face | +| 7 | M3 | Item list at slice stage | Coffee + cream soda only | +| 8 | **M6 — lead time** | **Who makes the art** | Decide now, not at M6; it's the only external dependency | +| 9 | M4 | Scene schema sign-off | Ten-minute review, saves a rewrite | +| 10 | M4 | String IDs for future JP text | Yes — nearly free now, expensive later | +| 11 | M5 | Arrival weights | React to a table I propose | +| 12 | M6 | Palette spec to artist **before** drawing | Non-negotiable if M7 is to stay cheap | +| 13 | M7 | Number of palette sets | Fewer, art-directed well | +| 14 | M8 | How overt Nakajima's reveal gets | Never stated by anyone but her | +| 15 | M9 | Sound chip: PSG / FM / retarget | PSG-only via your existing lVGM pipeline | +| 16 | M10 | Hardware access + 50/60Hz target | — | + +Items 1–3 are small and I can proceed on my recommendations if you'd rather +not think about them. Items 4 and 8 are the two that genuinely shape the +project, and both want answering now. + +## 9. Immediate next actions + +1. ✅ **M2 day spine** — day counter, three phases, journal line. Done and + playable. **Persistence still blocked**; see the disk section in + `../CLAUDE.md` for everything established so far. +2. ✅ **Aki's beats as prose** — done, along with the other three arcs, in + `SCRIPT.md`. Awaiting your edit pass. +3. ⬜ **Unblock saving.** Either keep digging at the disk path, or switch + medium. Note the RTC CMOS fallback is off the table (§8 item 1). +4. ⬜ **Start the portrait question** (§5) — still the only item with external + lead time, and now the longest pole by some distance. + +3 and 4 are independent; 4 can start today and should. diff --git a/projects/kissaten_yugure/docs/SCRIPT.md b/projects/kissaten_yugure/docs/SCRIPT.md new file mode 100644 index 0000000..31ba65c --- /dev/null +++ b/projects/kissaten_yugure/docs/SCRIPT.md @@ -0,0 +1,492 @@ +# Kissaten Yūgure — Script + +First draft of all four character arcs, plus order lines, brew reactions and +ambient lines. Written as prose-first per `PLAN.md` §9 — no data format yet. + +**This is a draft to edit, not to approve.** The characters are yours; if +anyone's voice is wrong, it's wrong now and cheap to fix. + +--- + +## How to read this + +Every beat is one dialogue box: **3 lines × 26 characters**, ASCII, as laid +out in `DESIGN.md` §4. Every line here has been checked against that limit. + +``` +**B4 · the crack** — `day>=7` — troubled + Mock exam results came. + Law faculty. Tokyo. + Father framed the sheet. +``` + +- **B4** — beat number. Beats fire in order; a beat's trigger is *also* + gated on the previous beat having played (`arc_stage`). +- **Trigger** — conditions from the vocabulary in `PLAN.md` §4. +- **Expression** — `neutral` / `happy` / `troubled`. Three per character, per + `DESIGN.md` §5. + +Triggers are deliberately loose. Tighten them once pacing is playable — +day numbers here assume roughly one visit per character per 2 days. + +--- + +## 1. Hoshino Aki — the exam student + +> Cream soda. Weekday evenings, scarcer as exams approach. +> **Want:** pass the Tokyo entrance exams. +> **Secret:** the course is her father's choice. She wants astronomy, at a +> small college in Sendai with an observatory on the roof. +> **Change:** orders coffee instead of the soda. + +The dusk sky through the shop window is her arc's spine — she is the reason +that window is there. **Beats 1–6 are the M3 vertical slice.** + +**B1** — `day>=2, phase=evening` — neutral + Cream soda, please. + The big one, if that's + all right. + +**B2** — `day>=3` — neutral + Exams in the spring. + They say third year goes + fast. It hasn't. + +**B3** — `day>=5, served=CREAM_SODA` — happy + You remembered. + Nobody at home remembers + what I like. + +**B4 · the crack** — `day>=7` — troubled + Mock exam results came. + Law faculty. Tokyo. + Father framed the sheet. + +**B5** — `day>=9, weather=rain` — neutral + Can't see the sky today. + ...Sorry. That was a + strange thing to say. + +**B6 · slice ends** — `day>=11, served=CREAM_SODA` — neutral + Master, do you ever look + out that window and think + about anything else? + +**B7** — `day>=13` — troubled + Father's friend teaches + at the Tokyo faculty. + It's arranged, basically. + +**B8** — `day>=15, absent>=3` — troubled + Sorry I haven't come. + Cram school added hours. + I missed this place. + +**B9** — `day>=17, affinity>=60` — neutral + There's a small college + in Sendai. They have an + observatory on the roof. + +**B10** — `day>=19` — happy + I wrote for a prospectus. + It came in a brown + envelope. I hid it. + +**B11** — `day>=21, weather=clear, phase=evening` — happy + Look - that's Vega. + You can see it even from + town, if you know where. + +**B12** — `day>=23` — troubled + Applications close on + the twentieth. + I haven't told them. + +**B13 · the change** — `day>=25, served=COFFEE` — neutral + Not the soda today. + Coffee. Black, like you + make for Ota-san. + +**B14** — `day>=27` — troubled + I told them. + Mother cried. Father + hasn't spoken since. + +**B15 · resolution** — `day>=30` — happy + Sendai. I posted it. + Father drove me to the + post office. Said nothing. + +--- + +## 2. Ōta-san — the retired stationmaster + +> Black coffee, no sugar. Early mornings. Rain makes him certain. +> **Want:** somewhere to be at a fixed time. +> **Secret:** the ten-fifteen was cut in his last spring. His precision keeps +> a timetable that no longer exists. +> **Change:** stops apologising for lingering; orders a second cup. + +**B1** — `day>=1, phase=morning` — neutral + Coffee. Black. + Seven forty. You open + at seven forty. + +**B2** — `day>=3, phase=morning` — neutral + Your father opened at + seven forty as well. + I never asked him why. + +**B3** — `day>=5, weather=rain` — neutral + Rain. I always came in + the rain. The platform + had no roof at the end. + +**B4** — `day>=7, served=COFFEE` — happy + Thirty-one years I + poured tea on that + platform. Never coffee. + +**B5 · the crack** — `day>=9` — troubled + They cut the ten-fifteen + in my last spring. + Nobody came to say so. + +**B6** — `day>=11` — neutral + I still wake at five. + The body keeps a + timetable of its own. + +**B7** — `day>=13, absent>=2` — troubled + Forgive me. I'm keeping + your counter. I'll go. + ...No. Not yet. + +**B8** — `day>=15, affinity>=50` — neutral + The house is not small. + That is the trouble. + It was sized for four. + +**B9** — `day>=17` — neutral + My daughter telephones + on Sundays. Eleven a.m. + She is very punctual. + +**B10** — `day>=19, weather=rain` — happy + You knew I'd come. + The pot was already on. + That is a fine thing. + +**B11** — `day>=21` — neutral + The station is a + convenience store now. + I have not gone in. + +**B12** — `day>=23` — troubled + I went in. For batteries. + The ticket window is + where the freezers are. + +**B13** — `day>=25, affinity>=80` — neutral + You keep good time, + Master. That is not a + small compliment from me. + +**B14 · the change** — `day>=27` — happy + I'll have a second cup. + I am not going anywhere + in particular today. + +**B15 · resolution** — `day>=30` — happy + Seven forty. + Tomorrow also, I think. + That is all I wanted. + +--- + +## 3. Fujimoto — the manga artist + +> Strongest coffee, endless refills, toast set when reminded to eat. +> Very late. Frequency and panic follow a monthly deadline cycle. +> **Want:** finish the chapter. +> **Secret:** the series is being cancelled. Comes here to be anywhere that +> isn't the desk. +> **Change:** the shop appears in the final chapter. + +Fujimoto carries the comedy. A cozy game with four sad people is not cozy — +it's a wake. Let them be funny about their own misery. + +**B1** — `day>=2, phase=evening` — neutral + Strongest you have. + No, stronger. I have + eleven pages by Friday. + +**B2** — `day>=4` — troubled + Do you know how long + eleven pages is? + Neither do I anymore. + +**B3** — `day>=6, served=COFFEE` — neutral + Refill. And again after. + I'll pay for the pot. + Cheaper by the hour. + +**B4** — `day>=8` — troubled + Editor called me + 'reliable' today. That's + what they say before. + +**B5** — `day>=10, absent>=4` — troubled + Four days at the desk. + I have drawn one hand. + It's a bad hand. + +**B6 · the crack** — `day>=12` — troubled + I don't come here to + work, Master. + I come here to not. + +**B7** — `day>=14, served=TOAST` — happy + When did I last eat? + Don't answer that. + ...Thank you for this. + +**B8** — `day>=16` — neutral + Thirty-two volumes. + Nine years. My whole + twenties, in a drawer. + +**B9 · the secret** — `day>=18, affinity>=50` — troubled + They're ending it. + Two more chapters. + I asked for three. + +**B10** — `day>=20` — troubled + Nine years and I get + two chapters to say + goodbye properly. + +**B11** — `day>=22` — neutral + I've been drawing this + counter. Not for work. + Just drawing it. + +**B12** — `day>=24, weather=rain` — neutral + Rain's good for pages. + Nobody expects you + anywhere in the rain. + +**B13 · the change** — `day>=26, affinity>=70` — happy + The last chapter is + set in a coffee shop. + I hope that's all right. + +**B14** — `day>=28` — happy + Here. Page forty. + That's your counter. + That's your lamp. + +**B15 · resolution** — `day>=31` — happy + It's done. Nine years. + I'm starting something + new. Set here, I think. + +--- + +## 4. Nakajima-san — the widow + +> Two coffees. One with milk, one black. Slow, seasonal, weather-tied. +> **Want:** keep a routine that belonged to two people. +> **Secret:** the second coffee is his. It goes cold every time. +> **Change:** one day she orders one. + +**Handle with care.** Her hook is the strongest in the game because the +player solves it unaided — the order is visible in the serve mechanic long +before anything is said. Per `PLAN.md` §8 item 14: **no one but Nakajima-san +ever explains it**, and she does so obliquely, at B4, and never apologises +for it. Do not let another character mention her situation. + +Her arc runs on a slower day scale than the others by design — she should +feel like someone you see less often. + +**B1** — `day>=4` — neutral + Two coffees, please. + One with milk. + The other black. + +**B2** — `day>=8` — neutral + The same, please. + You remember which is + which. That's good. + +**B3** — `day>=12, served=TWO_COFFEE` — neutral + No, don't clear it yet. + Leave it a while. + I'm not finished. + +**B4 · the crack** — `day>=16, season=autumn` — neutral + He liked the autumn. + Said the town looked + better in brown. + +**B5** — `day>=20` — neutral + Forty-one years of + two coffees. + It's a hard habit. + +**B6** — `day>=24, weather=rain` — troubled + He'd have hated today. + He walked everywhere. + Never once took a bus. + +**B7** — `day>=28, affinity>=40` — neutral + Your father served us + at that end table. + We were young then. + +**B8** — `day>=32` — neutral + I know it goes cold. + That isn't the point + of it, Master. + +**B9** — `day>=36, season=winter` — troubled + Three years this month. + People stop asking after + the second one. + +**B10** — `day>=40` — neutral + My son says it's time + I stopped. He means + well. He means well. + +**B11** — `day>=44, season=spring` — neutral + The cherry by the + station is out. + He'd have said it's early. + +**B12** — `day>=48, affinity>=70` — neutral + I don't cry about it. + I just like ordering + for two. It's an hour. + +**B13** — `day>=52` — neutral + You never asked me why. + That's why I come here + and not the new place. + +**B14 · the change** — `day>=56` — neutral + One coffee today. + ...With milk. + Just the one. + +**B15 · resolution** — `day>=60, affinity>=90` — happy + One, please. + I'll take the window + seat. He never liked it. + +--- + +## 5. Order lines + +Spoken on arrival, before the brew. Rotate at random within a character; +they're texture, not story. + +**Aki** + Cream soda, please. + The usual, Master. + Soda. I've earned it. + +**Ōta-san** + Coffee. Black. + The usual, if you please. + Coffee. No sugar. Ever. + +**Fujimoto** + Coffee. Keep it coming. + Something that will hurt. + Strongest. And toast. + +**Nakajima-san** + Two coffees, please. + The same as always. + Two. Milk in one. + +--- + +## 6. Brew reactions + +Replaces the generic quality lines currently hardcoded in `kissaten.c`. +Quality bands as implemented: 3 = perfect, 2 = good, 1 = fair, 0 = off +(thin or over-strong depending on which side of centre you locked). + +| | Aki | Ōta-san | Fujimoto | Nakajima-san | +|---|---|---|---|---| +| **3** | The ice is just right. | That is correct coffee. | Oh, that's cruel. Good. | He'd have liked that one. | +| **2** | Mm. That's a good one. | Good. Very good. | That'll do damage. | Very good, Master. | +| **1** | Thank you, Master. | Thank you. | Fine. It's wet. | Thank you, dear. | +| **0 thin** | It's a bit flat today. | A little pale today. | Is this tea? | A little weak today. | +| **0 strong** | Ooh. That's a lot. | Strong. I'll manage. | Perfect. Awful. Perfect. | Oh my. That's bracing. | + +--- + +## 7. Ambient lines + +Shown while the shop is empty. The four currently in `kissaten.c` are +placeholders; these replace them. Weather- and season-gated where marked. + +**Any time** + The radio hums softly. + The kettle ticks as it + cools. + Steam on the window glass. + A bicycle bell, outside. + The lamp buzzes, once. + +**Rain** — `weather=rain` + Rain taps at the window. + Someone runs past under + a newspaper. + The gutter is singing. + +**Clear evening** — `weather=clear, phase=evening` + Dusk settles over the + street. + The sky goes orange, then + nothing. + +**Autumn** — `season=autumn` + A leaf is stuck to the + door. + +**Winter** — `season=winter` + The window fogs faster + than you can wipe it. + +--- + +## 8. Notes and open questions + +**Aki's soda doesn't fit the brew minigame.** The siphon minigame is +coffee-specific, but her usual is a cream soda. My recommendation: **soda +skips the minigame entirely** — it's poured, not brewed. That's not a +workaround, it's an improvement: her visits get a different, lighter rhythm +than the three coffee drinkers, which suits a teenager dropping in after cram +school. Her B13 switch to coffee then means she enters the minigame for the +first time, which is a lovely mechanical echo of the story beat. + +**Nakajima-san's two coffees imply a two-brew serve.** Simplest reading: one +minigame, both cups. Making the player brew twice would be tedious and would +also make the second cup feel like a chore rather than a small sadness. + +**Cross-references are deliberate.** Aki's B13 names Ōta-san; Ōta's B2 and +Nakajima's B7 both reference your father running the shop before you. These +cost nothing and make the cast feel like one place rather than four +unrelated visitors. Add more of these freely — but never let anyone discuss +Nakajima-san's second cup. + +**Nobody dies during the game, nobody is cured.** All four arcs resolve by +someone deciding something small. That is the genre working correctly. + +**Beat count is 60.** At three beats per sitting that's the twenty writing +sessions budgeted in `PLAN.md` §5 — already spent, if this draft survives +editing. diff --git a/projects/kissaten_yugure/emul/rom/kissaten.rom b/projects/kissaten_yugure/emul/rom/kissaten.rom new file mode 100644 index 0000000..bab2882 Binary files /dev/null and b/projects/kissaten_yugure/emul/rom/kissaten.rom differ diff --git a/projects/kissaten_yugure/kissaten.c b/projects/kissaten_yugure/kissaten.c new file mode 100644 index 0000000..aff7952 --- /dev/null +++ b/projects/kissaten_yugure/kissaten.c @@ -0,0 +1,923 @@ +// Kissaten Yūgure — stage 1 of the scope ladder (see docs/DESIGN.md §8): +// shop scene + one customer + siphon brew minigame + serve loop. No story, +// no money, no day cycle yet. All "art" is procedural placeholder fills; +// real bitmaps will replace the scene drawing and sprite data later. + +//============================================================================= +// INCLUDES +//============================================================================= +#include "msxgl.h" + +// Fonts data +#include "font/font_mgl_sample6.h" + +//============================================================================= +// DEFINES +//============================================================================= + +// Screen layout (DESIGN.md §4): 16px status bar, 132px shop scene, 64px dialogue +#define STATUS_Y 0 +#define STATUS_H 16 +#define SCENE_Y 16 +#define SCENE_H 132 +#define DLG_Y 148 +#define DLG_H 64 + +// Palette slots (DESIGN.md §5: sky in 8-11, wood/warm in 4-7, so seasons and +// time-of-day can later be palette swaps without touching the bitmap) +#define C_OUTLINE 1 // near black +#define C_WOOD_D 2 // dark wood / floor / shadow +#define C_WOOD_M 3 // mid wood (wall panels, door) +#define C_WOOD_L 4 // light wood (counter top) +#define C_LAMP 5 // warm lamp glow +#define C_CREAM 6 // cream (glassware, cup) +#define C_WALL 7 // muted upper wall +#define C_SKY1 8 // dusk sky, deep +#define C_SKY2 9 // dusk violet +#define C_SKY3 10 // dusk rose +#define C_SKY4 11 // horizon amber +#define C_COFFEE 12 // coffee brown +#define C_COAT 13 // customer coat blue +#define C_SKIN 14 // skin +#define C_TEXT 15 // warm white + +// G4 mode: one byte = two pixels of the same palette index +#define PX(c) (u8)(((c) << 4) | (c)) + +// Shop scene landmarks +#define COUNTER_TOP_Y 98 +#define DOOR_X 216 // where the customer appears/exits +#define SEAT_X 104 // where the customer stands at the counter +#define CUST_Y 84 // sprite top: bottom edge rests on the counter top +#define SIPHON_X 32 // siphon rig center +#define CUP_X 86 // served cup position on the counter + +// Sprites (mode 2 is implicit in SCREEN 5; two layers per customer) +#define SPR_CUST_FILL 0 // front layer +#define SPR_CUST_LINE 1 // outline layer behind +#define SPR_STEAM 2 +#define PAT_CUST_FILL 0 // 16x16 patterns: indices are multiples of 4 +#define PAT_CUST_LINE 4 +#define PAT_STEAM_A 8 +#define PAT_STEAM_B 12 + +// Day length. Event-driven clock: customers *are* the clock, so this single +// constant is the whole pacing dial (PLAN.md §8 items 2-3). +#define CUSTOMERS_PER_DAY 5 + +// Brew minigame gauge (drawn in the dialogue window) +#define GAUGE_X 64 +#define GAUGE_Y 186 +#define GAUGE_W 148 +#define GAUGE_H 10 +#define GAUGE_CENTER (GAUGE_W / 2) + +// Dialogue text layout: 48px portrait zone left, 3 text lines right +#define DLG_TEXT_X 64 +#define DLG_LINE_Y(n) (u8)(156 + (n) * 14) + +//============================================================================= +// READ-ONLY DATA +//============================================================================= + +// Palette: u16 = G<<8 | R<<4 | B, components 0-7 +#define RGB(r, g, b) (u16)(((g) << 8) | ((r) << 4) | (b)) +const u16 g_Palette[16] = { + RGB(0, 0, 0), // 0 (transparent) + RGB(1, 1, 1), // 1 outline + RGB(2, 1, 1), // 2 dark wood + RGB(4, 2, 1), // 3 mid wood + RGB(5, 3, 2), // 4 light wood + RGB(7, 5, 2), // 5 lamp glow + RGB(7, 6, 4), // 6 cream + RGB(3, 2, 2), // 7 muted wall + RGB(2, 1, 4), // 8 dusk deep + RGB(4, 2, 5), // 9 dusk violet + RGB(6, 3, 3), // 10 dusk rose + RGB(7, 4, 2), // 11 horizon amber + RGB(3, 1, 0), // 12 coffee + RGB(2, 3, 5), // 13 coat blue + RGB(7, 5, 4), // 14 skin + RGB(7, 7, 6), // 15 warm white +}; + +// 16x16 sprite patterns: 32 bytes each, left column rows 0-15 then right column. +// Customer silhouette, fill layer +const u8 g_SprCustFill[32] = { + 0x07, 0x0F, 0x1F, 0x1F, 0x1F, 0x0F, 0x07, 0x1F, // left, rows 0-7 + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x1F, 0x0E, 0x0E, // left, rows 8-15 + 0xE0, 0xF0, 0xF8, 0xF8, 0xF8, 0xF0, 0xE0, 0xF8, // right, rows 0-7 + 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xF8, 0x70, 0x70, // right, rows 8-15 +}; +// Customer silhouette dilated by 1px: black outline layer drawn behind the fill +const u8 g_SprCustLine[32] = { + 0x1F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x7F, + 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x3F, 0x1F, + 0xF8, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFC, 0xF8, +}; +// Steam wisps, two animation frames +const u8 g_SprSteamA[32] = { + 0x01, 0x03, 0x07, 0x0E, 0x1C, 0x1E, 0x0F, 0x07, + 0x03, 0x03, 0x07, 0x0E, 0x06, 0x00, 0x00, 0x00, + 0x80, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; +const u8 g_SprSteamB[32] = { + 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0xC0, 0xE0, 0x70, 0x38, 0x78, 0xF0, 0xE0, + 0xC0, 0xC0, 0xE0, 0x70, 0x60, 0x00, 0x00, 0x00, +}; + +// Ambient lines shown while the shop is empty +const c8* const g_Ambient[4] = { + "The radio hums softly.", + "Rain taps at the window.", + "The kettle ticks as it cools.", + "Dusk settles over the street.", +}; + +//============================================================================= +// GAME STATE +//============================================================================= + +enum State +{ + STATE_WAIT, // shop empty, waiting for the next customer + STATE_ARRIVE, // customer walks from door to counter + STATE_ORDER, // order shown, waiting for SPACE + STATE_BREW, // siphon minigame running + STATE_SERVE, // reaction to the brew + STATE_DRINK, // quiet moment with the cup + STATE_LEAVE, // customer walks back to the door +}; + +// Day phases (DESIGN.md §2). The open-hours phase holds the STATE_* loop above. +enum Phase +{ + PHASE_MORNING, // prep — day title card, waiting to open + PHASE_OPEN, // the serve loop runs + PHASE_EVENING, // close — tally, journal line, save +}; + +u8 g_State = STATE_WAIT; +u16 g_Timer = 120; // frames left in timed states +u8 g_CustX = DOOR_X; // customer sprite X +u16 g_Served = 0; // cups served, lifetime +u8 g_Ambience = 0; // rotating ambient line index +i16 g_Needle = 0; // brew gauge needle position +i8 g_NeedleDir = 3; +u8 g_Quality = 0; // 0 weak/overdone .. 3 perfect +bool g_Overdone = FALSE; // which side of center the lock landed on +u8 g_Frame = 0; +bool g_SpaceHit = FALSE; // SPACE pressed this frame (edge, sampled after Halt) +u8 g_SpacePrev = 0; + +u8 g_Phase = PHASE_MORNING; +u8 g_DayServed = 0; // customers served so far today +u8 g_DayQuality = 0; // summed brew quality today, for the journal line +u8 g_DiskErr = 0; // last disk error code (0 = fine) +bool g_HasDisk = FALSE; // a usable save disk was found at boot + +//============================================================================= +// SAVE STATE (DESIGN.md §6) +//============================================================================= + +// Bumped whenever the layout below changes; a mismatch is treated as "no save" +// rather than migrated — there is no shipped version to be compatible with yet. +#define SAVE_MAGIC 0x594B // 'KY' +#define SAVE_VERSION 1 + +typedef struct +{ + u8 id; + u8 arc_stage; // progress through their story + u8 affinity; // 0-255, raised by right orders + u8 last_visit_day; +} Regular; + +typedef struct +{ + u16 magic; + u8 version; + u16 day; + u8 season; // derives palette set + u8 weather; // affects visitor table + u16 money; + u16 served; + u8 unlocked_items; // bitmask: grinder, records, cat... + Regular regulars[8]; + u8 checksum; // sum of all preceding bytes, negated +} SaveState; + +SaveState g_Save; + +// One 512-byte sector staging buffer. Must live in RAM (page 3) — PHYDIO +// writes straight into it, and pages 1-2 are ROM on a cartridge target. +u8 g_Sector[512]; + +// Sector holding the save, derived from the disk's own BPB at boot rather +// than hardcoded, so a 360K disk works as well as the 720K one. +u16 g_SaveSector = 14; + +//============================================================================= +// DISK ACCESS +//============================================================================= + +// Number of disk drives, maintained by the Disk ROM at boot. Zero means there +// is no disk interface at all — and on such a machine (C-BIOS, for one) the +// PHYDIO hook is not patched and calling it is not safe. +#define M_NMBDRV_ADDR 0xF347 +#define M_MASTER_ADDR 0xF348 // main Disk ROM slot, 0 if there is none + +// PHYDIO trampoline, executed from RAM. +// +// PHYDIO (Main-ROM 0144h) reaches the Disk ROM through an inter-slot call, and +// the Disk ROM lives at 4000h — the same page as this cartridge. For the +// duration of the call our ROM is switched out of page 1, so the instruction +// making the call must not itself be in page 1. Copying these twelve bytes to +// RAM and calling *them* keeps the call site alive across the switch. +u8 g_DiskStub[21]; + +void Disk_InitStub() +{ + // Calls DSKIO (Disk ROM 4010h) through CALSLT rather than the Main ROM's + // PHYDIO wrapper at 0144h: PHYDIO jumps to a RAM hook that this machine + // leaves unpatched, so it returns carry-clear — reporting success while + // never touching the buffer. Going straight at the Disk ROM avoids it. + // + // IY high byte must hold the slot ID, and MASTER lives at F348h, so a + // word load from F347h lands it in IYh exactly as CALSLT wants. + static const u8 code[21] = + { + 0xF3, // di + 0xFD, 0x2A, 0x47, 0xF3, // ld iy, (0xF347) ; IYh = MASTER + 0xDD, 0x21, 0x10, 0x40, // ld ix, #0x4010 ; DSKIO + 0xCD, 0x1C, 0x00, // call 0x001C ; CALSLT + 0x38, 0x04, // jr c, err + 0x2E, 0x00, // ld l, #0 ; success + 0xFB, // ei + 0xC9, // ret + 0x6F, // err: ld l, a ; BIOS error code + 0xFB, // ei + 0xC9, // ret + }; + Mem_Copy(code, g_DiskStub, sizeof(code)); +} + +//----------------------------------------------------------------------------- +// Absolute sector read/write into g_Sector. +// carry clear = read, carry set = write +// A=drive B=sector count C=media ID DE=first sector HL=buffer +// Returns the BIOS error code, or 0 on success. +u8 Disk_Access(u16 sector, u8 write) __NAKED +{ + sector; // on stack + write; + +__asm + push ix + ld ix, #0 + add ix, sp + + ld e, 4(ix) // sector low + ld d, 5(ix) // sector high + ld a, 6(ix) // write flag + + ld hl, #_g_Sector + ld b, #1 // one sector + ld c, #0xF9 // media ID: 720K DS/DD + or a // test flag; also clears carry (= read) + jr z, phydio_go + scf // carry set = write +phydio_go: + ld a, #0 // drive A:. 'ld a,n' preserves flags + call _g_DiskStub // -> PHYDIO, from RAM. Returns code in L + + pop ix + ret +__endasm; +} + +//----------------------------------------------------------------------------- +// Read the BIOS Parameter Block and work out where the data area starts. +// Returns FALSE if no disk responded at all. +bool Disk_Probe() +{ + u8 drives = *(const u8*)M_NMBDRV_ADDR; + + // Both of these are set by the Disk ROM's INIT. Without them the entry + // points below are not safe to call — on C-BIOS this work area is never + // initialised at all, and calling anyway black-screens the machine. + if (drives == 0 || drives > 8) + return FALSE; + if (*(const u8*)M_MASTER_ADDR == 0) + return FALSE; + + Disk_InitStub(); + + // Sentinel: the disk entry points can return carry-clear ("success") + // without having touched the buffer, so a clean error code is not on its + // own proof that a read happened. Verify the buffer actually changed. + Mem_Set(0xAA, g_Sector, 16); + g_DiskErr = Disk_Access(0, 0); + if (g_DiskErr != 0) + return FALSE; + if (g_Sector[0] == 0xAA && g_Sector[11] == 0xAA) + return FALSE; // nothing was read; treat the disk as unusable + + u16 bytesPerSec = g_Sector[11] | ((u16)g_Sector[12] << 8); + u16 reserved = g_Sector[14] | ((u16)g_Sector[15] << 8); + u8 numFATs = g_Sector[16]; + u16 rootEntries = g_Sector[17] | ((u16)g_Sector[18] << 8); + u16 secPerFAT = g_Sector[22] | ((u16)g_Sector[23] << 8); + + if (bytesPerSec != 512) // anything else is not a disk we made + return FALSE; + + // Root directory occupies ceil(entries * 32 / 512) sectors + u16 rootSecs = (u16)((rootEntries + 15) >> 4); + g_SaveSector = reserved + (u16)numFATs * secPerFAT + rootSecs; + return TRUE; +} + +//----------------------------------------------------------------------------- +u8 Save_Checksum(const SaveState* s) +{ + const u8* p = (const u8*)s; + u8 sum = 0; + for (u8 i = 0; i < sizeof(SaveState) - 1; i++) + sum += p[i]; + return (u8)(0 - sum); +} + +//----------------------------------------------------------------------------- +// TRUE if a valid save was loaded; FALSE means "start a new game", which is +// also what a blank disk gives us — no error path for the player to hit. +bool Save_Load() +{ + if (!g_HasDisk) + return FALSE; + + g_DiskErr = Disk_Access(g_SaveSector, 0); + if (g_DiskErr != 0) + return FALSE; + + SaveState* s = (SaveState*)g_Sector; + if (s->magic != SAVE_MAGIC || s->version != SAVE_VERSION) + return FALSE; + if (s->checksum != Save_Checksum(s)) + return FALSE; + + Mem_Copy(s, &g_Save, sizeof(SaveState)); + return TRUE; +} + +//----------------------------------------------------------------------------- +bool Save_Write() +{ + if (!g_HasDisk) + return FALSE; + + g_Save.magic = SAVE_MAGIC; + g_Save.version = SAVE_VERSION; + g_Save.checksum = Save_Checksum(&g_Save); + + Mem_Set(0, g_Sector, sizeof(g_Sector)); + Mem_Copy(&g_Save, g_Sector, sizeof(SaveState)); + + g_DiskErr = Disk_Access(g_SaveSector, 1); + return (g_DiskErr == 0); +} + +//----------------------------------------------------------------------------- +void Save_Reset() +{ + Mem_Set(0, &g_Save, sizeof(SaveState)); + g_Save.day = 1; + g_Save.season = 0; + g_Save.money = 0; + g_Save.served = 0; + for (u8 i = 0; i < 8; i++) + g_Save.regulars[i].id = i; +} + +//============================================================================= +// HELPERS +//============================================================================= + +//----------------------------------------------------------------------------- +// Customer sprites: fill layer in front, outline layer behind +void Customer_Show(u8 x, u8 y) +{ + VDP_SetSpritePosition(SPR_CUST_FILL, x, y); + VDP_SetSpritePosition(SPR_CUST_LINE, x, y); +} + +void Customer_Hide() +{ + VDP_HideSprite(SPR_CUST_FILL); + VDP_HideSprite(SPR_CUST_LINE); +} + +//----------------------------------------------------------------------------- +// Steam wisp: alternates two patterns every 16 frames +void Steam_Show(u8 x, u8 y) +{ + VDP_SetSpriteExUniColor(SPR_STEAM, x, y, + (g_Frame & 16) ? PAT_STEAM_A : PAT_STEAM_B, C_CREAM); +} + +void Steam_Hide() +{ + VDP_HideSprite(SPR_STEAM); +} + +//----------------------------------------------------------------------------- +// Status bar: title left, served count right +void DrawStatusBar() +{ + VDP_CommandHMMV(0, STATUS_Y, 256, STATUS_H, PX(C_OUTLINE)); + VDP_CommandWait(); + + Print_SetColor(C_LAMP, C_OUTLINE); + Print_SetPosition(4, 4); + Print_DrawText("DAY "); + Print_DrawInt(g_Save.day); + + // Phase marker sits mid-bar; the clock is the day phase, nothing finer + Print_SetColor(C_CREAM, C_OUTLINE); + Print_SetPosition(96, 4); + switch (g_Phase) + { + case PHASE_MORNING: Print_DrawText("MORNING"); break; + case PHASE_OPEN: Print_DrawText("OPEN"); break; + default: Print_DrawText("CLOSED"); break; + } + + Print_SetColor(C_TEXT, C_OUTLINE); + Print_SetPosition(176, 4); + Print_DrawText("SERVED "); + Print_DrawInt(g_Save.served); +} + +//----------------------------------------------------------------------------- +// Shop scene: placeholder fills standing in for the real background bitmap +void DrawScene() +{ + // Wall, wood paneling, floor + VDP_CommandHMMV(0, SCENE_Y, 256, 60, PX(C_WALL)); // upper wall + VDP_CommandHMMV(0, 76, 256, 44, PX(C_WOOD_M)); // wainscot + VDP_CommandHMMV(0, 76, 256, 1, PX(C_WOOD_D)); // panel rail + VDP_CommandHMMV(0, 120, 256, 28, PX(C_WOOD_D)); // floor + VDP_CommandHMMV(0, 120, 256, 1, PX(C_OUTLINE)); // floor edge + + // Window (left): frame + dusk sky gradient in slots 8-11 + VDP_CommandHMMV(16, 24, 72, 48, PX(C_WOOD_D)); // frame + VDP_CommandHMMV(20, 28, 64, 10, PX(C_SKY1)); + VDP_CommandHMMV(20, 38, 64, 10, PX(C_SKY2)); + VDP_CommandHMMV(20, 48, 64, 10, PX(C_SKY3)); + VDP_CommandHMMV(20, 58, 64, 10, PX(C_SKY4)); + VDP_CommandHMMV(50, 28, 2, 40, PX(C_WOOD_D)); // mullion + VDP_CommandHMMV(20, 46, 64, 2, PX(C_WOOD_D)); // transom + + // Door (right) + VDP_CommandHMMV(208, 32, 36, 88, PX(C_WOOD_D)); // frame + VDP_CommandHMMV(212, 36, 28, 82, PX(C_WOOD_M)); // door + VDP_CommandHMMV(218, 40, 16, 18, PX(C_SKY2)); // door pane + VDP_CommandHMMV(236, 76, 2, 3, PX(C_LAMP)); // knob + + // Hanging lamp above the counter + VDP_CommandHMMV(139, SCENE_Y, 2, 12, PX(C_OUTLINE)); // cord + VDP_CommandHMMV(132, 28, 16, 8, PX(C_LAMP)); // shade + + // Counter: top slab + front panel + VDP_CommandHMMV(8, COUNTER_TOP_Y, 192, 8, PX(C_WOOD_L)); + VDP_CommandHMMV(8, COUNTER_TOP_Y + 8, 192, 34, PX(C_WOOD_D)); + VDP_CommandHMMV(8, COUNTER_TOP_Y, 192, 1, PX(C_CREAM)); // top highlight + + // Siphon rig on the counter (left, kept below the window): stand, globe, flask + VDP_CommandHMMV(SIPHON_X + 5, 74, 2, 24, PX(C_OUTLINE)); // stand pole + VDP_CommandHMMV(SIPHON_X - 6, 74, 24, 2, PX(C_OUTLINE)); // top arm + VDP_CommandHMMV(SIPHON_X - 6, 76, 12, 8, PX(C_CREAM)); // upper globe + VDP_CommandHMMV(SIPHON_X - 7, 86, 14, 10, PX(C_CREAM)); // lower flask + VDP_CommandWait(); +} + +//----------------------------------------------------------------------------- +// Coffee level in the lower flask, 0-8 px +void DrawFlask(u8 level) +{ + VDP_CommandHMMV(SIPHON_X - 6, 87, 12, 8, PX(C_CREAM)); + if (level > 0) + { + if (level > 8) + level = 8; + VDP_CommandHMMV(SIPHON_X - 6, (u16)(95 - level), 12, level, PX(C_COFFEE)); + } + VDP_CommandWait(); +} + +//----------------------------------------------------------------------------- +// Burner flame under the flask (on/off) +void DrawFlame(bool on) +{ + VDP_CommandHMMV(SIPHON_X - 3, 96, 6, 2, on ? PX(C_SKY4) : PX(C_WOOD_M)); + VDP_CommandWait(); +} + +//----------------------------------------------------------------------------- +// Cup on the counter in front of the customer +void DrawCup(bool visible) +{ + if (visible) + { + VDP_CommandHMMV(CUP_X, 91, 10, 7, PX(C_CREAM)); + VDP_CommandHMMV(CUP_X + 1, 91, 8, 2, PX(C_COFFEE)); + } + else // restore the wainscot the cup stands against, not the counter top + VDP_CommandHMMV(CUP_X, 91, 10, 7, PX(C_WOOD_M)); + VDP_CommandWait(); +} + +//----------------------------------------------------------------------------- +// Dialogue window: dark panel with a portrait placeholder box on the left +void Dlg_Clear() +{ + VDP_CommandHMMV(0, DLG_Y, 256, DLG_H, PX(C_WOOD_D)); // border + VDP_CommandHMMV(2, DLG_Y + 2, 252, DLG_H - 4, PX(C_OUTLINE)); // panel + VDP_CommandHMMV(6, 154, 52, 52, PX(C_WOOD_M)); // portrait frame + VDP_CommandHMMV(8, 156, 48, 48, PX(C_WOOD_D)); // portrait (48x48, stage 3+) + VDP_CommandWait(); +} + +void Dlg_Line(u8 line, const c8* text) +{ + Print_SetColor(C_TEXT, C_OUTLINE); + Print_SetPosition(DLG_TEXT_X, DLG_LINE_Y(line)); + Print_DrawText(text); +} + +// Prompt line in warm accent color +void Dlg_Prompt(const c8* text) +{ + Print_SetColor(C_LAMP, C_OUTLINE); + Print_SetPosition(DLG_TEXT_X, DLG_LINE_Y(3)); + Print_DrawText(text); +} + +//----------------------------------------------------------------------------- +// Brew gauge: zones repainted whole each frame, then the needle on top +void DrawGauge() +{ + // Full band first: the needle sticks out 2px above and below the zones + VDP_CommandHMMV(GAUGE_X, GAUGE_Y - 2, GAUGE_W + 2, GAUGE_H + 4, PX(C_OUTLINE)); + VDP_CommandHMMV(GAUGE_X, GAUGE_Y, GAUGE_W, GAUGE_H, PX(C_WOOD_D)); + VDP_CommandHMMV(GAUGE_X + GAUGE_CENTER - 10, GAUGE_Y, 20, GAUGE_H, PX(C_LAMP)); + VDP_CommandHMMV(GAUGE_X + GAUGE_CENTER - 3, GAUGE_Y, 6, GAUGE_H, PX(C_SKY4)); + VDP_CommandHMMV((u16)(GAUGE_X + g_Needle), GAUGE_Y - 2, 2, GAUGE_H + 4, PX(C_TEXT)); + VDP_CommandWait(); +} + +void EraseGauge() +{ + VDP_CommandHMMV(GAUGE_X, GAUGE_Y - 2, GAUGE_W + 2, GAUGE_H + 4, PX(C_OUTLINE)); + VDP_CommandWait(); +} + +//============================================================================= +// PHASE TRANSITIONS +//============================================================================= + +//----------------------------------------------------------------------------- +// Morning: day title card. No prep decisions yet — those arrive with the menu +// system, if it survives the cut (PLAN.md §6). +void EnterMorning() +{ + g_Phase = PHASE_MORNING; + g_DayServed = 0; + g_DayQuality = 0; + Customer_Hide(); + Steam_Hide(); + DrawStatusBar(); + DrawScene(); + Dlg_Clear(); + Dlg_Line(0, "Morning. The shutters go"); + Dlg_Line(1, "up, the kettle goes on."); + Dlg_Prompt("[SPACE] open the shop"); +} + +//----------------------------------------------------------------------------- +// Evening: tally, one journal line reflecting how the day went, then save. +void EnterEvening() +{ + g_Phase = PHASE_EVENING; + Customer_Hide(); + Steam_Hide(); + DrawStatusBar(); + Dlg_Clear(); + + Dlg_Line(0, "Closing. Cups today: "); + Print_DrawInt(g_DayServed); + + // Average quality drives the journal line — no score, just a mood + u8 avg = g_DayServed ? (u8)(g_DayQuality / g_DayServed) : 0; + if (avg >= 2) + Dlg_Line(1, "A good day at the siphon."); + else if (avg >= 1) + Dlg_Line(1, "An ordinary, decent day."); + else + Dlg_Line(1, "Tomorrow, a steadier hand."); + + if (Save_Write()) + Dlg_Prompt("[SPACE] sleep (saved)"); + else if (!g_HasDisk) + Dlg_Prompt("[SPACE] sleep (no disk)"); + else + Dlg_Prompt("[SPACE] sleep (SAVE FAILED)"); +} + +//----------------------------------------------------------------------------- +void AdvanceDay() +{ + g_Save.day++; + EnterMorning(); +} + +//============================================================================= +// STATE TRANSITIONS +//============================================================================= + +void EnterWaitState() +{ + g_State = STATE_WAIT; + g_Timer = 120 + Math_GetRandomMax8(180); + Dlg_Clear(); + Dlg_Line(0, g_Ambient[g_Ambience]); + g_Ambience = (g_Ambience + 1) & 3; +} + +void EnterArrive() +{ + g_State = STATE_ARRIVE; + g_CustX = DOOR_X; + VDP_SetSpriteExUniColor(SPR_CUST_LINE, DOOR_X, CUST_Y, PAT_CUST_LINE, C_OUTLINE); + VDP_SetSpriteExUniColor(SPR_CUST_FILL, DOOR_X, CUST_Y, PAT_CUST_FILL, C_COAT); + Dlg_Clear(); + Dlg_Line(0, "The door chime rings."); +} + +void EnterOrder() +{ + g_State = STATE_ORDER; + Dlg_Clear(); + Dlg_Line(0, "\"A coffee, please.\""); + Dlg_Prompt("[SPACE] brew"); +} + +void EnterBrew() +{ + g_State = STATE_BREW; + g_Needle = 0; + g_NeedleDir = 3; + g_Timer = 0; + Dlg_Clear(); + Dlg_Line(0, "Release when the needle"); + Dlg_Line(1, "crosses the amber mark!"); + DrawFlame(TRUE); +} + +void EnterServe() +{ + g_State = STATE_SERVE; + g_Timer = 150; + EraseGauge(); + DrawFlame(FALSE); + DrawFlask(0); + DrawCup(TRUE); + Dlg_Clear(); + switch (g_Quality) + { + case 3: + Dlg_Line(0, "\"Ah... perfect.\""); + Dlg_Line(1, "The evening feels warmer."); + break; + case 2: + Dlg_Line(0, "\"Mm. Nice and rich.\""); + break; + case 1: + Dlg_Line(0, "\"Thank you.\""); + break; + default: + Dlg_Line(0, g_Overdone ? "\"Oh... quite strong.\"" + : "\"Hm... a little thin.\""); + break; + } +} + +void EnterDrink() +{ + g_State = STATE_DRINK; + g_Timer = 180; + Dlg_Clear(); + Dlg_Line(0, "A quiet moment passes."); +} + +void EnterLeave() +{ + g_State = STATE_LEAVE; + Steam_Hide(); + DrawCup(FALSE); + Dlg_Clear(); + Dlg_Line(0, "\"See you tomorrow.\""); +} + +//============================================================================= +// STATE UPDATES (one call per frame) +//============================================================================= + +void UpdateWait() +{ + if (--g_Timer == 0) + EnterArrive(); +} + +void UpdateArrive() +{ + g_CustX--; + // Small walk bob + Customer_Show(g_CustX, (g_CustX & 8) ? CUST_Y : CUST_Y - 1); + if (g_CustX <= SEAT_X) + { + Customer_Show(SEAT_X, CUST_Y); + EnterOrder(); + } +} + +void UpdateOrder() +{ + if (g_SpaceHit) + EnterBrew(); +} + +void UpdateBrew() +{ + // Needle sweeps back and forth + g_Needle += g_NeedleDir; + if (g_Needle <= 0) { g_Needle = 0; g_NeedleDir = 3; } + if (g_Needle >= GAUGE_W - 2){ g_Needle = GAUGE_W - 2; g_NeedleDir = -3; } + DrawGauge(); + + // Coffee slowly rises while the needle sweeps + g_Timer++; + if ((g_Timer & 15) == 0) + DrawFlask((u8)(g_Timer >> 4)); + Steam_Show(SIPHON_X - 8, 58); + + if (g_SpaceHit) + { + i16 dist = g_Needle + 1 - GAUGE_CENTER; + g_Overdone = (dist > 0); + if (dist < 0) + dist = -dist; + if (dist <= 3) g_Quality = 3; + else if (dist <= 10) g_Quality = 2; + else if (dist <= 24) g_Quality = 1; + else g_Quality = 0; + g_DayQuality += g_Quality; + EnterServe(); + } +} + +void UpdateServe() +{ + Steam_Show(CUP_X - 4, 78); + if (--g_Timer == 0) + EnterDrink(); +} + +void UpdateDrink() +{ + Steam_Show(CUP_X - 4, 78); + if (--g_Timer == 0) + EnterLeave(); +} + +void UpdateLeave() +{ + g_CustX++; + Customer_Show(g_CustX, (g_CustX & 8) ? CUST_Y : CUST_Y - 1); + if (g_CustX >= DOOR_X) + { + Customer_Hide(); + g_Save.served++; + g_DayServed++; + DrawStatusBar(); + + // Customers are the clock: the day ends when the last one leaves + if (g_DayServed >= CUSTOMERS_PER_DAY) + EnterEvening(); + else + EnterWaitState(); + } +} + +//============================================================================= +// MAIN LOOP +//============================================================================= + +//----------------------------------------------------------------------------- +// Program entry point +void main() +{ + VDP_SetMode(VDP_MODE_SCREEN5); + VDP_SetColor(C_OUTLINE); + VDP_EnableVBlank(TRUE); + VDP_ClearVRAM(); + + // Palette (skip entry 0: transparent) + for (u8 i = 1; i < 16; i++) + VDP_SetPaletteEntry(i, g_Palette[i]); + + // Sprites: 16x16, tables above page 1 (page 1 becomes the asset warehouse later) + VDP_EnableSprite(TRUE); + VDP_SetSpritePatternTable(0x17000); + VDP_SetSpriteAttributeTable(0x17A00); + VDP_SetSpriteFlag(VDP_SPRITE_SIZE_16); + VDP_LoadSpritePattern(g_SprCustFill, PAT_CUST_FILL, 4); + VDP_LoadSpritePattern(g_SprCustLine, PAT_CUST_LINE, 4); + VDP_LoadSpritePattern(g_SprSteamA, PAT_STEAM_A, 4); + VDP_LoadSpritePattern(g_SprSteamB, PAT_STEAM_B, 4); + VDP_HideAllSprites(); + VDP_DisableSpritesFrom(3); + + Print_SetBitmapFont(g_Font_MGL_Sample6); + + // Explicit game-state init: don't rely on C initializers, the mapper + // crt0's data-segment setup has proven unreliable here. + g_Served = 0; + g_Ambience = 0; + g_Frame = 0; + g_SpaceHit = FALSE; + g_SpacePrev = 1; // treat SPACE as held at boot: no phantom first edge + g_DiskErr = 0; + g_SaveSector = 14; + + // Save disk. A missing or blank disk is not an error the player has to + // deal with — it just means today is day one and nothing will persist. + Save_Reset(); + g_HasDisk = Disk_Probe(); + Save_Load(); + + EnterMorning(); + + while (1) + { + Halt(); // Wait V-Blank + g_Frame++; + + // Sample input immediately after Halt(): the BIOS ISR drives the PPI + // keyboard row selection too, so a read elsewhere in the frame can + // race with it and produce phantom edges. + // Atomic keyboard read: Keyboard_Read() is a row-select 'out' followed + // by an 'in'; if the BIOS ISR lands between them, its own matrix scan + // moves the row selection and the read returns garbage (phantom keys). + DisableInterrupt(); + u8 raw = Keyboard_Read(KEY_ROW(KEY_SPACE)); + EnableInterrupt(); + u8 space = (raw & KEY_FLAG(KEY_SPACE)) == 0; + g_SpaceHit = space && !g_SpacePrev; + g_SpacePrev = space; + + // Morning and evening are single-key card screens; only the open + // phase runs the serve loop's state machine. + if (g_Phase == PHASE_MORNING) + { + if (g_SpaceHit) + { + g_Phase = PHASE_OPEN; + DrawStatusBar(); + EnterWaitState(); + } + continue; + } + if (g_Phase == PHASE_EVENING) + { + if (g_SpaceHit) + AdvanceDay(); + continue; + } + + switch (g_State) + { + case STATE_WAIT: UpdateWait(); break; + case STATE_ARRIVE: UpdateArrive(); break; + case STATE_ORDER: UpdateOrder(); break; + case STATE_BREW: UpdateBrew(); break; + case STATE_SERVE: UpdateServe(); break; + case STATE_DRINK: UpdateDrink(); break; + case STATE_LEAVE: UpdateLeave(); break; + } + } +} diff --git a/projects/kissaten_yugure/msxgl_config.h b/projects/kissaten_yugure/msxgl_config.h new file mode 100644 index 0000000..9f031a1 --- /dev/null +++ b/projects/kissaten_yugure/msxgl_config.h @@ -0,0 +1,641 @@ +// ____________________________ +// ██▀▀█▀▀██▀▀▀▀▀▀▀█▀▀█ │ ▄▄▄ ▄▄ +// ██ ▀ █▄ ▀██▄ ▀ ▄█ ▄▀▀ █ │ ▀█▄ ▄▀██ ▄█▄█ ██▀▄ ██ ▄███ +// █ █ █ ▀▀ ▄█ █ █ ▀▄█ █▄ │ ▄▄█▀ ▀▄██ ██ █ ██▀ ▀█▄ ▀█▄▄ +// ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀────────┘ ▀▀ +// Library configuration +//───────────────────────────────────────────────────────────────────────────── +#pragma once + +//----------------------------------------------------------------------------- +// BUILD +//----------------------------------------------------------------------------- + +// Target +// - TARGET_ROM_8K 8 KB ROM in page 1 (4000h ~ 5FFFh) +// - TARGET_ROM_8K_P2 8 KB ROM in page 2 (8000h ~ 9FFFh) +// - TARGET_ROM_16K 16 KB ROM in page 1 (4000h ~ 7FFFh) +// - TARGET_ROM_16K_P2 16 KB ROM in page 2 (8000h ~ BFFFh) +// - TARGET_ROM_32K 32 KB ROM in page 1&2 (4000h ~ BFFFh) +// - TARGET_ROM_48K 48 KB ROM in page 0-2 (0000h ~ BFFFh) +// - TARGET_ROM_48K_ISR 48 KB ROM in page 0-2 (0000h ~ BFFFh) with ISR replacement +// - TARGET_ROM_64K 64 KB ROM in page 0-3 (0000h ~ FFFFh) +// - TARGET_ROM_64K_ISR 64 KB ROM in page 0-3 (0000h ~ FFFFh) with ISR replacement +// - TARGET_ROM_ASCII8 ASCII-8: 8KB segments for a total of 64 KB to 2 MB +// - TARGET_ROM_ASCII16 ASCII-16: 16KB segments for a total of 64 KB to 4 MB +// - TARGET_ROM_KONAMI Konami MegaROM (aka Konami4): 8 KB segments for a total of 64 KB to 2 MB +// - TARGET_ROM_KONAMI_SCC Konami MegaROM SCC (aka Konami5): 8 KB segments for a total of 64 KB to 2 MB +// - TARGET_DOS1 MSX-DOS 1 program (starting at 0100h) +// - TARGET_DOS2 MSX-DOS 2 program (starting at 0100h) +// - TARGET_DOS0 Direct program boot from disk (starting at 0100h) +// - TARGET_BIN BASIC binary program (starting at 8000h) +// - TARGET_BIN_USR BASIC USR binary driver (starting at C000h) +// TARGET is defined by the build tool + +// MSX version +// - MSX_1 ........................ MSX1 +// - MSX_2 ........................ MSX2 +// - MSX_12 ....................... MSX1 and 2 (support each) +// - MSX_2K ....................... Korean MSX2 (SC9 support) +// - MSX_2P ....................... MSX2+ +// - MSX_22P ...................... MSX2 and 2+ (support each) +// - MSX_122P ..................... MSX1, 2 and 2+ (support each) +// - MSX_0 ........................ MSX0 (MSX2+) +// - MSX_TR ....................... MSX turbo R +// - MSX_3 ........................ MSX3 +// MSX_VERSION is defined by the build tool + +//----------------------------------------------------------------------------- +// BIOS MODULE +//----------------------------------------------------------------------------- + +// Default slot BIOS access +// - BIOS_CALL_DIRECT ............. Use direct access to Bios routines (ROM slot must be selected in corresponding page) +// - BIOS_CALL_INTERSLOT .......... Use inter-slot access to Bios routines (through CALSLT routine) +#define BIOS_CALL_MAINROM BIOS_CALL_DIRECT +#define BIOS_CALL_SUBROM BIOS_CALL_INTERSLOT +#define BIOS_CALL_DISKROM BIOS_CALL_INTERSLOT + +// MAIN-Bios module setting +#define BIOS_USE_MAINROM TRUE // Allow use of Main-ROM routines +#define BIOS_USE_VDP TRUE // Give access to Main-ROM routines related to VDP +#define BIOS_USE_PSG TRUE // Give access to Main-ROM routines related to PSG +#define BIOS_USE_SUBROM TRUE // Allow use of Sub-ROM routines (MSX2/2+/turbo R) +#define BIOS_USE_DISKROM TRUE // Allow use of Disk-ROM routines + +//----------------------------------------------------------------------------- +// VDP MODULE +//----------------------------------------------------------------------------- + +// VRAM addressing unit +// - VDP_VRAM_ADDR_14 ............. Use 14-bits 16K VRAM addressing for MSX1 (u16) +// - VDP_VRAM_ADDR_17 ............. Use 17-bits 128K VRAM addressing for MSX2/2+/turbo R (u32) +#define VDP_VRAM_ADDR VDP_VRAM_ADDR_17 + +// VDP X/Y units +// - VDP_UNIT_U8 .................. X and Y use 8-bits values +// - VDP_UNIT_X16 ................. X use 16-bits and Y use 8-bits values +// - VDP_UNIT_Y16 ................. X use 8-bits and Y use 16-bits values +// - VDP_UNIT_U16 ................. X and Y use 16-bits values +#define VDP_UNIT VDP_UNIT_X16 + +// VDP screen modes (additionnal limitations come from the selected MSX_VERSION) +#define VDP_USE_MODE_T1 TRUE // MSX1 Screen 0 Width 40 +#define VDP_USE_MODE_G1 TRUE // MSX1 Screen 1 +#define VDP_USE_MODE_G2 TRUE // MSX1 Screen 2 +#define VDP_USE_MODE_MC TRUE // MSX1 Screen 3 +#define VDP_USE_MODE_T2 TRUE // MSX2 Screen 0 Width 80 +#define VDP_USE_MODE_G3 TRUE // MSX2 Screen 4 +#define VDP_USE_MODE_G4 TRUE // MSX2 Screen 5 +#define VDP_USE_MODE_G5 TRUE // MSX2 Screen 6 +#define VDP_USE_MODE_G6 TRUE // MSX2 Screen 7 +#define VDP_USE_MODE_G7 TRUE // MSX2/2+ Screen 8, 10, 11 & 12 + +#define VDP_USE_VRAM16K TRUE // Use 16K VRAM access functions on MSX2 +#define VDP_USE_SPRITE TRUE // Use sprite handling functions +#define VDP_USE_COMMAND TRUE // Use VDP commands wrapper functions +#define VDP_USE_CUSTOM_CMD FALSE // Use custom VDP commands through data buffer +#define VDP_AUTO_INIT TRUE // Call VDP_Initialize() at the first call to VDP_SetMode() +#define VDP_USE_UNDOCUMENTED TRUE // Allow the use of undocumented screen mode (WIP) +#define VDP_USE_VALIDATOR TRUE // Handle some option specific for each VDP mode (highly recommended) +#define VDP_USE_DEFAULT_PALETTE FALSE // Add data for default MSX2 palette +#define VDP_USE_MSX1_PALETTE FALSE // Add data for default MSX1 palette +#define VDP_USE_DEFAULT_SETTINGS TRUE // Auto-initialization of common VDP feature +#define VDP_USE_16X16_SPRITE TRUE // Use 16x16 sprites mode +#define VDP_USE_RESTORE_S0 TRUE // Do restore of status register pointer to S#0 (needed onlt for default BIOS ISR) +#define VDP_USE_PALETTE16 FALSE // Use 16 entries palette (use only 15 entries otherwise) + +// ISR protection while modifiying VDP registers +// - VDP_ISR_SAFE_NONE ............ No ISR protection (for program not using VDP interruption) +// - VDP_ISR_SAFE_DEFAULT ......... Protect only VDP register pair writing (default behavior; ISR can read/write registers but VRAM ones) +// - VDP_ISR_SAFE_ALL ............. Protect all VDP writing process +#define VDP_ISR_SAFE_MODE VDP_ISR_SAFE_DEFAULT + +// Initial screen mode setting +// - VDP_INIT_OFF ................. Force option to be disable +// - VDP_INIT_ON .................. Force option to be enable +// - VDP_INIT_AUTO ................ Determining the best value for the context +// - VDP_INIT_DEFAULT ............. Keep default value +#define VDP_INIT_50HZ VDP_INIT_DEFAULT + +//----------------------------------------------------------------------------- +// V9990 MODULE +//----------------------------------------------------------------------------- + +// V9990 screen modes support +#define V9_USE_MODE_P1 TRUE // Tile mode 0 256x212 +#define V9_USE_MODE_P2 TRUE // Tile mode 1 512x212 +#define V9_USE_MODE_B0 TRUE // Bitmap mode 1 192x240 (Undocumented v9990 mode) +#define V9_USE_MODE_B1 TRUE // Bitmap mode 1 256x212 +#define V9_USE_MODE_B2 TRUE // Bitmap mode 2 384x240 +#define V9_USE_MODE_B3 TRUE // Bitmap mode 3 512x212 +#define V9_USE_MODE_B4 TRUE // Bitmap mode 4 768x240 +#define V9_USE_MODE_B5 TRUE // Bitmap mode 5 640x400 (VGA) +#define V9_USE_MODE_B6 TRUE // Bitmap mode 6 640x480 (VGA) +#define V9_USE_MODE_B7 TRUE // Bitmap mode 7 1024x212 (Undocumented v9990 mode) + +#define V9_INT_PROTECT TRUE // VRAM access protection mode against interruption +// Palette input data format +// - V9_PALETTE_YSGBR_16 .......... 16 bits RGB + Ys [Ys|G|G|G|G|G|R|R] [R|R|R|B|B|B|B|B] +// - V9_PALETTE_GBR_16 ............ 16 bits RGB [0|G|G|G|G|G|R|R] [R|R|R|B|B|B|B|B] +// - V9_PALETTE_RGB_24 ............ 24 bits RGB [0|0|0|R|R|R|R|R] [0|0|0|G|G|G|G|G] [0|0|0|B|B|B|B|B] +#define V9_PALETTE_MODE V9_PALETTE_RGB_24 + +//----------------------------------------------------------------------------- +// INPUT MODULE +//----------------------------------------------------------------------------- + +// Input module setting +#define INPUT_USE_JOYSTICK TRUE // Add functions to handle joystick using I/O port +#define INPUT_USE_KEYBOARD TRUE // Add functions to handle keyboard using I/O port +#define INPUT_USE_MOUSE TRUE // Add support for Mouse handling functions +#define INPUT_USE_DETECT TRUE // Add feature to detect device plugged in General purpose ports +#define INPUT_USE_ISR_PROTECTION TRUE // Disable interruptions while access PSG registers (needed if you use BIOS or access PSG in your own ISR) +#define INPUT_JOY_UPDATE FALSE // Add function to update all joystick states at once +#define INPUT_HOLD_SIGNAL FALSE // Determines whether functions that modify signals should keep the state of those they don't need to modify (which slows functions down a bit) +// Key update handler +// Keep FALSE: the buffered mode stores state in the BIOS NEWKEY/OLDKEY work +// area, which C-BIOS does not maintain in the standard matrix format (phantom +// key edges). Direct reads are safe if sampled right after Halt() — see main(). +#define INPUT_KB_UPDATE FALSE // Add function to update all keyboard rows at once +#define INPUT_KB_UPDATE_MIN 0 // First row to update +#define INPUT_KB_UPDATE_MAX 8 // Last row to update (10 for numerical-pad, 8 otherwise) + +//----------------------------------------------------------------------------- +// PADDLE MODULE +//----------------------------------------------------------------------------- + +// Paddle module setting +#define PADDLE_USE_CALIB TRUE // Add functions paddle calibration feature + +//----------------------------------------------------------------------------- +// MEMORY MODULE +//----------------------------------------------------------------------------- + +#define MEM_USE_VALIDATOR FALSE // Activate validator to handle invalide input value +#define MEM_USE_FASTCOPY FALSE // Add support for fast-copy function (using unrolled-LDI loop) +#define MEM_USE_FASTSET FALSE // Add support for fast-set function (using unrolled-LDI loop) +#define MEM_USE_DYNAMIC FALSE // Add support for malloc style dynamic allocator +#define MEM_USE_BUILTIN TRUE // Use SDCC built-in memcpy and memset function instead of MSXgl ones + +//----------------------------------------------------------------------------- +// MSX-DOS MODULE +//----------------------------------------------------------------------------- + +#define DOS_USE_FCB TRUE // Add support for file managment features through FCB structure +#define DOS_USE_HANDLE TRUE // Add support for file managment features through file handle +#define DOS_USE_UTILITIES TRUE // Add support for file managment features through filename +#define DOS_USE_VALIDATOR TRUE // Add support for last error backup and return value validation +#define DOS_USE_ERROR_HANDLER TRUE // Add support for MSX-DOS 1 error handler callback +#define DOS_USE_BIOSCALL TRUE // Add support for call to BIOS routines + +//----------------------------------------------------------------------------- +// CLOCK MODULE +//----------------------------------------------------------------------------- + +#define RTC_USE_CLOCK TRUE // Add functions to handle date and time +#define RTC_USE_CLOCK_EXTRA TRUE // Add extra date and time functions that require additional data +#define RTC_USE_SAVEDATA TRUE // Add functions to read/write into the CMOS +#define RTC_USE_SAVESIGNED TRUE // Add signature handling to validate CMOS I/O + +//----------------------------------------------------------------------------- +// DRAW MODULE +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// PRINT MODULE +//----------------------------------------------------------------------------- + +// Print module setting +#define PRINT_USE_TEXT TRUE // Allow use of Text font (T1-T2, G1-G3) +#define PRINT_USE_BITMAP TRUE // Allow use of Bitmap font (G4-G7) +#define PRINT_USE_VRAM TRUE // Allow use of VRAM stored font (G4-G7) +#define PRINT_USE_SPRITE TRUE // Allow use of Sprite font (G3-G7) +#define PRINT_USE_FX_SHADOW TRUE // [Bitmap] Allow use of text shadow +#define PRINT_USE_FX_OUTLINE TRUE // [Bitmap] Allow use of text outline +#define PRINT_USE_2_PASS_FX FALSE // [Bitmap] Allow use 2-pass FX render to prevent character overlap +#define PRINT_USE_GRAPH TRUE // Allow use of character lines and boxes +#define PRINT_USE_VALIDATOR TRUE // Add validator character code +#define PRINT_USE_UNIT FALSE // Display integer type (h: hexadecimal, b: binary) +#define PRINT_USE_FORMAT TRUE // Add printf type function +#define PRINT_USE_32B TRUE // Allow to print 32-bits integers +#define PRINT_USE_MULTIFONT FALSE // Use multiple fonts (each one with its own data structure) +#define PRINT_SKIP_SPACE TRUE // Skill space character +#define PRINT_COLOR_NUM 12 // 1 color per line +// Character width +// - PRINT_WIDTH_1 (text mode) +// - PRINT_WIDTH_6 +// - PRINT_WIDTH_8 +// - PRINT_WIDTH_X (variable) +#define PRINT_WIDTH PRINT_WIDTH_X +// Character height +// - PRINT_HEIGHT_1 (text mode) +// - PRINT_HEIGHT_8 +// - PRINT_HEIGHT_X (variable) +#define PRINT_HEIGHT PRINT_HEIGHT_X + +//----------------------------------------------------------------------------- +// SPRITE FX MODULE +//----------------------------------------------------------------------------- + +// Supported sprite size +#define SPRITEFX_USE_8x8 TRUE // Use 8x8 pixels effects +#define SPRITEFX_USE_16x16 TRUE // Use 16x16 pixels effects + +// Supported effect +#define SPRITEFX_USE_CROP TRUE // Use cropping effect +#define SPRITEFX_USE_FLIP TRUE // Use flipping effect +#define SPRITEFX_USE_MASK TRUE // Use masking effect +#define SPRITEFX_USE_ROTATE TRUE // Use rotating effect + +//----------------------------------------------------------------------------- +// GAME MAIN MODULE +//----------------------------------------------------------------------------- + +// Game state setting +#define GAME_USE_STATE TRUE // Add state machine features +#define GAME_USE_VSYNC TRUE // Add vertical synchronization features +#define GAME_USE_LOOP TRUE // Add game main loop with call to v-synch and state +#define GAME_USE_SYNC_50HZ TRUE // Force 50Hz synchronization on 60Hz machine + +//----------------------------------------------------------------------------- +// GAME PAWN MODULE +//----------------------------------------------------------------------------- + +// Pawn setting +#define PAWN_ID_PER_LAYER FALSE // Set sprite ID for each layer (otherwise set per pawn) +#define PAWN_USE_RT_LOAD TRUE // Load sprite pattern data on the fly (real-time) +#define PAWN_USE_SPRT_FX TRUE // Allow sprite effects (crop, flip, mask, rotate) +#define PAWN_SPRITE_SIZE 16 // Sprite size mode (8 for 8x8 pixel mode, or 16 for 16x16) +#define PAWN_BLEND_OFFSET 12 // Sprite pattern offset for blending mode +#define PAWN_USE_PHYSICS TRUE // Add physics and collision features +// Pawn coordinate unit +// - PAWN_UNIT_SCREEN Default screen (pixel) unit (8-bit unsigned int) +// - PAWN_UNIT_QMN(n) Fixed-point (Qm.n) unit (16-bit signed int) +#define PAWN_UNIT PAWN_UNIT_SCREEN +// Pawn's bound (can be fixed for all pawn, or setable for each one) +#define PAWN_BOUND_X PAWN_BOUND_CUSTOM +#define PAWN_BOUND_Y PAWN_BOUND_CUSTOM +// Collision position options for each pawn's side +// - PAWN_COL_0 +// - PAWN_COL_25 +// - PAWN_COL_50 +// - PAWN_COL_75 +// - PAWN_COL_100 +#define PAWN_COL_DOWN (PAWN_COL_25|PAWN_COL_75) +#define PAWN_COL_UP PAWN_COL_50 +#define PAWN_COL_RIGHT PAWN_COL_50 +#define PAWN_COL_LEFT PAWN_COL_50 +// Options to determine which border collide or trigger events +// - PAWN_BORDER_NONE +// - PAWN_BORDER_DOWN +// - PAWN_BORDER_UP +// - PAWN_BORDER_RIGHT +// - PAWN_BORDER_LEFT +#define PAWN_BORDER_EVENT (PAWN_BORDER_UP|PAWN_BORDER_DOWN|PAWN_BORDER_LEFT|PAWN_BORDER_RIGHT) +#define PAWN_BORDER_BLOCK (PAWN_BORDER_UP|PAWN_BORDER_DOWN|PAWN_BORDER_LEFT|PAWN_BORDER_RIGHT) +// Top/bottom border position (in pixel) +#define PAWN_BORDER_MIN_Y 0 // High border Y coordinade +#define PAWN_BORDER_MAX_Y 211 // Low border Y coordinate +#define PAWN_TILEMAP_WIDTH 32 // Width of the tiles map +#define PAWN_TILEMAP_HEIGHT 27 // Height of the tiles map +// Collision tilemap source +// - PAWN_TILEMAP_SRC_AUTO ........ Backward compatibility option +// - PAWN_TILEMAP_SRC_RAM ......... Tilemap located in a buffer in RAM (best for performance) +// - PAWN_TILEMAP_SRC_VRAM ........ Tilemap located in VRAM (slow but don't need additionnal data) +// - PAWN_TILEMAP_SRC_V9 .......... Tilemap located in V9990's VRAM +#define PAWN_TILEMAP_SRC PAWN_TILEMAP_SRC_RAM +// Pawn's sprite mode +// - PAWN_SPT_MODE_AUTO ........... Backward compatibility option +// - PAWN_SPT_MODE_MSX1 ........... Sprite Mode 1 (MSX1 screens) +// - PAWN_SPT_MODE_MSX2 ........... Sprite Mode 2 unicolor (MSX2 screens) +// - PAWN_SPT_MODE_MSX2_MULTI ..... Sprite Mode 2 multi-color (MSX2 screens) +// - PAWN_SPT_MODE_MSX12 .......... Sprite Mode 1 & 2 unicolor (MSX1 & MSX2 screens) +// - PAWN_SPT_MODE_V9_P1 .......... V9990 sprite in P1 mode +// - PAWN_SPT_MODE_V9_P2 .......... V9990 sprite in P2 mode +#define PAWN_SPT_MODE PAWN_SPT_MODE_MSX2 + +//----------------------------------------------------------------------------- +// GAME MENU MODULE +//----------------------------------------------------------------------------- + +#define MENU_USE_DEFAULT_CALLBACK TRUE // Use default input/print callback +#define MENU_SCREEN_WIDTH MENU_VARIABLE // Screen width +#define MENU_FRAME_X 0 // Frame position X +#define MENU_FRAME_Y 6 // Frame position Y +#define MENU_FRAME_WIDTH 32 // Frame width +#define MENU_FRAME_HEIGHT 8 // Frame height +#define MENU_CHAR_CLEAR '\0' // Clear character +#define MENU_CHAR_CURSOR '@' // Cursor character +#define MENU_CHAR_TRUE 'O' // True character +#define MENU_CHAR_FALSE 'X' // False character +#define MENU_CHAR_LEFT '<' // Left edit character +#define MENU_CHAR_RIGHT '>' // Right edit character +#define MENU_TITLE_X 4 // Title position X +#define MENU_TITLE_Y 6 // Title position Y +#define MENU_ITEM_X 6 // Item label X position +#define MENU_ITEM_Y 8 // Item label X position +#define MENU_ITEM_X_GOTO 6 // Goto type item label X position +#define MENU_ITEM_ALIGN MENU_ITEM_ALIGN_LEFT // Item label alignment +#define MENU_ITEM_ALIGN_GOTO MENU_ITEM_ALIGN_LEFT // Goto type item label alignment +#define MENU_VALUE_X 14 // Item value X position +// Type of cursor +// - MENU_CURSOR_MODE_NONE ........ No cursor +// - MENU_CURSOR_MODE_CHAR ........ Character cursor +// - MENU_CURSOR_MODE_SPRT ........ Sprite cursor +#define MENU_CURSOR_MODE MENU_CURSOR_MODE_CHAR +#define MENU_CURSOR_OFFSET (-2) // Cursor X position offset + +//----------------------------------------------------------------------------- +// GAME QUEST VARIABLES MODULE +//----------------------------------------------------------------------------- + +#define BITFIELD_MAX 64 // Maximum number of quest variables (8 variables per byte) + +//----------------------------------------------------------------------------- +// STRING MODULE +//----------------------------------------------------------------------------- + +// Support for integer to ASCII string converter functions +#define STRING_USE_FROM_INT8 TRUE // Signed 8-bits integer to string +#define STRING_USE_FROM_UINT8 TRUE // Unsigned 8-bits integer to string +#define STRING_USE_FROM_INT16 TRUE // Signed 16-bits integer to string +#define STRING_USE_FROM_UINT16 TRUE // Unsigned 16-bits integer to string + +// Support for sprintf style formating function +#define STRING_USE_FORMAT TRUE +#define STRING_USE_INT32 TRUE // Add support for 32-bits integer + +//----------------------------------------------------------------------------- +// SCROLL MODULE +//----------------------------------------------------------------------------- + +// Allow horizontal and/or vertical scrolling +#define SCROLL_HORIZONTAL TRUE // Activate horizontal scrolling +#define SCROLL_VERTICAL TRUE // Activate vertical scrolling +// Source data info +#define SCROLL_SRC_X 64 // Start X coordinate of the source data +#define SCROLL_SRC_Y 0 // Start Y coordinate of the source data +#define SCROLL_SRC_W 128 // Width of the source data +#define SCROLL_SRC_H 24 // Height of the source data +// Destination data info +#define SCROLL_DST_X 0 // Destination x coordinate (in layout table) +#define SCROLL_DST_Y 2 // Destination y coordinate (in layout table) +#define SCROLL_DST_W 32 // Destination width +#define SCROLL_DST_H 20 // Destination height +#define SCROLL_SCREEN_W 32 // Screen width in tile number +// Allow scroll data looping (only for horizontal scrolling) +#define SCROLL_WRAP TRUE +// Use screen position adjust register (allow per-pixel scrolling) [MSX2] +#define SCROLL_ADJUST TRUE // Global ajustement +#define SCROLL_ADJUST_SPLIT TRUE // Destination windows ajustement using screen split +// Use sprite mask (allow smooth per-pixel scrolling) [MSX2] +#define SCROLL_MASK TRUE // Use sprite to mask +#define SCROLL_MASK_ID 0 // First sprite ID to use +#define SCROLL_MASK_COLOR COLOR_BLACK // Must be the same than border color +#define SCROLL_MASK_PATTERN 0 // Sprite pattern to use + +//----------------------------------------------------------------------------- +// TILE +//----------------------------------------------------------------------------- + +#define TILE_WIDTH 8 // Tile width +#define TILE_HEIGHT 8 // Tile height +#define TILE_BPP 4 // Screen bits-per-pixel +#define TILE_SCREEN_WIDTH 256 // Screen width +#define TILE_SCREEN_HEIGHT 212 // Screen height +#define TILE_USE_SKIP TRUE // Skip drawing of a given index +#define TILE_SKIP_INDEX 0 // The index tile to skip + +//----------------------------------------------------------------------------- +// AUDIO +//----------------------------------------------------------------------------- + +// PSG options +// - PSG_INTERNAL ................. Use internal PSG chip (port A0-A2) +// - PSG_EXTERNAL ................. Use external PSG chip (port 10-12) +// - PSG_BOTH ..................... Use both internal and external PSG chips +#define PSG_CHIP PSG_INTERNAL +// - PSG_DIRECT ................... Function set directly the PSG registers +// - PSG_INDIRECT ................. Function set a buffer (Apply() function must be call once a frame) +#define PSG_ACCESS PSG_INDIRECT +#define PSG_USE_NOTES FALSE // Add notes table to convert note to tone +#define PSG_USE_EXTRA TRUE // Add helper functions to handle PSG settings +#define PSG_USE_RESUME TRUE // Add function to allow playback pause and resume + +// MSX-Music options +#define MSXAUDIO_USE_RESUME TRUE // Add function to allow playback pause and resume + +// MSX-Audio options +#define MSXMUSIC_USE_RESUME TRUE // Add function to allow playback pause and resume + +// SCC options +#define SCC_USE_EXTA TRUE // Add helper functions to handle PSG settings +#define SCC_USE_RESUME TRUE // Add function to allow playback pause and resume +// - SCC_SLOT_DIRECT .............. Program on a SCC cartridge +// - SCC_SLOT_FIXED ............... Fixed slot-id (non-expanded second cartridge slot) +// - SCC_SLOT_USER ................ Defined by the user +// - SCC_SLOT_AUTO ................ First auto-detected cartridge +#define SCC_SLOT_MODE SCC_SLOT_AUTO + +// VGM supported chip +#define VGM_USE_PSG TRUE // Allow PSG data parsing and audio output +#define VGM_USE_MSXMUSIC TRUE // Allow MSX-Music data parsing and audio output +#define VGM_USE_MSXAUDIO TRUE // Allow MSX-Audio data parsing and audio output +#define VGM_USE_SCC TRUE // Allow SCC data parsing and audio output + +// PCM-Encoder supported frequency (more than 1 value allowed) +// - PCMENC_NONE, PCMENC_8K, PCMENC_11K, PCMENC_22K and PCMENC_44K +#define PCMENC_FREQ PCMENC_8K | PCMENC_11K | PCMENC_22K | PCMENC_44K + +// PCMPlay +// - PCMPLAY_8K or PCMPLAY_11K +#define PCMPLAY_FREQ PCMPLAY_8K +#define PCMPLAY_USE_RESTORE FALSE + +// PT3 options +#define PT3_SKIP_HEADER TRUE // Don't use PT3 data header (first 100 bytes must be truncated) +#define PT3_AUTOPLAY TRUE // Play music automatically +#define PT3_EXTRA TRUE // Add helper functions + +// ayFX options +// - AYFX_BUFFER_DEFAULT .......... Use PSG module's PSG registers buffer +// - AYFX_BUFFER_PSG2 ............. Use PSG module's 2nd PSG registers buffer (only if PSG_CHIP == PSG_BOTH) +// - AYFX_BUFFER_PT3 .............. Use PT3 module's PSG registers buffer +#define AYFX_BUFFER AYFX_BUFFER_PT3 + +// TriloTracker options +#define TRILO_USE_SFXPLAY TRUE // Add SFX playback through Trilo SCC player (ayFX + SCC format) +#define TRILO_USE_TREMOLO TRUE // Add support for tremolo effect (little bit expensive) +#define TRILO_USE_TAIL FALSE // Add tail to prevent volume to fall to zero + +// LVGM replayer options +#define LVGM_USE_PSG TRUE // Add parser for PSG data +#define LVGM_USE_MSXMUSIC TRUE // Add parser for MSX-Music data +#define LVGM_USE_MSXAUDIO TRUE // Add parser for MSX-Audio data +#define LVGM_USE_SCC TRUE // Add parser for Konami SCC data +#define LVGM_USE_SCCI FALSE // Add parser for Konami SCC+ data +#define LVGM_USE_PSG2 FALSE // Add parser for secondary PSG data +#define LVGM_USE_OPL4 FALSE // Add parser for OPL4 data +#define LVGM_USE_NOTIFY TRUE // Add parser for PSG data + +// WYZ Tracker replayer options +// Channels number +// - WYZ_3CH +// - WYZ_6CH +#define WYZ_CHANNELS WYZ_3CH // Number of supported channels (can be 3 for 1 PSG or 6 for 2 PSG) +#define WYZ_USE_DIRECT_ACCESS FALSE // Send data directly to PSG registers (otherwise, write in a RAM buffer) +#define WYZ_CHAN_BUFFER_SIZE 0x20 // Size of the channel buffer + +// Arkos Tracker options +#define ARKOS_BUFFER_ADDR 0xF000 // Replayer working area address in RAM +#define ARKOS_ISR_PROTECTION FALSE // Prevent interruption during audio update +#define ARKOS_SFX_START_IDX 0 // Do SFX indexes start at 0 or 1? Default is 0 but Arkos Tracker use 1 +#define ARKOS_USE_EVENT FALSE // Support for event callback function (AKG replayer only) + +// NDP player options +#define NDP_USE_INDERCT FALSE // Output music data into RAM buffer (need to use with ayFX for example) + +//----------------------------------------------------------------------------- +// MATH MODULE +//----------------------------------------------------------------------------- + +// Random methods +// - RANDOM_8_NONE ................ No 8-bits random +// - RANDOM_8_REGISTER ............ R Register value (7-bits) +// - RANDOM_8_RACC ................ R Register accumulation (7-bits) +// - RANDOM_8_ION ................. Ion Random +// - RANDOM_8_MEMORY .............. Memory Peek from R +#define RANDOM_8_METHOD RANDOM_8_ION +// - RANDOM_16_NONE ............... No 16-bits random +// - RANDOM_16_LINEAR ............. Linear congruential +// - RANDOM_16_XORSHIFT ........... XOR Shift +// - RANDOM_16_LFSR_LCG ........... Combined LFSR/LCG +#define RANDOM_16_METHOD RANDOM_16_XORSHIFT + +//----------------------------------------------------------------------------- +// COMPRESS +//----------------------------------------------------------------------------- + +// RLEp compression +#define COMPRESS_USE_RLEP TRUE // Use RLEp unpacker +#define COMPRESS_USE_RLEP_DEFAULT TRUE // Data include the default value (0 otherwise) +#define COMPRESS_USE_RLEP_FIXSIZE FALSE // Gize the data size as function input (loop up to terminator otherwise) + +// Pletter compression +#define PLETTER_LENGTHINDATA FALSE // Is length included in data (first 2 bytes) +// Disable interruption mode +// - PLETTER_DI_NONE .............. Don't disable interruption +// - PLETTER_DI_FULL .............. Disable interruption during the whole function +// - PLETTER_DI_LOOP .............. Disable interruption during VRAM write loop +#define PLETTER_DI_MODE PLETTER_DI_LOOP +// VRAM write timing mode +// - PLETTER_WRITE_SAFE ........... Safe VRAM write speed (30 t-states) +// - PLETTER_WRITE_NODISPLAY ...... Safe VRAM write speed when screen display disable (22 t-states) +// - PLETTER_WRITE_MINIMAL ........ Minimal wait beetween write (17 t-states) +// - PLETTER_WRITE_QUICK .......... No wait beetween write (12 t-states) +// - PLETTER_WRITE_AUTO ........... Determine the worst case according to selected screen mode (12~30 t-states) +#define PLETTER_WRITE_MODE PLETTER_WRITE_SAFE + +// BitBuster compression +// VRAM write timing mode +// - BITBUSTER_WRITE_SAFE ......... Safe VRAM write speed (include nop between write) +// - BITBUSTER_WRITE_QUICK ........ No wait beetween write +#define BITBUSTER_WRITE_MODE BITBUSTER_WRITE_SAFE + +// ZX0 compression +// Unpack mode +// - ZX0_MODE_STANDARD ............ Standard routine: 68 bytes only +// - ZX0_MODE_TURBO ............... Turbo routine: 126 bytes, about 21% faster +// - ZX0_MODE_FAST ................ Fast routine: 187 bytes, about 25% faster +// - ZX0_MODE_MEGA ................ Mega routine: 673 bytes, about 28% faster +#define ZX0_MODE ZX0_MODE_STANDARD + +// LZ48 compression +// - LZ48_MODE_STANDARD ........... Standard routine +// - LZ48_MODE_SPEED .............. Version optimized for speed +// - LZ48_MODE_SIZE ............... Version optimized for size +#define LZ48_MODE LZ48_MODE_STANDARD + +// MSXi compressor support +#define MSXi_USE_COMP_NONE TRUE +#define MSXi_USE_COMP_CROP16 TRUE +#define MSXi_USE_COMP_CROP32 TRUE +#define MSXi_USE_COMP_CROP256 TRUE +#define MSXi_USE_COMP_CROPLINE16 TRUE +#define MSXi_USE_COMP_CROPLINE32 TRUE +#define MSXi_USE_COMP_CROPLINE256 TRUE +#define MSXi_USE_COMP_RLE0 TRUE +#define MSXi_USE_COMP_RLE4 TRUE +#define MSXi_USE_COMP_RLE8 TRUE + +//----------------------------------------------------------------------------- +// NINJATAP MODULE +//----------------------------------------------------------------------------- + +// Supported driver(s) +// - NTAP_DRIVER_MSXGL ............ MSXgl custom driver (based on DM-System2 one) +// - NTAP_DRIVER_GIGAMIX .......... Original Gigamix's DM-System2 driver +// - NTAP_DRIVER_SHINOBI .......... Shinobi Tap driver by Danjovic +#define NTAP_DRIVER NTAP_DRIVER_MSXGL | NTAP_DRIVER_GIGAMIX | NTAP_DRIVER_SHINOBI +#define NTAP_USE_PREVIOUS TRUE // Backup previous data to allow push/release detection + +//----------------------------------------------------------------------------- +// PAC MODULE +//----------------------------------------------------------------------------- + +#define PAC_USE_SIGNATURE TRUE // Handle application signature to validate saved data +#define PAC_USE_VALIDATOR TRUE // Add code to validate input parameters +#define PAC_DEVICE_MAX 4 // Maximum number of supported PAC devices +// SRAM access method +// - PAC_ACCESS_DIRECT ............ Direct access to SRAM (must be selected in page 1) +// - PAC_ACCESS_BIOS .............. Access through BIOS routines +// - PAC_ACCESS_SWITCH_BIOS ....... Access through BIOS routines with BIOS switched in +// - PAC_ACCESS_SYSTEM ............ Access through MSXgl routine (no need BIOS) +#define PAC_ACCESS PAC_ACCESS_BIOS + +//----------------------------------------------------------------------------- +// QR CODE MODULE +//----------------------------------------------------------------------------- + +#define QRCODE_VERSION_MIN 1 // The minimum version number supported in the QR Code Model 2 standard +#define QRCODE_VERSION_MAX 20 // The maximum version number supported in the QR Code Model 2 standard +#define QRCODE_VERSION_CUSTOM FALSE // TRUE: Allow to define version using . FALSE: Use hardcoded min/max version +#define QRCODE_USE_BYTE_ONLY TRUE // TRUE: Allow only BYTE mode. FALSE allow all mode including NUMERIC, ALPHANUMERIC, KANJI and ECI. +#define QRCODE_USE_EXTRA FALSE // TRUE: Add extra function to generate custom data segments. +#define QRCODE_BOOST_ECL FALSE // If boostEcl is TRUE, then the ECC level of the result may be higher than the ecl argument if it can be done without increasing the version. QRCODE_PARAM_CUSTOM: Allow to use function. + +#define QRCODE_TINY_VERSION 10 // The version number supported in the QR Code Model 2 standard +// Error correction level +// - QRCODE_ECC_LOW ............... The QR Code can tolerate about 7% erroneous codewords +// - QRCODE_ECC_MEDIUM ............ The QR Code can tolerate about 15% erroneous codewords +// - QRCODE_ECC_QUARTILE .......... The QR Code can tolerate about 25% erroneous codewords +// - QRCODE_ECC_HIGH .............. The QR Code can tolerate about 30% erroneous codewords +#define QRCODE_TINY_ECC QRCODE_ECC_LOW +// Mask pattern +// - QRCODE_MASK_0 ................ (i + j) % 2 = 0 +// - QRCODE_MASK_1 ................ i % 2 = 0 +// - QRCODE_MASK_2 ................ j % 3 = 0 +// - QRCODE_MASK_3 ................ (i + j) % 3 = 0 +// - QRCODE_MASK_4 ................ (i / 2 + j / 3) % 2 = 0 +// - QRCODE_MASK_5 ................ (i * j) % 2 + (i * j) % 3 = 0 +// - QRCODE_MASK_6 ................ ((i * j) % 3 + i * j) % 2 = 0 +// - QRCODE_MASK_7 ................ ((i * j) % 3 + i + j) % 2 = 0 +#define QRCODE_TINY_MASK QRCODE_MASK_0 + +//----------------------------------------------------------------------------- +// DEBUG & PROFILE +//----------------------------------------------------------------------------- + +// Debugger options +// - DEBUG_DISABLE ................ No debug tool +// - DEBUG_EMULICIOUS ............. Debug features for Emulicious +// - DEBUG_OPENMSX ................ Debug features for openMSX using 'debugdevice' extension +// - DEBUG_OPENMSX_P .............. Debug features for openMSX using PVM script (tools/script/openMSX/debugger_pvm.tcl) +#define DEBUG_TOOL DEBUG_DISABLE +// Profiler options +// - PROFILE_DISABLE .............. No profile tool +// - PROFILE_OPENMSX_G ............ Profiler features for openMSX using Grauw script (tools/script/openMSX/profiler_grauw.tcl) +// - PROFILE_OPENMSX_S ............ Profiler features for openMSX using Salutte script (tools/script/openMSX/profiler_salutte.tcl) +#define PROFILE_TOOL PROFILE_DISABLE +#define PROFILE_LEVEL 10 \ No newline at end of file diff --git a/projects/kissaten_yugure/project_config.js b/projects/kissaten_yugure/project_config.js new file mode 100644 index 0000000..6cc68d1 --- /dev/null +++ b/projects/kissaten_yugure/project_config.js @@ -0,0 +1,35 @@ +// Kissaten Yūgure - MSXgl project configuration +// MSX2 · SCREEN 5 · ASCII16 MegaROM (see docs/DESIGN.md §5) + +// ASCII-safe name: used for output filenames (ROM, symbols, intermediates) +ProjName = "kissaten"; +ProjModules = [ ProjName ]; +LibModules = [ "system", "bios", "vdp", "print", "input", "memory", "math" ]; + +Machine = "2"; + +// ASCII16 MegaROM from day one: dialogue, portraits and music banks will not +// fit in 32KB (DESIGN.md §5). 16KB segments; segments 0-1 are the fixed main +// code at 4000h~BFFFh, segments 2+ are banked data (dialogue, art, music). +Target = "ROM_ASCII16"; +ROMSize = 128; +ROMMainSegments = 2; +// Enable when banked data/code actually lands (stage 3 dialogue banks); +// with it on from day one, RAM globals were getting corrupted at runtime. +BankedCall = false; + +CheckVersion = true; // MSX2 required (SCREEN 5, V9938 commands) +AddROMSignature = true; + +// Required for disk saving. 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 PHYDIO hook is never patched. This option +// instead installs an H.STKE hook and returns to the scan, so the game starts +// after the disk system is up. Boot takes visibly longer as a result. +ROMDelayBoot = true; + +AppSignature = true; +AppCompany = "JL"; +AppID = "KY"; + +Verbose = true;