Scaffold Kissaten Yugure; add M2 day spine, plan and script
Stage 1 (shop scene, customer, siphon brew minigame, serve loop) and the M2 day spine: day counter, three-phase day, event-driven clock, journal line, and a SaveState with magic/version/checksum. Saving to disk is NOT working yet. A cartridge ROM can reach the Disk ROM only from RAM (both PHYDIO and CALSLT switch page 1 out from under the caller), and ROMDelayBoot is required so the Disk ROM's INIT runs at all. With both in place the disk is detected, but sector I/O returns carry-clear without transferring — proven with a sentinel. Probe now verifies the buffer actually changed, so a non-working disk degrades to "no save" rather than corrupting anything. Full findings in the project CLAUDE.md. Also documented: keyboard reads need interrupt protection (the BIOS ISR scans the matrix too), and BankedCall must stay off until banked data exists. Docs: PLAN.md (ten milestones, per-stage requirements, decision queue), SCRIPT.md (all four character arcs, 60 beats, checked against the 26-character box), and amendment notes in DESIGN.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,7 @@ dos2
|
|||||||
!/projects/template
|
!/projects/template
|
||||||
!/projects/template_msx2
|
!/projects/template_msx2
|
||||||
!/projects/mazegame
|
!/projects/mazegame
|
||||||
|
!/projects/kissaten_yugure
|
||||||
|
|
||||||
/projects/samples/datasrc
|
/projects/samples/datasrc
|
||||||
|
|
||||||
|
|||||||
@@ -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 <s> {screenshot f.png}` and `keymatrixdown 8 1` (SPACE).
|
||||||
|
Add `set throttle off` to the script: emulated time then runs many times
|
||||||
|
faster than wall clock, so a 200-second play-through takes a couple of
|
||||||
|
seconds. `after time` still counts emulated seconds, so timings hold.
|
||||||
|
- **Boot is slow — budget for it.** `ROMDelayBoot = true` (needed for disk
|
||||||
|
access, see below) means the game only appears at roughly 40-90s of
|
||||||
|
emulated time. Screenshots before that show a blue BASIC screen, which
|
||||||
|
looks exactly like a hang and is not one. Test with a disk machine:
|
||||||
|
`openmsx -machine Philips_NMS_8250 -carta emul/rom/kissaten.rom -diska save.dsk`
|
||||||
|
- Dialogue data (stage 3): `node tools/compile_dialogue.js data/dialogue/*.yaml`
|
||||||
|
- Music data (stage 3+): `node tools/generate_mml.js` → NDP compiler → `data/music/`
|
||||||
|
|
||||||
|
## Platform gotchas (learned the hard way)
|
||||||
|
- **Keyboard:** read the matrix row inside `DisableInterrupt()`/`EnableInterrupt()`.
|
||||||
|
`Keyboard_Read()` is an `out` (row select) then `in`; the BIOS ISR scans the
|
||||||
|
matrix too, and if it lands between the two you get phantom keypresses.
|
||||||
|
`INPUT_KB_UPDATE` (buffered mode) is *not* a fix — it reads the BIOS
|
||||||
|
NEWKEY/OLDKEY area, which C-BIOS does not maintain in the standard format.
|
||||||
|
- **`BankedCall = true` is off until banked code actually exists.** With it on
|
||||||
|
and nothing banked, RAM globals were corrupted at runtime (state machine
|
||||||
|
ran wild, counters garbage). Re-enable deliberately at stage 3.
|
||||||
|
- Erase blits must repaint the *actual* background at that spot — the counter
|
||||||
|
area has three bands (wainscot / counter top highlight / counter top).
|
||||||
|
|
||||||
|
## Disk saving from a cartridge ROM — status: UNRESOLVED
|
||||||
|
|
||||||
|
M2 wants saves on a real `.dsk`. The game is a cartridge MegaROM, which makes
|
||||||
|
this awkward. What is established so far, all verified on
|
||||||
|
`Philips_NMS_8250` + `-carta` + `-diska`:
|
||||||
|
|
||||||
|
- **A cartridge ROM does boot fine on a disk machine.** Not a problem.
|
||||||
|
- **`ROMDelayBoot = true` is required.** A cartridge's INIT normally runs
|
||||||
|
during the BIOS slot scan, and MSXgl never returns from it — so the Disk
|
||||||
|
ROM's own INIT never happens, `NMBDRV` stays 0 and the disk hooks are never
|
||||||
|
patched. This option installs an H.STKE hook and returns to the scan
|
||||||
|
instead. **Boot becomes much slower** — the game appears at ~40-90s of
|
||||||
|
emulated time, so early screenshots will show a blue BASIC screen and look
|
||||||
|
like a hang when they are not.
|
||||||
|
- **Any call into the disk system must be made from RAM.** Both PHYDIO and
|
||||||
|
CALSLT reach the Disk ROM at 4000h — the same page as this cartridge — so
|
||||||
|
the calling instruction is switched out mid-call unless it lives elsewhere.
|
||||||
|
`Disk_InitStub()` copies a small stub to RAM for exactly this. Calling
|
||||||
|
directly from ROM black-screens the machine.
|
||||||
|
- With the above in place, `NMBDRV` reads 2 and `MASTER` is non-zero, so the
|
||||||
|
Disk ROM *has* initialised by the time the game runs.
|
||||||
|
|
||||||
|
**The remaining blocker:** both `PHYDIO` (Main ROM 0144h) and a direct
|
||||||
|
`CALSLT` to `DSKIO` (Disk ROM 4010h) return **carry clear — "success" — while
|
||||||
|
never touching the destination buffer.** Proven with a 0xAA sentinel that
|
||||||
|
survives the call intact. Page 0 is confirmed to be the Main ROM at call time
|
||||||
|
(0x002D reads 1 = MSX2, 0x0144 holds 0xC3 = `JP`), so the entry points
|
||||||
|
themselves are addressable.
|
||||||
|
|
||||||
|
Because a clean error code is *not* proof that a read happened, `Disk_Probe()`
|
||||||
|
now verifies the buffer actually changed and treats "no change" as no disk.
|
||||||
|
The game degrades gracefully: no disk means day 1, nothing persists, and the
|
||||||
|
evening card says so. Nothing crashes on either C-BIOS or a real-BIOS machine.
|
||||||
|
|
||||||
|
Next things to try: check whether the H.PHYD RAM hook is genuinely patched at
|
||||||
|
that moment; try a different disk machine profile; or reconsider the medium
|
||||||
|
(see PLAN.md §8 item 1).
|
||||||
|
|
||||||
|
**RTC CMOS is not a viable fallback**, despite `RTC_USE_SAVEDATA` being TRUE
|
||||||
|
in `msxgl_config.h`. Block 3 of the RP-5C01 is 13 nibbles, and MSXgl's
|
||||||
|
`RTC_SaveData()` (`engine/src/clock.c`) stores exactly **6 bytes** — against a
|
||||||
|
~40-byte `SaveState`. `RTC_SaveDataSigned()` is tighter still. Remaining
|
||||||
|
options are cartridge SRAM, the FM-PAC SRAM (MSXgl has a `PAC` module), or
|
||||||
|
shrinking the save to a 6-byte fingerprint — which would cost per-character
|
||||||
|
affinity and money, so it is a design decision, not just a storage one.
|
||||||
|
Reference: MSX2 Technical Handbook ch.5, CLOCK-IC section.
|
||||||
|
|
||||||
|
## Architecture conventions
|
||||||
|
- VRAM page 0: visible shop scene bitmap. Page 1: asset warehouse (sprite
|
||||||
|
frames, portraits, UI tiles) blitted with VDP commands (HMMM/HMMV).
|
||||||
|
- Seasons and time-of-day are palette swaps only (32-byte tables); never
|
||||||
|
duplicate background art per season.
|
||||||
|
- Sprite mode 2, two layered 16×16 sprites per customer (outline + fill).
|
||||||
|
- Portraits are 48×48, three expressions per character max.
|
||||||
|
- Game state lives in a single `SaveState` struct (~40 bytes); see DESIGN.md.
|
||||||
|
Implemented in `kissaten.c` with a magic word, version byte and checksum;
|
||||||
|
a failed or absent load is not an error, it just means day one.
|
||||||
|
- The day is a three-phase machine (morning / open / evening) and the clock is
|
||||||
|
**event-driven** — customers are the clock. `CUSTOMERS_PER_DAY` is the
|
||||||
|
single pacing dial.
|
||||||
|
- Dialogue scenes are data, not code: trigger conditions → portrait,
|
||||||
|
expression, text, optional choice, effects.
|
||||||
|
|
||||||
|
## Code style
|
||||||
|
- C99, MSXgl idioms (u8/u16 types, `msxgl_` module prefixes)
|
||||||
|
- Keep ISR/VBlank work minimal; game logic in main loop
|
||||||
|
- Comment bank-switching boundaries explicitly
|
||||||
|
- Tools are Node.js (plain JS, no framework), living in `tools/`
|
||||||
|
|
||||||
|
## Scope ladder (build in this order; every stage must be playable)
|
||||||
|
1. ✅ Shop scene + one customer + brew minigame + serve loop (no story)
|
||||||
|
2. ◐ Day/night cycle + save + money — day counter, three phases and the
|
||||||
|
journal line work; **persistence is blocked** (see the disk section below).
|
||||||
|
Money deliberately deferred to stage 5, since nothing gates on it before.
|
||||||
|
3. Dialogue engine + two regulars with short arcs ← real milestone
|
||||||
|
4. Seasons/weather/palette system
|
||||||
|
5. Remaining cast, upgrades, endings
|
||||||
|
|
||||||
|
`docs/PLAN.md` expands this into ten milestones with per-stage requirements
|
||||||
|
and a decision queue. `docs/SCRIPT.md` holds the full first-draft script.
|
||||||
@@ -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
|
||||||
Executable
+15
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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."
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
Binary file not shown.
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 <QRCode_SetVersion>. 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 <QRCode_SetBoostECL> 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
|
||||||
@@ -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;
|
||||||
Reference in New Issue
Block a user