Compare commits

..
15 Commits
Author SHA1 Message Date
jurjen.ladeniusandClaude Opus 5 d5789ad8b3 Art: labelled palette reference cards
make_palettes.py now also emits kissaten-full-card.png and
kissaten-portrait-card.png — each swatch shown with its palette index and
hex code, so a colour can be identified while hand-editing without
counting along an anonymous strip.

Text is drawn with a hand-rolled 3x5 bitmap font covering digits, A-F and
'#'; there is no font library available here.

Drops palette_ref.png, superseded by kissaten-full.png.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 18:12:26 +02:00
jurjen.ladeniusandClaude Opus 5 aa8fa3ddf9 Art: emit the palette as .gpl/.ase/.aco as well as .act
The .act files only helped Photoshop. Affinity imports .ase and .aco;
Inkscape, GIMP, Krita and Aseprite want .gpl. tools/make_palettes.py now
generates all four plus swatch PNGs, for both the full 16 and the
11-colour portrait subset, all derived from the same PALETTE table shared
with kissaten.c so nothing can drift.

Colour names ride along in .gpl/.ase/.aco, so the swatches show up labelled
("skin", "lamp glow", "dusk violet") rather than as anonymous chips.

.ase output verified by reading it back and checking all 11 colours
round-trip exactly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 21:34:14 +02:00
jurjen.ladeniusandClaude Opus 5 db7de024de Art: SVG -> 48x48 portrait converter, and it works
Magnific's SVG export is flat-shaded vector (~550 solid-fill paths, 6
gradients), which converts to pixel art cleanly. tools/svg2portrait.py
rasterises at 16x the target via qlmanage, then takes the most common
colour per 16x16 block — a true pixelate with no blending — and maps to
the 11-colour portrait palette.

Measured on Aki: 19 colours after pixelate, 10 after palette, visually
near-identical. So the portrait palette is sufficient for this art.

Contrast with the bitmap route this replaces: a 48x48 PNG resized in
Photoshop had 1330 unique colours in 2304 pixels, because bicubic
resampling turns flat regions into gradients. Quantising that speckled
badly across the blazer and background.

Also adds tools/pixtool.py — dependency-free PNG decode/encode (no Pillow
on this machine) plus the palette table shared with kissaten.c.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 20:46:12 +02:00
jurjenwerk 1c0d2d9a88 Add AKI images 2026-07-28 11:41:10 +02:00
jurjen.ladeniusandClaude Opus 5 60c9e13fa8 Art brief: give each asset type its own palette block
The palette constraint is not uniform across assets, so a single generic
line was not enough:

- Portraits list only the 11 non-sky colours, since a face never shows sky;
  omitting the sky slots is safer than asking a generator to avoid them.
  Adds the flat #492424 backdrop requirement (HMMM blits have no
  transparency, so whatever surrounds the head gets copied literally).
- The background block is split by region: four sky colours inside the
  window opening, eleven interior colours everywhere else, with the frame
  and mullions counted as interior wood. Also reserves the top 16 and
  bottom 64 rows the game draws over.
- Sprites take no palette at all — they are hardware sprites coloured at
  runtime, so the art only defines shape.

Notes that the available clothing colours are few enough that characters
need distinct ones or they blur together in the dialogue box.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 10:23:34 +02:00
jurjen.ladeniusandClaude Opus 5 25a098e6af Add art brief and palette references for the PixelLab pipeline
Resolves decision 8: art is generated in PixelLab from written prompts.
docs/ART.md carries the palette spec, the sky/wood slot rule, per-asset
sizes and return format, and prompts for Aki (needed for M3), the rest of
the cast, and the shop background.

art/palette.png (16x1, one pixel per entry) and art/palette_ref.png
(512x64 swatch strip) are generated from g_Palette so the generator and
the build cannot drift apart.

Flags a constraint the palette had hidden: only one literal skin entry, so
portraits use 6 -> 14 -> 4 as a hue-compatible skin ramp.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 22:34:05 +02:00
jurjen.ladeniusandClaude Opus 5 97eebbaefc Plan: park the M10 save-slot choices as deferred decisions
Records slot count, naming, presentation, delete guard and the day-length
dial as decision-queue items 16-20 marked "defer": captured so the
reasoning survives, but explicitly not decided now. They only bite at M10
and are all easier to judge against a real slot screen than in the
abstract. Adds a "defer" convention to the queue preamble.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 22:26:54 +02:00
jurjen.ladeniusandClaude Opus 5 70b42f6da1 Plan: add M10 save slots (multi-file), renumber polish to M11
tool/disk_save already indexes every call by entry, so multiple save files
cost almost nothing on the storage side — KISSAT00.SAV, KISSAT01.SAV, and
DiskSave_Check/Delete/GetFreeEntries already exist per entry. The work is
the boot-time slot screen, which is why it sits late: it is easier to
design once there is real save content to display.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 22:21:01 +02:00
jurjen.ladeniusandClaude Opus 5 cbde457da5 M2 complete: disk saving works via MSXgl's tool/disk_save module
Replaces the hand-rolled PHYDIO/DSKIO sector writer, which never worked,
with engine/src/tool/disk_save.h — added in MSXgl v1.3.0 for exactly this
("save to disk from a ROM application"). Saves are now a real FAT file,
KISSAT00.SAV, on the disk in drive A.

Four non-obvious requirements, all documented in CLAUDE.md:
- LibModules needs both "tool/disk_save" and "dos"
- ROMDelayBoot = true, or the Disk ROM's INIT never runs
- boot then takes 40-90s of emulated time (use `set throttle off`)
- DiskSave_Check() returns SAVEDATA_UNSIGNED for good files: with APPSIGN
  it wants the first 4 bytes to be g_AppSignature, but DiskSave_Save()
  writes the payload raw and never adds it

Also fixes a save-timing bug: the write now happens in AdvanceDay() after
the day increments, so the file describes the morning the player wakes to.
Saving during the evening made a reload replay that day and double-count
its cups.

Verified: play a full day, cold boot, resume at day 2 with served intact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 22:17:49 +02:00
jurjen.ladeniusandClaude Opus 5 45749775fa Scaffold Kissaten Yugure; add M2 day spine, plan and script
Stage 1 (shop scene, customer, siphon brew minigame, serve loop) and the
M2 day spine: day counter, three-phase day, event-driven clock, journal
line, and a SaveState with magic/version/checksum.

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

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

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 21:58:22 +02:00
jurjen.ladeniusandClaude Fable 5 4c33bdb880 Document special keys and ayFX in CLAUDE.md
Game flow now lists all movement-loop keys (M/joy-A minimap, S cat
sense, C cheat landing one move short of the exit), and the MSXgl
configuration section covers the full audio stack including ayFX.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 23:36:09 +02:00
jurjen.ladeniusandClaude Fable 5 d64cb8b37c Add ayFX sound effects; fix torus strip fills ignoring the orthogonal base
Sound effects (ayFX on the PSG, mixed over the FM music):
- 8-effect bank from sfx/sfx_bank.afb embedded as sfx_bank.h in fixed
  main ROM (interrupt-readable without bank switching). Triggers: random
  cat-paw footstep (0-2) per move, fanfare (4) on reaching the exit, map
  unfold (5) on M, laugh (6) on C, ta-daa (7) when advancing the title/
  hint/win screens. Priorities keep footsteps below UI/finish sounds.
- The H_TIMI hook now also runs ayFX_Update + PSG_Apply each frame;
  PSG switched to indirect (buffered) access with AYFX_BUFFER_DEFAULT.
- Interrupt-safety fixes this surfaced: all joystick reads go through
  ReadJoy() (DI/EI around Joystick_Read, which drives the PSG and raced
  the ISR's PSG_Apply — caused noise, phantom input, minimap-on-move),
  and the PSG mixer is muted at startup (zeroed buffer = all channels
  enabled active-low, which PSG_Apply drove as noise during the logo).

Torus scrolling fix (bug from 7c24daa, exposed by mixed-direction play):
- ScrollDraw's bulk strip fills only honoured the scroll-axis base, so
  with a non-zero orthogonal base the newly revealed edge was drawn
  rotated by 2/4/6 tiles — phantom walls/passages while collision stayed
  correct. Vertical strips are now pre-rotated by g_BaseX in staging;
  horizontal column fills split at the 256-row wrap via FillStripColumns.

CLAUDE.md updated for both (SFX table + ISR rules, both-axis fill rule).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 23:33:07 +02:00
jurjen.ladeniusandClaude Opus 4.8 00b5833bb2 C cheat drops player one move from the exit, not on it
Instead of teleporting straight onto the exit cell (instant win), the C
cheat now places the player on an open neighbour of the exit, leaving one
valid move onto it. The exit is a normal spanning-tree cell, so at least
one of its up/left/right neighbours has an open passage; pick the first.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 21:48:14 +02:00
jurjen.ladeniusandClaude Opus 4.8 9faff1cfba Fix music tempo, note-on-switch, and title scroll shift
Three follow-up fixes to the per-screen music:

- Decode music in the H_TIMI VBlank interrupt (MusicVBlank) instead of
  once per Halt() in the game loops. The heavy per-step VDP work in
  ScrollDraw could overrun a VBlank, dropping the decode rate so the
  music slowed only while moving; the ISR keeps tempo steady regardless.
  The playing track is mapped into the unused bank-3 window (0xA000) and
  left there for the screen, so the ISR reads it with no bank switching
  and never races the main code's bank-2 tile/sprite swaps. PlayTrack()
  switches tracks under DI/EI. The game loops revert to plain Halt()s.

- PlayTrack() mutes the OPLL (key-off all 9 channels) before starting the
  next track, silencing the note that lingered across screen changes.

- TitleScreen()/ShowHints() zero the hardware scroll offset before drawing
  their absolute-coord images. Gameplay rests at g_BaseX/g_BaseY in
  {0,64,128,192}; without the reset the returning title/hints screen
  appeared horizontally shifted (only after finishing on a non-zero base).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 21:15:55 +02:00
jurjen.ladeniusandClaude Opus 4.8 1c0636e8ff Add per-screen FM music (lVGM / MSX-Music)
Each screen now plays its own looping MSX-Music (YM2413/OPLL) track:
title, hints, gameplay, and victory. Tracks are lVGM files embedded in
banked segments 26-29 (one per segment, each < 8KB so it never crosses a
boundary); segments 30-31 remain free.

- project_config.js: add psg/msx-music/vgm/lvgm_player modules; set
  EmulMSXMusic so ./build.sh run gives openMSX the FM-PAC (-ext fmpac).
- msxgl_config.h: enable LVGM_USE_PSG/MSXMUSIC (rest off), PSG_DIRECT.
- mazegame.c: MSXMusic_Initialize() once at boot; PlayTrack()/PlayFrame()
  bracket every player call with a bank-2 save/restore so music never
  disturbs the tile/sprite banking; a PlayFrame() follows every Halt() in
  the wait/animation loops (ScrollDraw, input loops now frame-synced,
  WaitKeySequence) to advance each track at 60 Hz. Logo stays silent.
- CLAUDE.md: document the music pipeline, banking protocol, and segments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 20:43:18 +02:00
53 changed files with 5425 additions and 28 deletions
+1
View File
@@ -41,6 +41,7 @@ dos2
!/projects/template
!/projects/template_msx2
!/projects/mazegame
!/projects/kissaten_yugure
/projects/samples/datasrc
+114
View File
@@ -0,0 +1,114 @@
# Kissaten Yūgure — MSX2 cozy game
## Project overview
A cozy coffee-shop (kissaten) simulation for MSX2, set in a provincial Japanese
town in 1987. Target audience: adult retro gamers who love Japanese art and
computers. Written in C using the MSXgl library. Working title: "Kissaten Yūgure"
(Twilight Coffee Shop).
Read `docs/DESIGN.md` before making any design or architecture decisions.
Read `docs/CONVERSATION.md` for the original design discussion and rationale.
## Design pillars (never violate)
- Cozy contract: no fail states, no bankruptcy, no time pressure outside the
brew minigame. Progression is always forward.
- Atmosphere over mechanics: palette work, music, and character writing carry
the game.
- Static-heavy rendering: no scrolling. Only sprites and small blits move.
- Small, finishable scope. When in doubt, cut.
## Tech stack
- Target: MSX2, SCREEN 5 (G4, 256×212, 16 colors from 512)
- ROM format: ASCII16 MegaROM (banked) — plan bank layout from day one
- Library: MSXgl (C) — https://github.com/aoineko-fr/MSXgl
- Compiler: SDC C via MSXgl build system
- Music: **unresolved — this line is stale.** It describes a PSG / MML → NDP
pipeline, but the working pipeline used for `projects/mazegame` is the
`msx-music-generator` repo emitting lVGM + ayFX, on MSX-Music (YM2413).
This project targets plain MSX2, where FM is not standard hardware.
Decision deferred to M9; see `docs/PLAN.md` §8 item 15.
- Dialogue: authored in YAML/JSON, compiled to C arrays / binary banks by
`tools/compile_dialogue.js` (Node.js)
- Emulator for testing: openMSX (also verify on real hardware profiles)
## Build & test
- Build: `./build.sh` (MSXgl build_tool; ROM → `out/` and `emul/rom/kissaten.rom`)
- Run: `./build.sh run` → builds and launches openMSX
- Requires `node` on PATH, else build.sh falls back to the bundled
Windows/Linux binary and dies: `export PATH="/opt/homebrew/opt/node@24/bin:$PATH"`
- Headless verification: drive openMSX with a Tcl script —
`openmsx -machine C-BIOS_MSX2 -carta emul/rom/kissaten.rom -script test.tcl`
using `after time <s> {screenshot f.png}` and `keymatrixdown 8 1` (SPACE).
Add `set throttle off` to the script: emulated time then runs many times
faster than wall clock, so a 200-second play-through takes a couple of
seconds. `after time` still counts emulated seconds, so timings hold.
- **Boot is slow — budget for it.** `ROMDelayBoot = true` (needed for disk
access, see below) means the game only appears at roughly 40-90s of
emulated time. Screenshots before that show a blue BASIC screen, which
looks exactly like a hang and is not one. Test with a disk machine:
`openmsx -machine Philips_NMS_8250 -carta emul/rom/kissaten.rom -diska save.dsk`
- Dialogue data (stage 3): `node tools/compile_dialogue.js data/dialogue/*.yaml`
- Music data (stage 3+): `node tools/generate_mml.js` → NDP compiler → `data/music/`
## Platform gotchas (learned the hard way)
- **Keyboard:** read the matrix row inside `DisableInterrupt()`/`EnableInterrupt()`.
`Keyboard_Read()` is an `out` (row select) then `in`; the BIOS ISR scans the
matrix too, and if it lands between the two you get phantom keypresses.
`INPUT_KB_UPDATE` (buffered mode) is *not* a fix — it reads the BIOS
NEWKEY/OLDKEY area, which C-BIOS does not maintain in the standard format.
- **`BankedCall = true` is off until banked code actually exists.** With it on
and nothing banked, RAM globals were corrupted at runtime (state machine
ran wild, counters garbage). Re-enable deliberately at stage 3.
- Erase blits must repaint the *actual* background at that spot — the counter
area has three bands (wainscot / counter top highlight / counter top).
## Disk saving from a cartridge ROM — WORKING
Saves go to a real file on a real disk: `KISSAT00.SAV` in the root of the
disk in drive A. The disk stays a normal FAT disk you can inspect and copy.
**Use `engine/src/tool/disk_save.h`.** MSXgl v1.3.0 added this module
specifically for "save to disk from a ROM application". Do not hand-roll
sector I/O — an earlier attempt using PHYDIO (Main ROM 0144h) and a direct
CALSLT to DSKIO (4010h) had both entry points return carry-clear *while
transferring nothing*, proven with a sentinel that survived the call intact.
Four things are required, none of them obvious:
1. **`LibModules` must include both `"tool/disk_save"` and `"dos"`.** The
module alone will not link. See `projects/samples/s_save.js` for the
reference configuration, and `s_save.c` for usage.
2. **`ROMDelayBoot = true`.** A cartridge's INIT normally runs during the BIOS
slot scan and MSXgl never returns from it, so the Disk ROM's own INIT never
happens and there is no disk system to talk to. This option installs an
H.STKE hook and returns to the scan instead.
3. **Boot becomes slow.** The game appears at roughly 40-90s of *emulated*
time. Screenshots before that show a blue BASIC screen and look exactly
like a hang. Use `set throttle off` in the test script so this costs
seconds of wall clock, not minutes.
4. **`DiskSave_Check()` returns `SAVEDATA_UNSIGNED` for perfectly good
files.** With `AppSignature = true` (`-DAPPSIGN`), `DiskSave_Check()`
requires the file's first four bytes to equal `g_AppSignature` — but
`DiskSave_Save()` writes the payload raw and never adds it. Treat
`SAVEDATA_UNSIGNED` as success; `SaveState` carries its own magic, version
and checksum, which is a stronger check regardless.
**Save timing:** written in `AdvanceDay()` *after* the day counter increments,
so the file always describes the morning the player wakes to. Saving during
the evening instead makes a reload replay the day just finished and
double-count its cups.
Absent or unusable disk is not an error the player has to handle — it just
means nothing persists, and the morning card says so.
Test:
```
openmsx -machine Philips_NMS_8250 -carta emul/rom/kissaten.rom -diska save.dsk
```
Create a blank 720K disk with `tools/build/msxtar/msxtar -cf save.dsk --dos1 --size=720K`.
**RTC CMOS is not an alternative** for a save this size, despite
`RTC_USE_SAVEDATA` being TRUE in `msxgl_config.h`. Block 3 of the RP-5C01 is
13 nibbles and MSXgl's `RTC_SaveData()` stores exactly **6 bytes**, against a
~40-byte `SaveState`. (MSXgl has no cartridge-SRAM mapper target either; the
`PAC` module's FM-PAC SRAM, 8 x 1024 bytes, is the other real option.)
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,20 @@
GIMP Palette
Name: Kissaten Yugure (full)
Columns: 16
#
0 0 0 transparent
36 36 36 outline
73 36 36 dark wood
146 73 36 mid wood
182 109 73 light wood
255 182 73 lamp glow
255 219 146 cream
109 73 73 muted wall
73 36 146 dusk deep
146 73 182 dusk violet
219 109 109 dusk rose
255 146 73 horizon amber
109 36 0 coffee
73 109 182 coat blue
255 182 146 skin
255 255 219 warm white
Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,15 @@
GIMP Palette
Name: Kissaten Yugure (portrait)
Columns: 11
#
36 36 36 outline
73 36 36 dark wood
146 73 36 mid wood
182 109 73 light wood
255 182 73 lamp glow
255 219 146 cream
109 73 73 muted wall
109 36 0 coffee
73 109 182 coat blue
255 182 146 skin
255 255 219 warm white
Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

+12
View File
@@ -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
+15
View File
@@ -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
+252
View File
@@ -0,0 +1,252 @@
# Kissaten Yūgure — Art brief
## Pipeline — export SVG, not PNG
**Generate at high resolution, export as SVG, drop it in `art/`, and run:**
```
python3 tools/svg2portrait.py art/aki-neutral.svg
```
That writes `art/aki-neutral-48.png` (the real asset) and
`art/aki-neutral-48-x6.png` (a 6× preview for judging).
**Export SVG, never a resized bitmap.** This was learned the hard way. A
48×48 PNG produced by resizing in Photoshop came back with **1330 unique
colours** in 2304 pixels — bicubic resampling turns every flat region into a
gradient, and reducing *that* to 11 colours speckles horribly, worst on large
flat areas like the blazer and background.
The SVG from Magnific is flat-shaded vector: ~550 paths, solid fills, a
handful of gradients. The converter rasterises it at 768×768 (16× the target)
via `qlmanage`, then takes the **most common colour in each 16×16 block**
a true pixelate with no blending at all. Measured on Aki: 19 colours after
the pixelate, 10 after the palette reduction, and the two are nearly
indistinguishable. **The 11-colour portrait palette is sufficient for this
art** — that was the open question and it's settled.
If you must work from bitmaps, the same logic applies: resize only by exact
integer factors with nearest-neighbour, never bicubic.
---
## 1. The palette — read this before generating anything
Everything in the game shares **one 16-colour palette**. It is already in
`kissaten.c` as `g_Palette` and is what the current build renders with.
- `art/palette.png` — 16×1 px, one pixel per entry. Machine-readable; use it
wherever PixelLab accepts a palette image.
- `art/palette_ref.png` — 512×64 swatch strip, same order, for eyeballing.
| # | Hex | Role | Notes |
|---|---|---|---|
| 0 | `#000000` | transparent | **Never paint with this.** It is the transparent index. |
| 1 | `#242424` | outline | near-black; outlines, dark hair |
| 2 | `#492424` | dark wood | deep shadow, dark brown hair |
| 3 | `#924924` | mid wood | wall panels, doors, deep skin shadow |
| 4 | `#B66D49` | light wood | counter top, **skin shadow** |
| 5 | `#FFB649` | lamp glow | warm light |
| 6 | `#FFDB92` | cream | glassware, **skin highlight** |
| 7 | `#6D4949` | muted wall | upper wall, muted cloth |
| 8 | `#492492` | dusk deep | **sky only** |
| 9 | `#9249B6` | dusk violet | **sky only** |
| 10 | `#DB6D6D` | dusk rose | **sky only** |
| 11 | `#FF9249` | horizon amber | **sky only** |
| 12 | `#6D2400` | coffee | coffee, dark red-brown cloth |
| 13 | `#496DB6` | coat blue | uniforms, coats |
| 14 | `#FFB692` | skin | **skin base** |
| 15 | `#FFFFDB` | warm white | highlights, cup rims, eye whites |
### The one non-negotiable rule
**Slots 811 are sky. Slots 47 are wood and warm interior.** Nothing else may
use 811.
This is what makes seasons and time-of-day free: the background bitmap is
authored once, and each season/hour is a 32-byte palette swap. If a character's
jacket is painted in slot 9, that jacket changes colour every time the sun
moves. Getting this wrong is not a cosmetic issue — it silently destroys the
whole seasonal system, and you don't find out until M7.
### Skin, specifically
There is only one literal "skin" entry, which is too thin for a face. Use
these three as a ramp — they're hue-compatible:
- highlight `#FFDB92` (6) → base `#FFB692` (14) → shadow `#B66D49` (4)
- deepest shadow / hair: `#924924` (3), `#492424` (2), `#242424` (1)
---
## 2. What I need back
| Asset | Size | Count |
|---|---|---|
| Character portrait | **48×48** | 3 per character (neutral / happy / troubled) |
| Shop background | **256×212** | 1 |
| Customer sprite | **16×16** | 2 layers per character (fill + outline) |
**PNG, generated natively at the target size — do not downscale a larger
image.** Downscaling pixel art produces half-pixels and anti-aliased edges
that cannot be quantised cleanly.
No anti-aliasing, no gradients, no dithering unless it's deliberate hard
checkerboard. If PixelLab can be constrained to the 16 colours, do that; if
not, generate close and I'll quantise — but the sky/wood slot rule above
still has to hold visually or the quantiser can't fix it.
**48×48 is very small** — roughly 6 pixels of eye. Faces need a strong
silhouette and simplified features. Detail that reads at 200×200 will turn to
mud. If a generation looks great zoomed out and illegible at 1:1, it's wrong.
---
## 3. Prompts — Aki (needed now, for M3)
Hoshino Aki, 17, studying for Tokyo entrance exams, cream soda drinker.
Character notes in `SCRIPT.md` §1. Written as a girl there; that's one word to
change if you'd rather — the dialogue beats themselves use no gendered
pronouns.
### Portrait palette block — paste into every portrait prompt
Portraits never show sky, so the four sky slots are simply absent from the
list. That is safer than asking the generator to avoid them.
```
Use only these 11 colours and no others:
#242424 #492424 #924924 #B66D49 #FFB649 #FFDB92 #6D4949 #6D2400
#496DB6 #FFB692 #FFFFDB
Skin: #FFDB92 highlight, #FFB692 base, #B66D49 shadow, #924924 deep shadow.
Hair and outlines: #242424 or #492424.
Eye whites and highlights: #FFFFDB.
Fill the entire background behind the head with flat #492424.
Do not use pure black #000000 anywhere.
```
**Common style stem** — prepend to all three, then append the palette block:
> 48x48 pixel art portrait, 1987 Japanese anime style as drawn for MSX2 or
> PC-88 games, head and shoulders, three-quarter view, hard-edged pixels, no
> anti-aliasing, flat cel shading, simple readable silhouette, flat single
> colour background
**Aki — neutral**
> ...17 year old Japanese high school student, straight dark bob-length hair,
> tired but polite expression, calm eyes, slight shadows under the eyes,
> looking slightly off to one side
**Aki — happy**
> ...17 year old Japanese high school student, straight dark bob-length hair,
> small genuine smile, eyes softened and slightly narrowed, faint blush,
> looking toward the viewer
**Aki — troubled**
> ...17 year old Japanese high school student, straight dark bob-length hair,
> eyes cast downward, faint frown, tense mouth, weary
---
## 4. Prompts — the rest of the cast (M8, not urgent)
Same style stem and the **same portrait palette block from §3**, swapping the
clothing line. Note the clothing colours available are only `#496DB6` (blue),
`#6D4949` (muted brown-grey), `#6D2400` (dark red-brown), `#492424` and
`#242424` — so give each character a distinct one or they'll blur together in
the dialogue box.
**Ōta-san** — retired stationmaster, ~70, black coffee, punctual.
> elderly Japanese man in his seventies, close-cropped grey hair, deeply
> lined face, upright bearing, plain dark jacket buttoned to the collar
- *neutral:* composed, formal · *happy:* faint dignified smile, eyes crinkled
- *troubled:* jaw set, looking past the viewer
**Fujimoto** — manga artist, ~30, exhausted, funny about it.
> Japanese manga artist in their thirties, messy shoulder-length hair, heavy
> shadows under the eyes, round glasses, rumpled shirt with sleeves pushed up
- *neutral:* wired, sleepless · *happy:* crooked grin, animated
- *troubled:* hollow stare, hand near face
**Nakajima-san** — widow, ~65, orders two coffees.
> Japanese woman in her mid-sixties, silver hair pinned back neatly, soft
> composed face, high-collared blouse under a plain cardigan, small pearl
> earrings
- *neutral:* serene, self-possessed · *happy:* warm gentle smile
- *troubled:* distant, looking at something not in the room
---
## 5. Prompt — the shop background (M6, longest lead time)
256×212. This one carries the game's whole look and is the hardest to redo,
so it's worth several attempts.
Composition is fixed by the code — see `DESIGN.md` §4 and `DrawScene()`:
- Top **16px** is the status bar (drawn over) — keep it empty/dark.
- **y=16148** is the shop scene.
- Bottom **64px** is the dialogue window (drawn over) — keep it empty/dark.
- Window on the **left** (~x1688), showing dusk sky in slots 811 only.
- Door on the **right** (~x208244).
- Counter running horizontally at **y≈98**, its top edge level.
- Hanging lamp above the counter, centre.
- Siphon coffee rig sits on the counter left of centre — I can keep drawing
this procedurally if it's easier to leave out.
> 256x212 pixel art interior of a small 1987 Japanese kissaten coffee shop at
> dusk, viewed straight on, static single-screen game background, wooden
> counter running across the lower third, tall window on the left showing a
> violet and amber dusk sky, wooden door on the right, hanging lamp with warm
> glow above the counter, wood panelling, shelves with glassware, cosy and
> quiet, 1987 MSX2 game art, hard-edged pixels, no anti-aliasing, flat cel
> shading
### Background palette block — this one is split by region
Unlike the portraits, the background needs all 15 colours — but **which
colours may appear is decided by where you are in the image**. This is the
constraint that makes seasons free, and it is the easiest one to get wrong.
```
Use only these 15 colours and no others.
For the sky visible through the window, use ONLY these four:
#492492 #9249B6 #DB6D6D #FF9249
For everything else — walls, floor, counter, door, window frame, lamp,
shelves, glassware, every object in the room — use ONLY these eleven:
#242424 #492424 #924924 #B66D49 #FFB649 #FFDB92 #6D4949 #6D2400
#496DB6 #FFB692 #FFFFDB
The four sky colours must appear nowhere except inside the window opening.
The window frame, mullions and sill are wood and use the interior colours.
Do not use pure black #000000 anywhere.
Leave the top 16 rows and the bottom 64 rows flat #242424 — the game draws
its own status bar and dialogue box over them.
```
**Why this one rule matters more than the rest of this document:** each season
and hour of day is a 32-byte palette swap on the same bitmap. Repaint slots
811 and the sky goes from dusk to noon to rain for free. But if a chair leg
or a coffee cup is painted in `#9249B6`, that chair leg changes colour every
time the sun moves — and you won't find out until M7, long after the art is
finished.
---
## 6. Sprites (M6)
16×16, two layers per customer: a **fill** layer and an **outline** layer that
is the same silhouette dilated by one pixel, drawn behind. Sprite mode 2, so
each layer is a single colour — these are shapes, not shaded art. Current
placeholders are in `kissaten.c` as `g_SprCustFill` / `g_SprCustLine`.
**Palette does not apply here in the usual sense.** These are hardware
sprites, not bitmap art: each layer is one flat colour chosen at runtime by
the code, so the image only needs to define *shape*. Draw them as pure
black-on-white silhouettes and I'll assign colours — currently `#496DB6` for
the fill layer and `#242424` for the outline, per customer.
Worth doing by hand rather than generating; a 16×16 single-colour silhouette
is faster to draw than to prompt for.
@@ -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 ≈ 510 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:** 68 characters (retired stationmaster, exam student, manga
artist, widow...), each with 1220 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 811, wood tones in 47); 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:** 68 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
+175
View File
@@ -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 eleven-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 ≈ 510 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 ~1220 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 811, wood/warm tones in
47. 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: 68 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."
+559
View File
@@ -0,0 +1,559 @@
# 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 510 real minutes. I need one number to
tune against, plus a rough customers-per-day count (I'd start at 46).
- **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: ✅ complete.** Day counter, three phases, journal line, `SaveState`
and disk persistence all working. Verified by playing a day, cold-booting, and
resuming at day 2 with the served count intact.
**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 — the slot screen is already a boot-time front-end, so
"how long should a day be?" has a natural home there, asked once when a new
shop is started. 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 811, wood/warm 47), plus 12 portraits (4 characters × 3
expressions). Retire the placeholder `HMMV` rectangles.
**Requires — from you:** generated in **PixelLab** from the prompts in
`ART.md` — one 256×212 background and 12 portraits (48×48, three expressions
× four regulars), returned as PNGs in `art/`.
The palette spec and the sky/wood slot rule are written up in `ART.md` §1,
with machine- and human-readable references at `art/palette.png` and
`art/palette_ref.png`. **That rule is the whole of M7 riding on one
constraint**, and a generator will not respect it unprompted.
**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
68 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 68 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 — Save slots (multi-file)
Several shops on one disk. `tool/disk_save` is already built for this: every
call takes an `entry` index, so slots become `KISSAT00.SAV`, `KISSAT01.SAV`
and so on with no change to the save format. `DiskSave_Check(entry)` reports
each slot's state, `DiskSave_GetFreeEntries()` counts the free ones, and
`DiskSave_Delete(entry)` removes one. **The storage side is nearly free.**
The actual work is front-end: a slot screen at boot showing what's in each
one, and a confirm step before overwriting or deleting. That is also why this
milestone sits late — it touches the boot flow, and it is much easier to
design once there is real save content to display.
Worth framing in-fiction rather than as a file manager. A slot is not "Save
1", it is a shop with days behind it — *"Day 34 · 210 cups"* tells the player
which one is theirs better than a filename does.
**Requires — from you — all deferred to M10 (§8 items 1620):** how many
slots; whether they are named; whether a slot presents as a shop or a save
file; what guards deletion; and whether the day-length dial surfaces here as a
player-facing choice. Recommendations are recorded in the decision queue, but
none of these should be settled before the screen exists to judge them
against — they are cheap to decide late and awkward to unpick if fixed early.
**Requires — in place:** M2's save layer (done). Nothing else technically —
this could be built any time, and is late only because the design benefits
from real content.
**Produces:** slot selection screen, per-slot summary, delete with confirm,
`SAVE_ENTRY` becoming a runtime value rather than the `#define` it is now.
**Exit:** Three shops can be run in parallel from one disk, each resuming to
its own day and cast state, and no single keypress can destroy one.
### M11 — 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 |
|---|---|
| 13 | Establish the routine. Player learns the usual. |
| 48 | A crack. Mentioned in passing, never explained. |
| 914 | 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 (68 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 |
|---|---|---|
| 68 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; items marked **defer** are parked on purpose —
captured so the reasoning survives, but not to be decided until their
milestone arrives.
| # | Needed for | Decision | My recommendation |
|---|---|---|---|
| 1 | M2 | ~~Save medium~~**save disk (.dsk)** | ✅ working, via MSXgl's `tool/disk_save` module. Saves `KISSAT00.SAV` to drive A. Four non-obvious requirements — see the disk section in `../CLAUDE.md` |
| 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 | ~~Who makes the art~~**PixelLab, from my prompts** | ✅ resolved. Prompts, palette spec and return format in `ART.md`; palette reference images in `art/` |
| 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 · **defer** | How many save slots | 3 — the limit is screen space, not disk room |
| 17 | M10 · **defer** | Whether slots are named | Skip naming. Text entry on a machine with no line editor is real work; "Day 34 · 210 cups" identifies a shop better than a filename anyway |
| 18 | M10 · **defer** | Slot presentation: shops, or save files | Shops. A slot is somewhere with days behind it, not a numbered file |
| 19 | M10 · **defer** | Deletion guard | Two-step confirm. This is the only unrecoverable action in a game with no fail states — the cozy contract applies to the save screen too |
| 20 | M10 · **defer** | Day length as a player-facing choice | Ask once when a new shop is started, on the slot screen. One saved byte |
| 21 | M11 | Hardware access + 50/60Hz target | — |
**Deferred items (1620)** are recorded so the thinking isn't lost, but they
are deliberately *not* being decided now. They only bite at M10, and every one
of them is easier to judge with real save content on screen than in the
abstract. Don't let them pull scope forward.
With 8 resolved, nothing on the live list is externally blocked — the
remaining items are all things I can propose and you can react to.
## 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.**M3 vertical slice** — Aki's first six beats, hardcoded, with a scratch
portrait. The next real milestone, and the one that decides whether the
game works (§2, M3).
4.**Generate Aki's three portraits** from the prompts in `ART.md`, so M3
can be judged against a real face rather than a brown rectangle.
3 and 4 are independent, and 4 unblocks the honest version of 3.
+492
View File
@@ -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 16 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.
+828
View File
@@ -0,0 +1,828 @@
// 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"
#include "tool/disk_save.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
bool g_SaveOk = TRUE; // did the last write succeed (shown next morning)
//=============================================================================
// 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;
//=============================================================================
// DISK SAVE
//=============================================================================
//
// Uses MSXgl's tool/disk_save module (added in v1.3.0, "save to disk from a
// ROM application"). It writes a real named file — KISSAT0.SAV — through the
// Disk ROM, so the save disk stays a normal, inspectable FAT disk.
//
// This replaced a hand-rolled PHYDIO/DSKIO sector writer that never worked:
// both entry points returned carry-clear without transferring anything. Do
// not go back to raw sectors; use this module.
//
// Requirements, both non-obvious:
// - LibModules must include BOTH "tool/disk_save" and "dos".
// - ROMDelayBoot = true, or the Disk ROM's INIT never runs (see CLAUDE.md).
// Save slot. Multiple save files would be entries 1, 2, ... on the same disk.
#define SAVE_ENTRY 0
bool g_HasDisk = FALSE; // a usable save disk was found at boot
//-----------------------------------------------------------------------------
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);
}
//-----------------------------------------------------------------------------
// A missing drive or a blank disk is not an error the player has to deal
// with — it just means nothing will persist.
void Disk_Init()
{
DiskSave_SetName("KISSAT"); // 6 chars max
DiskSave_SetExtension("SAV"); // 3 chars max
g_HasDisk = (DiskSave_Initialize() == SAVEDATA_VALID);
}
//-----------------------------------------------------------------------------
// TRUE if a valid save was loaded; FALSE means "start a new game", which is
// also what a blank disk gives us.
bool Save_Load()
{
if (!g_HasDisk)
return FALSE;
// SAVEDATA_UNSIGNED is expected, not an error: with AppSignature on,
// DiskSave_Check() wants the file's first 4 bytes to be g_AppSignature,
// but DiskSave_Save() writes the payload raw and never adds it — so a
// perfectly good file always reports UNSIGNED. Only a genuinely absent or
// unreadable file is a reason to bail; the magic and checksum below are a
// stronger check than the signature would have been anyway.
u8 status = DiskSave_Check(SAVE_ENTRY);
if ((status != SAVEDATA_VALID) && (status != SAVEDATA_UNSIGNED))
return FALSE;
if (!DiskSave_Load(SAVE_ENTRY, (u8*)&g_Save, sizeof(SaveState)))
return FALSE;
// Trust nothing that came off a disk
if (g_Save.magic != SAVE_MAGIC || g_Save.version != SAVE_VERSION)
return FALSE;
if (g_Save.checksum != Save_Checksum(&g_Save))
return FALSE;
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);
return DiskSave_Save(SAVE_ENTRY, (const u8*)&g_Save, sizeof(SaveState));
}
//-----------------------------------------------------------------------------
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.");
if (!g_HasDisk)
Dlg_Line(2, "(no save disk in drive A)");
else if (!g_SaveOk)
Dlg_Line(2, "(save failed)");
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.");
Dlg_Prompt("[SPACE] sleep");
}
//-----------------------------------------------------------------------------
// Save *after* the day rolls over, so the file always describes the morning
// the player will wake up to. Saving during the evening instead would make a
// reload replay the day just finished and double-count its cups.
void AdvanceDay()
{
g_Save.day++;
g_SaveOk = Save_Write();
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
// 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();
Disk_Init();
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;
}
}
}
+641
View File
@@ -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,37 @@
// 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 ];
// "tool/disk_save" needs "dos" alongside it — see projects/samples/s_save.js
LibModules = [ "system", "bios", "vdp", "print", "input", "memory", "math",
"tool/disk_save", "dos" ];
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;
@@ -0,0 +1,131 @@
#!/usr/bin/env python3
"""Emit the Kissaten palette in every format the art tools want.
python3 tools/make_palettes.py
.gpl GIMP / Inkscape / Krita / Aseprite
.ase Affinity (Photo, Designer, Publisher), Illustrator
.aco Affinity, Photoshop
.act Photoshop indexed-colour tables
.png 16x1 exact + a swatch strip, and a palette reference for generators
Two sets are written: the full 16 (scene work) and the 11-colour portrait
subset with the sky slots and the transparent index removed.
"""
import struct, os, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from pixtool import PALETTE, write_png
NAMES = ["transparent", "outline", "dark wood", "mid wood", "light wood",
"lamp glow", "cream", "muted wall", "dusk deep", "dusk violet",
"dusk rose", "horizon amber", "coffee", "coat blue", "skin",
"warm white"]
PORTRAIT_IDX = [1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15]
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "art")
def gpl(path, cols, names, title):
L = [f"GIMP Palette", f"Name: {title}", f"Columns: {len(cols)}", "#"]
for c, n in zip(cols, names):
L.append(f"{c[0]:3d} {c[1]:3d} {c[2]:3d}\t{n}")
open(path, "w").write("\n".join(L) + "\n")
def ase(path, cols, names):
blocks = b""
for c, n in zip(cols, names):
nm = n.encode("utf-16-be") + b"\x00\x00"
body = (struct.pack(">H", len(n) + 1) + nm + b"RGB "
+ struct.pack(">fff", c[0] / 255, c[1] / 255, c[2] / 255)
+ struct.pack(">H", 2)) # 2 = normal colour
blocks += struct.pack(">HI", 0x0001, len(body)) + body
open(path, "wb").write(b"ASEF" + struct.pack(">HHI", 1, 0, len(cols)) + blocks)
def aco(path, cols, names):
# v1 then v2 concatenated — v1 for old readers, v2 carries the names
v1 = struct.pack(">HH", 1, len(cols))
for c in cols:
v1 += struct.pack(">HHHHH", 0, c[0] * 257, c[1] * 257, c[2] * 257, 0)
v2 = struct.pack(">HH", 2, len(cols))
for c, n in zip(cols, names):
v2 += struct.pack(">HHHHH", 0, c[0] * 257, c[1] * 257, c[2] * 257, 0)
v2 += struct.pack(">I", len(n) + 1) + n.encode("utf-16-be") + b"\x00\x00"
open(path, "wb").write(v1 + v2)
def act(path, cols):
d = bytearray()
for c in cols:
d += bytes(c)
d += bytes(768 - len(d))
open(path, "wb").write(bytes(d))
def swatches(path, cols, sw=32, h=64):
write_png(path, [[c for c in cols for _ in range(sw)]] * h)
# 3x5 glyphs, enough for indices and hex codes. No font library here.
GLYPHS = {
"0": "111101101101111", "1": "010110010010111", "2": "111001111100111",
"3": "111001111001111", "4": "101101111001001", "5": "111100111001111",
"6": "111100111101111", "7": "111001001001001", "8": "111101111101111",
"9": "111101111001111", "A": "111101111101101", "B": "110101110101110",
"C": "111100100100111", "D": "110101101101110", "E": "111100111100111",
"F": "111100111100100", "#": "101111101111101", " ": "000000000000000",
"-": "000000111000000",
}
def _text(px, x, y, s, col, scale=2):
for ch in s.upper():
g = GLYPHS.get(ch, GLYPHS[" "])
for gy in range(5):
for gx in range(3):
if g[gy*3 + gx] == "1":
for sy in range(scale):
for sx in range(scale):
yy, xx = y + gy*scale + sy, x + gx*scale + sx
if 0 <= yy < len(px) and 0 <= xx < len(px[0]):
px[yy][xx] = col
x += 4 * scale
def card(path, cols, names, idx, scale=2):
"""Labelled reference: swatch, palette index, hex code, role name."""
rowh, sww, pad = 7*scale + 8, 22*scale, 6
W = sww + pad*2 + 4*scale*22
H = rowh*len(cols) + pad*2
bg, fg = (26, 26, 26), (255, 255, 219)
px = [[bg]*W for _ in range(H)]
for i, (c, n, ix) in enumerate(zip(cols, names, idx)):
y = pad + i*rowh
for yy in range(y, y + 7*scale):
for xx in range(pad, pad + sww):
px[yy][xx] = c
label = f"{ix:02d} #{c[0]:02X}{c[1]:02X}{c[2]:02X}"
_text(px, pad + sww + pad, y + scale, label, fg, scale)
write_png(path, px)
def emit(stem, idx, title):
cols = [PALETTE[i] for i in idx]
names = [NAMES[i] for i in idx]
p = lambda ext: os.path.normpath(os.path.join(OUT, f"{stem}.{ext}"))
gpl(p("gpl"), cols, names, title)
ase(p("ase"), cols, names)
aco(p("aco"), cols, names)
act(p("act"), cols)
swatches(p("png"), cols)
card(os.path.normpath(os.path.join(OUT, f"{stem}-card.png")), cols, names, idx)
print(f"{stem}: {len(cols)} colours -> .gpl .ase .aco .act .png + -card.png")
if __name__ == "__main__":
emit("kissaten-full", list(range(16)), "Kissaten Yugure (full)")
emit("kissaten-portrait", PORTRAIT_IDX, "Kissaten Yugure (portrait)")
# exact 1px-per-entry image, for generators that take a palette image
write_png(os.path.normpath(os.path.join(OUT, "palette.png")), [list(PALETTE)])
print("palette.png: 16x1 exact")
+117
View File
@@ -0,0 +1,117 @@
#!/usr/bin/env python3
"""Dependency-free PNG read/write plus helpers for the Kissaten art pipeline.
No Pillow on this machine, so decode/encode are hand-rolled. Supports the
colour types PixelLab and friends actually emit: 2 (RGB), 6 (RGBA), 3
(palette), 0 (grey), 4 (grey+alpha), 8 bits per channel.
"""
import zlib, struct, binascii, sys
def read_png(path):
"""-> (w, h, pixels) where pixels is a list of rows of (r,g,b,a) tuples."""
d = open(path, "rb").read()
assert d[:8] == b"\x89PNG\r\n\x1a\x0a", f"{path}: not a PNG"
pos, idat, plte, trns = 8, b"", None, None
w = h = depth = ctype = None
while pos < len(d):
ln = struct.unpack(">I", d[pos:pos + 4])[0]
typ = d[pos + 4:pos + 8]
body = d[pos + 8:pos + 8 + ln]
if typ == b"IHDR":
w, h, depth, ctype = struct.unpack(">IIBB", body[:10])
elif typ == b"PLTE":
plte = body
elif typ == b"tRNS":
trns = body
elif typ == b"IDAT":
idat += body
elif typ == b"IEND":
break
pos += 12 + ln
assert depth == 8, f"{path}: only 8-bit channels supported (got {depth})"
nch = {0: 1, 2: 3, 3: 1, 4: 2, 6: 4}[ctype]
raw = zlib.decompress(idat)
stride = w * nch
out, prev = [], bytearray(stride)
p = 0
for _ in range(h):
f = raw[p]; p += 1
line = bytearray(raw[p:p + stride]); p += stride
for i in range(stride):
a = line[i - nch] if i >= nch else 0
b = prev[i]
c = prev[i - nch] if i >= nch else 0
if f == 1: line[i] = (line[i] + a) & 255
elif f == 2: line[i] = (line[i] + b) & 255
elif f == 3: line[i] = (line[i] + (a + b) // 2) & 255
elif f == 4:
pa, pb, pc = abs(b - c), abs(a - c), abs(a + b - 2 * c)
pr = a if (pa <= pb and pa <= pc) else (b if pb <= pc else c)
line[i] = (line[i] + pr) & 255
prev = line
row = []
for x in range(w):
v = line[x * nch:(x + 1) * nch]
if ctype == 2: row.append((v[0], v[1], v[2], 255))
elif ctype == 6: row.append((v[0], v[1], v[2], v[3]))
elif ctype == 0: row.append((v[0], v[0], v[0], 255))
elif ctype == 4: row.append((v[0], v[0], v[0], v[1]))
else:
i = v[0]
a = trns[i] if (trns and i < len(trns)) else 255
row.append((plte[i * 3], plte[i * 3 + 1], plte[i * 3 + 2], a))
out.append(row)
return w, h, out
def write_png(path, rows):
h, w = len(rows), len(rows[0])
raw = b"".join(b"\x00" + bytes(c for px in r for c in px[:3]) for r in rows)
def chunk(t, b):
c = t + b
return struct.pack(">I", len(b)) + c + struct.pack(">I", binascii.crc32(c) & 0xffffffff)
open(path, "wb").write(
b"\x89PNG\r\n\x1a\n"
+ chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0))
+ chunk(b"IDAT", zlib.compress(raw, 9))
+ chunk(b"IEND", b""))
def zoom(rows, n):
"""Nearest-neighbour upscale, so pixels stay square and countable."""
return [[px for px in r for _ in range(n)] for r in rows for _ in range(n)]
# The game palette, index-aligned with g_Palette in kissaten.c
PALETTE = [
(0x00, 0x00, 0x00), (0x24, 0x24, 0x24), (0x49, 0x24, 0x24), (0x92, 0x49, 0x24),
(0xB6, 0x6D, 0x49), (0xFF, 0xB6, 0x49), (0xFF, 0xDB, 0x92), (0x6D, 0x49, 0x49),
(0x49, 0x24, 0x92), (0x92, 0x49, 0xB6), (0xDB, 0x6D, 0x6D), (0xFF, 0x92, 0x49),
(0x6D, 0x24, 0x00), (0x49, 0x6D, 0xB6), (0xFF, 0xB6, 0x92), (0xFF, 0xFF, 0xDB),
]
SKY = {8, 9, 10, 11}
if __name__ == "__main__":
for path in sys.argv[1:]:
w, h, rows = read_png(path)
flat = [px for r in rows for px in r]
cols = {}
for px in flat:
cols[px[:3]] = cols.get(px[:3], 0) + 1
alphas = sorted({px[3] for px in flat})
exact = sum(n for c, n in cols.items() if c in PALETTE)
print(f"\n{path} {w}x{h}")
print(f" unique colours : {len(cols)}")
print(f" alpha values : {alphas if len(alphas) < 6 else str(alphas[:5]) + '...'}")
print(f" already in our palette: {len(cols) - len([c for c in cols if c not in PALETTE])}"
f" of {len(cols)} ({100*exact//len(flat)}% of pixels)")
print(" top 8 colours:")
for c, n in sorted(cols.items(), key=lambda kv: -kv[1])[:8]:
tag = " <- in palette" if c in PALETTE else ""
print(f" #{c[0]:02X}{c[1]:02X}{c[2]:02X} {n:5d} px {100*n//len(flat):3d}%{tag}")
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""SVG portrait -> 48x48 PNG in the Kissaten palette.
python3 tools/svg2portrait.py art/aki-neutral.svg [more.svg ...]
Why this route beats exporting a bitmap and resizing:
Magnific's SVG export is flat-shaded vector — hundreds of paths, each a solid
fill, with only a handful of gradients. Rendering that at an exact multiple of
48 and then taking the *most common* colour in each block is a true pixelate:
no blending, no half-pixels, no anti-aliased edges. Bicubic resizing of a
bitmap averages instead, which turns every flat region into a gradient and
makes the later palette reduction speckle badly.
Requires qlmanage (macOS built-in) to rasterise. No third-party deps.
"""
import subprocess, sys, os, tempfile
from collections import Counter
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from pixtool import read_png, write_png, zoom, PALETTE
SIZE = 48
SCALE = 16 # render at SIZE*SCALE, then block-reduce
PORTRAIT_IDX = [1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15] # no sky, no index 0
PP = [PALETTE[i] for i in PORTRAIT_IDX]
def nearest(c):
r, g, b = c[:3]
# green weighted heaviest: keeps eyes and mouth from dissolving into skin
return min(PP, key=lambda p: 3*(p[0]-r)**2 + 6*(p[1]-g)**2 + (p[2]-b)**2)
def rasterise(svg, px):
"""SVG -> PNG at px wide, via Quick Look. Returns decoded pixels."""
with tempfile.TemporaryDirectory() as td:
subprocess.run(["qlmanage", "-t", "-s", str(px), "-o", td, svg],
capture_output=True, check=True)
out = os.path.join(td, os.path.basename(svg) + ".png")
if not os.path.exists(out):
raise SystemExit(f"qlmanage produced nothing for {svg}")
return read_png(out)
def mode_reduce(rows, n):
"""Each n*n block collapses to its most common colour — a real pixelate."""
return [[Counter(rows[by*n + y][bx*n + x][:3]
for y in range(n) for x in range(n)).most_common(1)[0][0]
for bx in range(SIZE)] for by in range(SIZE)]
def convert(svg):
w, h, big = rasterise(svg, SIZE * SCALE)
if w != SIZE * SCALE:
raise SystemExit(f"{svg}: expected {SIZE*SCALE}px render, got {w}")
small = mode_reduce(big, SCALE)
quant = [[nearest(p) for p in r] for r in small]
stem = os.path.splitext(svg)[0]
write_png(f"{stem}-{SIZE}.png", [[p + (255,) for p in r] for r in quant])
write_png(f"{stem}-{SIZE}-x6.png",
zoom([[p + (255,) for p in r] for r in quant], 6))
print(f"{os.path.basename(svg)}: {len({p for r in small for p in r})} colours "
f"after pixelate -> {len({p for r in quant for p in r})} after palette")
print(f" wrote {stem}-{SIZE}.png and {stem}-{SIZE}-x6.png (preview)")
if __name__ == "__main__":
if len(sys.argv) < 2:
raise SystemExit(__doc__)
for f in sys.argv[1:]:
convert(f)
+137 -16
View File
@@ -7,7 +7,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
An MSX2+ maze game written in C against the **MSXgl** library (`../../engine`), built
into a 256KB **Konami SCC mapper ROM**. SCREEN 8 (G7, 256 colors), 32×32-pixel tiles,
player always centered with smooth easing scroll. The whole game is one translation unit
(`mazegame.c`) plus ~24 generated data files holding bitmap assets in banked ROM segments.
(`mazegame.c`) plus ~28 generated data files holding bitmap assets and per-screen music in
banked ROM segments. Each screen has its own looping **MSX-Music (FM/OPLL)** track, with
**ayFX** sound effects layered on the PSG.
## Build & run
@@ -33,7 +35,10 @@ at runtime with `SET_BANK_SEGMENT(2, n)` / `GET_BANK_SEGMENT(2)` (from `rom_mapp
The `_b2` suffix on the generated files means "bank 2". Always save and restore the
previous segment around a bank switch — the existing code does this everywhere.
Segment allocation (each file is `const u8 g_*[8192]` filling one 8KB segment exactly):
Segment allocation. The build auto-discovers any `mazegame_s{N}_b2.c` (N up to 31) and
pins its data to the start of segment N at the bank-2 window; the file's one `const u8 g_*`
array need not fill the whole 8KB (bitmap assets do; music tracks are smaller and the rest
of the segment is zero-padded).
| Segments | File(s) | Contents |
|----------|--------------------------|---------------------------------------------------|
@@ -43,10 +48,14 @@ Segment allocation (each file is `const u8 g_*[8192]` filling one 8KB segment ex
| 17 | `mazegame_s17_b2.c` | Sprite frames: east + west, frames 03 |
| 18 | `mazegame_s18_b2.c` | All 8 tile bitmaps (1024 B each, 32×32) |
| 1925 | `mazegame_s19..25_b2.c` | Hint screen image |
| 26 | `mazegame_s26_b2.c` | Music: title screen (lVGM, FM) |
| 27 | `mazegame_s27_b2.c` | Music: hints screen (lVGM, FM) |
| 28 | `mazegame_s28_b2.c` | Music: gameplay/maze (lVGM, FM) |
| 29 | `mazegame_s29_b2.c` | Music: victory screen (lVGM, FM) |
A 256×212 SCREEN 8 image is 53,248 bytes = 7 segments (last one zero-padded). Sprite
frames are 24×24 = 576 bytes each, addressed as `0x8000 + (localDir*4 + frame) * 576`.
Tiles are addressed as `0x8000 + index*1024`.
Segments 3031 are free. A 256×212 SCREEN 8 image is 53,248 bytes = 7 segments (last one
zero-padded). Sprite frames are 24×24 = 576 bytes each, addressed as
`0x8000 + (localDir*4 + frame) * 576`. Tiles are addressed as `0x8000 + index*1024`.
### Asset pipeline (PNG → C, run manually, then rebuild)
@@ -62,6 +71,70 @@ All converters encode pixels as SCREEN 8 **GRB** (bits 75 = G3, 42 = R3, 1
`to_grb()`. Any new color/asset code must use the same encoding. Sprite segments (16/17)
have no checked-in converter — edit them as data directly.
### Music & sound — per-screen lVGM (MSX-Music / FM) + ayFX (PSG)
Music and sound effects use **different chips**, so they mix with zero contention: looping
**MSX-Music (FM/OPLL)** tracks on the YM2413, and **ayFX** sound effects on the PSG.
Each screen has its own looping track played by MSXgl's `vgm/lvgm_player`. The tracks are
**lVGM** (compact VGM) files in `music/`, all **MSX-Music (YM2413 / OPLL / FM)** — so FM must
exist: built into most MSX2+ machines, and `project_config.js` sets `EmulMSXMusic = true`
so `./build.sh run` gives openMSX the FM-PAC (`-ext fmpac`). The PSG carries no music.
- **Source → ROM:** the `.lvgm` files were embedded as the `mazegame_s26..29_b2.c` data
files (one track per segment; each is < 8KB so it never crosses a segment boundary). To
regenerate after replacing a `.lvgm`, re-run the small embed step (bin2c-style: dump the
bytes as `const u8 g_Mus*[N]` in the matching `s{26..29}_b2.c`). See
`~/Repositories/msx-music-generator` (the generator + its `msxgl-example/README.md`).
- **Decode runs in the VBlank interrupt (this is the key design point).** `MusicVBlank()` is
installed as the `H_TIMI` hook (`BIOS_SetHookCallback`) and each VBlank calls `LVGM_Decode()`
(music) + `ayFX_Update()` + `PSG_Apply()` (SFX) — so the tempo is **steady regardless of how
long a frame's game work takes**. (An earlier design decoded once per `Halt()` in the main
loop; the heavy per-step VDP work in `ScrollDraw` could overrun a VBlank, so the decode rate —
and the music — slowed *only while moving*. The ISR decouples them.) The game loops therefore
contain **no** decode calls; they are plain `Halt()`/busy-waits as before the music existed.
- **Banking — why it's race-free:** the tracks are compiled at the bank-2 link address but at
runtime `PlayTrack()` maps the current track into the **bank-3 window** (`0xA000-0xBFFF`) and
leaves it there for the whole screen. Gameplay only ever switches **bank 2** (`0x8000`) for
tiles/sprites, so bank 3 is never touched and the ISR can read `0xA000` at any moment without
a bank save/restore and without racing the main code. `PlayTrack(seg)` runs under
`DisableInterrupt()`/`EnableInterrupt()` (mute the old track's hanging note → map bank 3 →
`LVGM_Play(0xA000, TRUE)`) so `MusicVBlank` can't decode mid-switch. `MSXMusic_Initialize()`
and the hook install run once at the top of `main()`; `MusicVBlank` is a no-op until a track
is playing (`LVGM_IsPlaying()`).
- **Config:** `LibModules` adds `psg`, `msx-music`, `vgm/lvgm_player`, `ayfx/ayfx_player`;
`msxgl_config.h` sets `LVGM_USE_PSG`/`LVGM_USE_MSXMUSIC` on (rest off), `PSG_ACCESS =
PSG_INDIRECT` and `AYFX_BUFFER = AYFX_BUFFER_DEFAULT` (ayFX writes the PSG buffer that
`PSG_Apply` flushes), and `PSG_USE_EXTRA = TRUE`.
- The tracks are authored at **60 Hz**; on a 50 Hz (PAL) machine they play a constant ~17 %
slower (the decode rate is the display refresh). That's uniform, not movement-specific.
**Sound effects (ayFX, PSG).** An 8-effect ayFX bank in `sfx/sfx_bank.afb` (with a descriptive
`sfx_bank.txt`), embedded as `sfx_bank.h` (`g_SFXBank[]`) in **fixed main ROM** so the interrupt
reads it with no bank switching. Trigger with the `PlaySfx(id, prio)` helper; ids and priorities
(0 = highest, never interrupted; footsteps lowest so any UI/finish sound cuts over them):
| id | effect | trigger | prio |
|----|--------|---------|------|
| 0-2 | cat paws (random) | each move | lowest |
| 4 | fanfare | landing on the exit | highest |
| 5 | map unfold | press **M** (minimap) | mid |
| 6 | laugh | press **C** (cheat) | mid |
| 7 | ta-daa | advancing title / hint / finish screen | high |
(id 3 is unused.) Two interrupt-safety rules make this work — **break either and you get noise
and phantom input**:
- **All joystick reads go through `ReadJoy()`**, which wraps `Joystick_Read` in
`DisableInterrupt()`/`EnableInterrupt()`. `Joystick_Read` polls the PSG directly (R#14/R#15);
the ISR's `PSG_Apply` also touches the PSG, so an un-guarded read gets corrupted *and* can
misdirect a port-select write into a sound register. (`Keyboard_Read` uses the PPI, not the
PSG, so it needs no guard.) `PlaySfx` and `PlayTrack` likewise run under DI/EI.
- **The PSG mixer is muted at startup** (`PSG_SetMixer(0); PSG_Apply();` before the hook is
installed). The zero-initialized PSG buffer has the mixer at `0` = *all channels enabled*
(active-low), which `PSG_Apply` would drive as noise; ayFX re-enables its own channel when it
plays an effect. `ayFX_InitBank`/`SetChannel(PSG_CHANNEL_A)`/`SetMode(FIXED)` run once in `main`.
- The boot **logo animation is intentionally silent** (no track).
### VRAM layout (off-screen caches above the visible 256×212)
`mazegame.c` reserves off-screen VRAM rows in the 128KB space and documents exact
@@ -72,30 +145,78 @@ coordinates in the constants block — keep these regions non-overlapping when c
Rendering uses VDP commands (HMMV/HMMM, enabled via `VDP_USE_COMMAND` in `msxgl_config.h`)
to blit from these caches rather than re-drawing per frame.
### Scrolling model — VRAM torus (why there is no per-move flash)
The visible page is treated as a **256×256 VRAM torus**. Two globals, `g_BaseX`/`g_BaseY`,
hold the *resting* hardware scroll offset (R#26/27 and R#23); both are always a multiple of
64 and cycle through `{0, 64, 128, 192}`. A screen pixel `(sx, sy)` maps to VRAM
`((sx+g_BaseX)&255, (sy+g_BaseY)&255)`.
A move (`ScrollDraw`) animates the scroll register from `base` to `base ± 64` over the 8
ease steps, rendering the two newly-exposed tile-rows/cols into the VRAM cells that scroll
off the *opposite* edge, then simply keeps the new offset as the resting base. **Nothing is
bulk-shifted or blanked afterwards** — the old content is already at the correct torus
position, so there is no offset reset, no bulk HMMM, no strip paste, and no display blank.
(The earlier "canonicalize after every move" design did all of those behind a
`VDP_EnableDisplay(FALSE)`, which was the visible per-move flash.)
Consequences to respect when editing:
- **All gameplay VRAM drawing must go through the base.** `DrawTileData`/`DrawTile` add it
to their HMMM/HMMV destination; the `WriteVV` helper does it for per-row CPU writes
(sprite, compositors, cat-sense arrow) and **splits a run at the 256-column wrap**. Tiles
are 32-aligned and the base is a multiple of 64, so tiles never straddle; the 24px sprite
can straddle mid-animation, which is why `WriteVV` exists.
- Fill targets in `ScrollDraw` are **base-relative** but the fill *timing* (which animation
step is safe to write each row/col) is **base-invariant**; at `base = 0` every expression
reduces exactly to the pre-torus code, which is the easy case to reason about.
- **Strip fills must respect BOTH axis bases, not just the scroll axis.** The bulk HMMM fills
handle the scroll-axis base in their destination, and the orthogonal axis as follows:
vertical strips are **pre-rotated by `g_BaseX` in staging** (`PreRenderStrip`), so the
full-width row copies stay at destination x=0; horizontal column fills go through
`FillStripColumns`, which **splits the 224-row copy at the 256-row wrap** for `g_BaseY`.
(Omitting either writes the new edge 2/4/6 tiles rotated whenever the other axis' base is
non-zero — the maze *looks* wrong while collision stays right: phantom walls/passages.)
- Vertical has only 32px of slack in the 256-row torus, so one new tile-row is pre-filled
(off-screen slack) and the other is deferred until it scrolls off; horizontal fills
progressively as columns wrap. This is the delicate part — verify long straight runs in
each direction (the base crosses its wrap on the 4th move) and the sprite at screen edges.
- Anything that draws at **absolute** VRAM coords must zero the scroll offset while shown and
restore `g_BaseX`/`g_BaseY` after — the minimap overlay does this.
- `main()` game setup resets `g_BaseX = g_BaseY = 0` and the offset registers before the
first `DrawFull`.
### Game flow (`main()`)
`ShowLogoAnimation()` (SCREEN 5 fade) → loop of: `TitleScreen()` (ordered dissolve from
opening to opening+titles using the banked images) → `srand(*0xFC9E)` seed from the BIOS
`ShowLogoAnimation()` (SCREEN 5 fade, silent) → loop of: `TitleScreen()` (ordered dissolve
from opening to opening+titles using the banked images) → `srand(*0xFC9E)` seed from the BIOS
jiffy clock → `GenerateMaze()` (iterative DFS, `g_RW`/`g_DW` wall arrays) → `ShowHints()`
→ set up SCREEN 8, preload tiles/minimap into VRAM → inner movement loop.
→ set up SCREEN 8, preload tiles/minimap into VRAM → inner movement loop`ShowWinScreen()`
on reaching the exit. Each of these screens starts its own looping track with `PlayTrack()`
(see "Music" above); `MSXMusic_Initialize()` runs once before the logo.
Movement is an 8-step ease-in/out scroll (`g_AO` cumulative offsets summing to 64px = two
tiles) implemented with VDP register scroll + strip rendering; input (keyboard + joystick,
sampled at the last animation frame) chains directly into the next move if a direction is
still held. The player is a 24×24 **software** sprite (`DrawSpriteAt`) drawn into VRAM —
hardware sprites are disabled. Hold **M** for the minimap overlay.
tiles) implemented as a **VRAM torus** (see "Scrolling model" above) — VDP register scroll
plus edge-strip rendering, with no per-move blank/flash. Input (keyboard + joystick, sampled
at the last animation frame) chains directly into the next move if a direction is still
held. The player is a 24×24 **software** sprite (`DrawSpriteAt`) drawn into VRAM — hardware
sprites are disabled. Special keys in the movement loop: hold **M** (or joystick button A)
for the minimap overlay; hold **S** for the cat-sense arrow pointing toward the exit;
**C** (cheat) teleports to an open neighbour of the exit — one move short, so the final
step (and the win) is still the player's.
### MSXgl configuration
`msxgl_config.h` is the per-project feature switch for the dynamically-compiled library:
SCREEN 5 (G4) and SCREEN 8 (G7) modes, VDP commands, 16×16 sprites, keyboard+joystick
input. Enabling a library feature the game starts using generally requires flipping the
matching `*_USE_*` define here.
input, and the audio stack (lVGM/PSG/MSX-Music replayer + ayFX — see "Music & sound").
Enabling a library feature the game starts using generally requires flipping the matching
`*_USE_*` define here **and** adding the module to `LibModules` in `project_config.js`.
## Conventions in this codebase
- Globals use terse `g_XX` names (documented inline at the top of `mazegame.c`); section
banners are `//==== … ====`. Match that style.
- `out/` is generated build output (`.asm/.rel/.lst/.sym/.map/.rom`) — never edit by hand.
- This directory is **not** a git repository, and MSXgl itself was patched for macOS
(see `../../SETUP_MACOS.md`); those edits are untracked, so don't assume version control.
- The repo root is `../../` (the whole `MSXgl-1.4.1` tree, pushing to the `mazegame` remote);
work happens directly on `main`. MSXgl itself was patched for macOS (see
`../../SETUP_MACOS.md`); those engine edits are untracked, so don't assume they're versioned.
Binary file not shown.
+165 -11
View File
@@ -7,6 +7,12 @@
//-----------------------------------------------------------------------------
#include "msxgl.h"
#include "rom_mapper.h"
#include "bios_hook.h"
#include "psg.h"
#include "msx-music.h"
#include "vgm/lvgm_player.h"
#include "ayfx/ayfx_player.h"
#include "sfx_bank.h" // g_SFXBank[] — ayFX sound-effect bank (fixed main ROM)
#include <stdlib.h>
//=============================================================================
@@ -96,6 +102,85 @@ u8 g_VSeed; // per-game seed for tile variant hash
u8 g_BaseX; // resting horizontal scroll offset (mult of 64)
u8 g_BaseY; // resting vertical scroll offset (mult of 64)
//=============================================================================
// MUSIC (lVGM, MSX-Music / OPLL) — one looping track per screen, in banked ROM
//=============================================================================
// Each track fills the start of its own ROM segment (the `_b2` files are compiled
// at the bank-2 link address, but at runtime the track is mapped into the *bank-3*
// window at 0xA000-0xBFFF and left there for the whole screen). Gameplay only ever
// switches bank 2 (0x8000) for tiles/sprites, so bank 3 is never disturbed and the
// track stays put. Decoding runs in the H_TIMI VBlank interrupt (MusicVBlank), so
// the music tempo is rock-steady no matter how long a frame's game work takes — the
// scroll animation can overrun a VBlank without dragging the music down with it.
// Because the ISR reads only from bank 3, it never races the main code's bank-2
// switching, and needs no bank save/restore. Each track is < 8KB (one segment).
#define MUS_SEG_TITLE 26 // mazegame_s26_b2.c (g_MusTitle)
#define MUS_SEG_HINTS 27 // mazegame_s27_b2.c (g_MusHints)
#define MUS_SEG_MAZE 28 // mazegame_s28_b2.c (g_MusMaze)
#define MUS_SEG_WIN 29 // mazegame_s29_b2.c (g_MusWin)
#define MUS_WINDOW ((const u8*)0xA000) // bank-3 window the track is mapped into
// Sound effects (ayFX on the PSG, mixed over the FM music). Bank ids (see
// sfx/sfx_bank.txt): 0-2 cat paws (movement, chosen at random), 4 fanfare (reaching
// the exit), 5 map unfold (M / minimap), 6 laugh (C / cheat), 7 ta-daa (advancing a
// screen). Priority: 0 = highest (never interrupted); higher number = interrupted
// more easily. Footsteps are lowest so any UI/finish sound cuts over them.
#define SFX_FINISH 4
#define SFX_MINIMAP 5
#define SFX_CHEAT 6
#define SFX_SCREEN 7
#define SFX_PRIO_FINISH 0
#define SFX_PRIO_SCREEN 1
#define SFX_PRIO_ACTION 2 // M / C
#define SFX_PRIO_MOVE 4 // footsteps
// H_TIMI (VBlank) hook: advance the music one frame (if a track is playing) and the
// current sound effect one frame, then flush the PSG buffer. Music reads from the
// bank-3 window; SFX reads the bank in fixed main ROM — neither needs bank switching.
void MusicVBlank(void)
{
if (LVGM_IsPlaying())
LVGM_Decode();
ayFX_Update();
PSG_Apply();
}
// Trigger a bank sound effect. ayFX state is set up here (main context) while the
// VBlank hook reads it, so disable interrupts across the call.
static void PlaySfx(u8 id, u8 prio)
{
DisableInterrupt();
ayFX_PlayBank(id, prio);
EnableInterrupt();
}
// Read joystick 1 with interrupts disabled. Joystick_Read drives the PSG directly
// (selects/writes R#15, reads R#14); the VBlank hook's PSG_Apply also touches the
// PSG. If the hook fired mid-read it would corrupt the returned data AND could
// misdirect a port-select write into a sound register (audible noise). All joystick
// reads in the game must go through this wrapper. (Keyboard_Read uses the PPI, not
// the PSG, so it is race-free and needs no protection.)
static u8 ReadJoy(void)
{
DisableInterrupt();
u8 j = Joystick_Read(JOY_PORT_1);
EnableInterrupt();
return j;
}
// Switch to a looping track: silence the previous track's lingering note (key-off all
// 9 FM channels), map the new track into bank 3, and start it. Interrupts are disabled
// across the switch so MusicVBlank can't decode from a half-updated pointer or segment.
static void PlayTrack(u8 seg)
{
DisableInterrupt();
MSXMusic_Mute();
SET_BANK_SEGMENT(3, seg);
LVGM_Play(MUS_WINDOW, TRUE);
EnableInterrupt();
}
//=============================================================================
// MAZE GENERATION (iterative DFS)
//=============================================================================
@@ -541,7 +626,14 @@ static void PreRenderStrip(i8 dx, i8 dy)
for (u8 ty = ya; ty <= yb; ty++) {
for (u8 tx = xa; tx <= xb; tx++) {
u8 srcX = GetTileSrcX(ox + (i8)tx, oy + (i8)ty);
u16 stx = (u16)(tx - xa) * TILE_W;
// Vertical strips are pasted by full-width HMMMs at VRAM x=0, so the
// staging row must already be rotated by the horizontal torus base:
// screen column 0 lives at VRAM column g_BaseX. Tiles are 32-aligned
// and the base is a multiple of 64, so no tile straddles the wrap.
// Horizontal strips are pasted column-by-column with an explicit
// destination x, so their staging stays unrotated (x = 0..63).
u16 stx = (dy != 0) ? (u8)(tx * TILE_W + g_BaseX)
: (u16)(tx - xa) * TILE_W;
u16 sty = STAGING_Y + (u16)(ty - ya) * TILE_W;
VDP_CommandHMMM(srcX, TILE_CACHE_Y, stx, sty, TILE_W, TILE_W);
VDP_CommandWait();
@@ -549,6 +641,22 @@ static void PreRenderStrip(i8 dx, i8 dy)
}
}
// Paste a w-wide, 224-tall column of the horizontal staging strip into the
// visible torus. Screen row 0 lives at VRAM row g_BaseY, so the copy is split
// at the 256-row wrap when the vertical base is non-zero (base 64/128/192 →
// 192/128/64 rows before the wrap, remainder continuing at VRAM row 0).
static void FillStripColumns(u8 srcX, u8 dstX, u8 w)
{
u8 by = g_BaseY;
if (by == 0) {
VDP_CommandHMMM(srcX, STAGING_Y, dstX, 0, w, 224);
} else {
u16 h1 = 256u - by; // rows before the torus wrap
VDP_CommandHMMM(srcX, STAGING_Y, dstX, by, w, h1);
VDP_CommandHMMM(srcX, STAGING_Y + h1, dstX, 0, w, (u16)(224u - h1));
}
}
// Wrap-around (torus) scroll: animate the hardware scroll register from the
// resting base to base±64, rendering the newly exposed edge into the VRAM cells
// that scroll off the opposite side. Nothing is bulk-shifted or blanked
@@ -574,7 +682,9 @@ u8 ScrollDraw(i8 dx, i8 dy)
// tile-rows exactly one is already off-screen (in the slack just past the
// viewport) and can be filled now; the other overlaps on-screen VRAM and is
// deferred into the loop. At base=0 these reduce to the original rows 224/0
// (dy=1) and 224/192 (dy=-1). Staging holds the new tiles at rows 0 / +32.
// (dy=1) and 224/192 (dy=-1). Staging holds the new tiles at rows 0 / +32,
// already rotated by g_BaseX (see PreRenderStrip), so these full-width
// copies stay at destination x=0 regardless of the horizontal base.
if (dy == 1) {
// New bottom tile-row 5 → VRAM row (base+224) (off-screen slack). Staging row 0.
VDP_CommandHMMM(0, STAGING_Y, 0, (u8)(base + 224u), 256, 32); VDP_CommandWait();
@@ -633,10 +743,10 @@ u8 ScrollDraw(i8 dx, i8 dy)
u8 cur = g_AO[i];
u8 w = cur - prev;
if (dx == 1) {
VDP_CommandHMMM(prev, STAGING_Y, (u8)(base + prev), 0, w, 224);
FillStripColumns(prev, (u8)(base + prev), w);
} else {
u8 fs = (u8)(64u - cur);
VDP_CommandHMMM(fs, STAGING_Y, (u8)(base + 192u + fs), 0, w, 224);
FillStripColumns(fs, (u8)(base + 192u + fs), w);
}
u8 new_vx = (u8)((i16)SPR_DRAW_X + (i16)dx * (i16)cur);
g_SprFrame = (g_SprFrame + 1) & 3;
@@ -650,7 +760,7 @@ u8 ScrollDraw(i8 dx, i8 dy)
// Read keyboard and joystick at the final animation frame (for chaining).
u8 row8 = Keyboard_Read(8);
g_Joy = Joystick_Read(JOY_PORT_1);
g_Joy = ReadJoy();
// Commit the new resting base. The newly exposed edge is already in VRAM (from
// the fills), the old content is already at the correct torus position, and the
@@ -820,7 +930,7 @@ void WriteImgRow(u8 first_seg, u8 y)
// Returns 1 if any key (rows 0-8) or any joystick direction/button is active.
static u8 AnyInput(void)
{
u8 joy = Joystick_Read(JOY_PORT_1);
u8 joy = ReadJoy();
if (JOY_GET_DIR(joy) != 0 ||
IS_JOY_PRESSED(joy, JOY_INPUT_TRIGGER_A) ||
IS_JOY_PRESSED(joy, JOY_INPUT_TRIGGER_B)) return 1;
@@ -834,6 +944,7 @@ static void WaitKeySequence(void)
{
while (AnyInput());
while (!AnyInput());
PlaySfx(SFX_SCREEN, SFX_PRIO_SCREEN); // ta-daa on the advancing keypress
while (AnyInput());
}
@@ -843,9 +954,13 @@ static void WaitKeySequence(void)
static void ShowHints(void)
{
VDP_EnableDisplay(FALSE);
// Drawn at absolute VRAM coords — clear any leftover hardware scroll offset.
VDP_SetVerticalOffset(0);
VDP_SetHorizontalOffset(0);
for (u8 y = 0; y < IMG_ROWS; y++)
WriteImgRow(IMG_SEG_HINTS, y);
VDP_EnableDisplay(TRUE);
PlayTrack(MUS_SEG_HINTS);
WaitKeySequence();
}
@@ -857,11 +972,18 @@ void TitleScreen(void)
VDP_EnableDisplay(FALSE);
VDP_ClearVRAM();
// This image is drawn at absolute VRAM coords, so clear any hardware scroll
// offset left over from a previous game (gameplay rests at g_BaseX/g_BaseY in
// {0,64,128,192}); without this the returning title screen appears shifted.
VDP_SetVerticalOffset(0);
VDP_SetHorizontalOffset(0);
// Stream image A (no title) to VRAM display area
for (u8 y = 0; y < IMG_ROWS; y++)
WriteImgRow(IMG_SEG_A, y);
VDP_EnableDisplay(TRUE);
PlayTrack(MUS_SEG_TITLE);
// Hold image A for ~2 seconds (100 VBlanks at 50Hz)
for (u8 i = 0; i < 100; i++) Halt();
@@ -877,6 +999,7 @@ void TitleScreen(void)
// Wait for any key or joystick input, then release
while (!AnyInput());
PlaySfx(SFX_SCREEN, SFX_PRIO_SCREEN); // ta-daa on the advancing keypress
while (AnyInput());
}
@@ -931,6 +1054,7 @@ static void ShowWinScreen(void)
Bios_SetCursorPosition(7, 15);
Bios_TextPrintString("Press any key to return...");
PlayTrack(MUS_SEG_WIN);
WaitKeySequence();
}
@@ -940,6 +1064,21 @@ static void ShowWinScreen(void)
void main(void)
{
// Init the MSX-Music (OPLL) chip, load the ayFX bank onto PSG channel A, then
// install the VBlank hook that advances music + SFX every frame (the hook is a
// no-op for music until a track plays, and silent for SFX until one is triggered).
MSXMusic_Initialize();
ayFX_InitBank((void*)g_SFXBank);
ayFX_SetChannel(PSG_CHANNEL_A);
ayFX_SetMode(AYFX_MODE_FIXED);
// Silence the PSG before the per-frame PSG_Apply starts: the zero-initialized
// register buffer has the mixer at 0 = all channels ENABLED, which PSG_Apply would
// drive as noise. Disable every channel; ayFX re-enables its own channel when it
// plays an effect (and drops back to volume 0 when the effect ends).
PSG_SetMixer(0);
PSG_Apply();
BIOS_SetHookCallback(H_TIMI, MusicVBlank);
ShowLogoAnimation();
while (1) {
@@ -1009,6 +1148,7 @@ void main(void)
g_MX = g_SX; g_MY = 0;
DrawFull();
VDP_EnableDisplay(TRUE);
PlayTrack(MUS_SEG_MAZE);
// Start timer: sample current jiffy clock, reset frame counter.
g_PrevTick = *(volatile u8*)0xFC9E;
@@ -1029,7 +1169,7 @@ void main(void)
do {
UpdateTimer();
row8 = Keyboard_Read(8);
g_Joy = Joystick_Read(JOY_PORT_1);
g_Joy = ReadJoy();
} while (IS_KEY_PRESSED(row8, KEY_UP) || IS_KEY_PRESSED(row8, KEY_DOWN) ||
IS_KEY_PRESSED(row8, KEY_LEFT) || IS_KEY_PRESSED(row8, KEY_RIGHT) ||
JOY_GET_DIR(g_Joy) != 0);
@@ -1041,16 +1181,27 @@ void main(void)
row3 = Keyboard_Read(3);
row4 = Keyboard_Read(4);
row5 = Keyboard_Read(5);
g_Joy = Joystick_Read(JOY_PORT_1);
g_Joy = ReadJoy();
} while (!IS_KEY_PRESSED(row8, KEY_UP) && !IS_KEY_PRESSED(row8, KEY_DOWN) &&
!IS_KEY_PRESSED(row8, KEY_LEFT) && !IS_KEY_PRESSED(row8, KEY_RIGHT) &&
!IS_KEY_PRESSED(row3, KEY_C) && !IS_KEY_PRESSED(row4, KEY_M) &&
!IS_KEY_PRESSED(row5, KEY_S) &&
JOY_GET_DIR(g_Joy) == 0 && !IS_JOY_PRESSED(g_Joy, JOY_INPUT_TRIGGER_A));
// C: teleport to exit
// C: teleport to one move away from the exit (the final step is left
// to the player). The exit (g_EX, MAZE_H-1) is a normal maze cell, so
// at least one of its up/left/right neighbours has an open passage to
// it; drop the player on the first such neighbour.
if (IS_KEY_PRESSED(row3, KEY_C)) {
g_MX = g_EX; g_MY = MAZE_H - 1;
PlaySfx(SFX_CHEAT, SFX_PRIO_ACTION);
u8 ey = MAZE_H - 1;
if (g_DW[g_EX][ey-1] == 0) { // open above -> step down
g_MX = g_EX; g_MY = ey - 1;
} else if (g_EX > 0 && g_RW[g_EX-1][ey] == 0) { // open left -> step right
g_MX = g_EX - 1; g_MY = ey;
} else { // open right -> step left
g_MX = g_EX + 1; g_MY = ey;
}
DrawFull();
row8 = 0xFF;
continue;
@@ -1058,6 +1209,7 @@ void main(void)
// M key or joystick button A: show minimap while held
if (IS_KEY_PRESSED(row4, KEY_M) || IS_JOY_PRESSED(g_Joy, JOY_INPUT_TRIGGER_A)) {
PlaySfx(SFX_MINIMAP, SFX_PRIO_ACTION);
VDP_EnableDisplay(FALSE);
// Minimap is drawn at absolute VRAM (0,0); show it with zero
// scroll offset regardless of the current torus base.
@@ -1070,7 +1222,7 @@ void main(void)
do {
Halt();
row4 = Keyboard_Read(4);
g_Joy = Joystick_Read(JOY_PORT_1);
g_Joy = ReadJoy();
} while (IS_KEY_PRESSED(row4, KEY_M) || IS_JOY_PRESSED(g_Joy, JOY_INPUT_TRIGGER_A));
// Restore game view (DrawFull includes DrawSprite). Put the
// scroll offset back to the current torus base first.
@@ -1121,10 +1273,12 @@ void main(void)
g_MX += (u8)dx;
g_MY += (u8)dy;
PlaySfx((u8)(rand() % 3), SFX_PRIO_MOVE); // random cat-paw footstep (id 0-2)
row8 = ScrollDraw(dx, dy);
// Win condition: reached exit cell
if (g_MY == MAZE_H-1 && g_MX == g_EX) {
PlaySfx(SFX_FINISH, SFX_PRIO_FINISH); // fanfare over the win screen
ShowWinScreen();
break; // back to outer loop → title screen → new maze
}
+334
View File
@@ -0,0 +1,334 @@
#include "msxgl.h"
// lVGM music track — title screen.
// Occupies the start of banked segment 26 (bank-2 window 0x8000-0x9FFF,
// via SET_BANK_SEGMENT(2, 26)). MSX-Music (YM2413/OPLL), 60 Hz, looping.
// 5186 bytes; the rest of the 8KB segment is unused padding.
// Access: LVGM_Play((const u8*)0x8000, TRUE) with segment 26 banked in.
const u8 g_MusTitle[5186] = {
0x6C, 0x56, 0x47, 0x4D, 0x06, 0x02, 0xF1, 0x16, 0x20, 0xFE, 0x26, 0x05, 0x17, 0x50, 0x27, 0x05,
0x18, 0xC0, 0x28, 0x01, 0x56, 0x00, 0x0E, 0x20, 0x10, 0xB3, 0x20, 0x19, 0x30, 0x42, 0x11, 0x59,
0x21, 0x17, 0x31, 0xB6, 0x12, 0x59, 0x22, 0x13, 0x32, 0xE4, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE6,
0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE6, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x03, 0x12,
0x02, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE6, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE6,
0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0x22, 0x05, 0x12, 0x59, 0x22, 0x13, 0x0E, 0x28, 0xE0, 0x0E,
0x20, 0xE6, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0x02, 0x20, 0x1B, 0x21,
0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x03, 0x12, 0x02, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20,
0xE6, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE6, 0x20, 0x0B, 0x10, 0x59, 0x20, 0x19, 0x21, 0x07,
0x11, 0x83, 0x21, 0x17, 0x22, 0x05, 0x12, 0x83, 0x22, 0x13, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE6,
0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0x83, 0x20, 0x19, 0x21, 0x07, 0x11,
0x22, 0x21, 0x19, 0x22, 0x03, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE6, 0x21,
0x09, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x07, 0x11, 0x83,
0x21, 0x17, 0x22, 0x05, 0x12, 0x83, 0x22, 0x13, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE6, 0x21, 0x07,
0x11, 0xCC, 0x21, 0x17, 0xE6, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x03, 0x12, 0x22, 0x22,
0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE6, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20, 0x09,
0x10, 0x02, 0x20, 0x1B, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0x22, 0x05, 0x12, 0xB3, 0x22, 0x13,
0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x05, 0xE0, 0x10, 0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19,
0xE0, 0x10, 0xFC, 0xE0, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0xE0, 0x10, 0xFF, 0xE0, 0x10, 0x02,
0x20, 0x1B, 0xE0, 0x10, 0x05, 0xE0, 0x10, 0x07, 0xE1, 0x10, 0x05, 0xE0, 0x10, 0x02, 0x21, 0x09,
0x11, 0x46, 0x21, 0x19, 0x22, 0x03, 0x12, 0x46, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10, 0xFF, 0x20,
0x19, 0x0E, 0x20, 0xE0, 0x10, 0xFC, 0xE1, 0x10, 0xFF, 0xE0, 0x10, 0x02, 0x20, 0x1B, 0xE0, 0x10,
0x05, 0xE0, 0x10, 0x07, 0xE0, 0x21, 0x09, 0x11, 0x02, 0x21, 0x19, 0xE0, 0x10, 0x05, 0xE0, 0x10,
0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC, 0xE1, 0x10, 0xFF, 0xE0, 0x10, 0x02, 0x20,
0x1B, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0x22, 0x05, 0x12, 0xB3, 0x22, 0x13, 0x0E, 0x28, 0xE0,
0x10, 0x05, 0x0E, 0x20, 0xE0, 0x10, 0x07, 0xE1, 0x10, 0x05, 0xE0, 0x10, 0x02, 0xE0, 0x10, 0xFF,
0x20, 0x19, 0xE0, 0x10, 0xFC, 0xE0, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0xE0, 0x10, 0xFF, 0xE0,
0x10, 0x02, 0x20, 0x1B, 0xE0, 0x10, 0x05, 0xE0, 0x10, 0x07, 0xE1, 0x10, 0x05, 0xE0, 0x10, 0x02,
0x21, 0x09, 0x11, 0x46, 0x21, 0x19, 0x22, 0x03, 0x12, 0x46, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10,
0xFF, 0x20, 0x19, 0x0E, 0x20, 0xE0, 0x10, 0xFC, 0xE1, 0x10, 0xFF, 0xE0, 0x10, 0x02, 0x20, 0x1B,
0xE0, 0x10, 0x05, 0xE0, 0x10, 0x07, 0xE0, 0x21, 0x09, 0x11, 0x02, 0x21, 0x19, 0xE0, 0x10, 0x05,
0xE0, 0x10, 0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC, 0xE1, 0x10, 0xFF, 0xE0, 0x20,
0x09, 0x21, 0x09, 0x11, 0x59, 0x21, 0x17, 0x22, 0x05, 0x12, 0x59, 0x22, 0x13, 0x0E, 0x30, 0xE0,
0x0E, 0x20, 0xE6, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE6, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x07,
0x11, 0x02, 0x21, 0x19, 0x22, 0x03, 0x12, 0x02, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2,
0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB, 0xE0, 0x21, 0x09, 0x11, 0xB3,
0x21, 0x17, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE1, 0x10,
0xB7, 0xE0, 0x20, 0x09, 0x10, 0x02, 0x20, 0x1B, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0x22, 0x05,
0x12, 0x59, 0x22, 0x13, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x05, 0xE0, 0x10, 0x02, 0xE0,
0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC, 0xE0, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10,
0xFF, 0xE0, 0x10, 0x02, 0x20, 0x1B, 0xE0, 0x10, 0x05, 0xE0, 0x10, 0x07, 0xE1, 0x10, 0x05, 0xE0,
0x20, 0x0B, 0x10, 0x22, 0x20, 0x1B, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x03, 0x12, 0x02,
0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x25, 0xE0, 0x10, 0x22, 0xE0, 0x10, 0x1F,
0xE0, 0x10, 0x1D, 0xE0, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10, 0x1F, 0xE0, 0x10, 0x22,
0xE0, 0x10, 0x25, 0xE0, 0x10, 0x27, 0xE1, 0x10, 0x25, 0xE0, 0x20, 0x0B, 0x10, 0x02, 0x20, 0x1B,
0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x0E,
0x20, 0xE2, 0x10, 0x05, 0xE0, 0x10, 0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC, 0xE0,
0x21, 0x09, 0x11, 0x59, 0x21, 0x19, 0xE0, 0x10, 0xFF, 0xE0, 0x10, 0x02, 0x20, 0x1B, 0xE0, 0x10,
0x05, 0xE0, 0x10, 0x07, 0xE1, 0x10, 0x05, 0xE0, 0x10, 0x02, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x19,
0x22, 0x05, 0x12, 0xB3, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0x0E, 0x20, 0xE0,
0x10, 0xFC, 0xE1, 0x10, 0xFF, 0xE0, 0x10, 0x02, 0x20, 0x1B, 0xE0, 0x10, 0x05, 0xE0, 0x10, 0x07,
0xE0, 0x21, 0x09, 0x11, 0x22, 0x21, 0x19, 0xE0, 0x10, 0x05, 0xE0, 0x10, 0x02, 0xE0, 0x10, 0xFF,
0x20, 0x19, 0xE0, 0x10, 0xFC, 0xE1, 0x10, 0xFF, 0xE0, 0x10, 0x02, 0x20, 0x1B, 0x21, 0x09, 0x21,
0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x28, 0xE0, 0x10, 0x05, 0x0E, 0x20, 0xE0, 0x10,
0x07, 0xE1, 0x10, 0x05, 0xE0, 0x10, 0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC, 0xE0,
0x21, 0x09, 0x11, 0x59, 0x21, 0x19, 0xE0, 0x10, 0xFF, 0xE0, 0x10, 0x02, 0x20, 0x1B, 0xE0, 0x10,
0x05, 0xE0, 0x10, 0x07, 0xE1, 0x10, 0x05, 0xE0, 0x20, 0x0B, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x09,
0x11, 0xB3, 0x21, 0x19, 0x22, 0x07, 0x12, 0xB3, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2,
0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB, 0xE0, 0x21, 0x09, 0x11, 0x22,
0x21, 0x19, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE1, 0x10,
0xB7, 0xE0, 0x20, 0x09, 0x10, 0x83, 0x20, 0x19, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0x22, 0x07,
0x12, 0xCC, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0,
0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE0, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0xE0, 0x10, 0x7F, 0xE0,
0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x10, 0x83, 0x21, 0x09,
0x11, 0x59, 0x21, 0x19, 0x22, 0x05, 0x12, 0x59, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x10, 0x7F, 0x0E,
0x20, 0xE0, 0x10, 0x7C, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A,
0xE0, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F,
0xE0, 0x10, 0x7C, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0x21, 0x07, 0x21, 0x17, 0x22, 0x07, 0x12,
0xCC, 0x22, 0x15, 0x0E, 0x28, 0xE0, 0x10, 0x87, 0x0E, 0x20, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87,
0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE0, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19,
0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0,
0x10, 0x83, 0x21, 0x09, 0x11, 0x59, 0x21, 0x19, 0x22, 0x05, 0x12, 0x59, 0x22, 0x17, 0x0E, 0x21,
0xE0, 0x10, 0x7F, 0x0E, 0x20, 0xE0, 0x10, 0x7C, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10,
0x87, 0xE0, 0x10, 0x8A, 0xE0, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE0, 0x10, 0x87, 0xE0, 0x10,
0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE1, 0x10, 0x7F, 0xE0, 0x20, 0x09, 0x21, 0x07, 0x11,
0x02, 0x21, 0x19, 0x22, 0x07, 0x12, 0x02, 0x22, 0x17, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE6, 0x21,
0x09, 0x11, 0x46, 0x21, 0x19, 0xE6, 0x21, 0x09, 0x11, 0x83, 0x21, 0x19, 0x22, 0x07, 0x12, 0x83,
0x22, 0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE6, 0x21, 0x09, 0x11, 0x02, 0x21, 0x19, 0xE6, 0x10,
0x59, 0x20, 0x19, 0x21, 0x09, 0x21, 0x19, 0x22, 0x07, 0x12, 0x02, 0x22, 0x17, 0x0E, 0x28, 0xE0,
0x0E, 0x20, 0xE2, 0x10, 0x5D, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0xE0, 0x21,
0x09, 0x11, 0x46, 0x21, 0x19, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x5D, 0xE0, 0x10,
0x5F, 0xE1, 0x10, 0x5D, 0xE0, 0x10, 0x59, 0x21, 0x09, 0x11, 0x83, 0x21, 0x19, 0x22, 0x07, 0x12,
0x83, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x10, 0x55, 0x0E, 0x20, 0xE0, 0x10, 0x53, 0xE1, 0x10, 0x55,
0xE0, 0x10, 0x59, 0xE0, 0x10, 0x5D, 0xE0, 0x10, 0x5F, 0xE0, 0x21, 0x09, 0x11, 0x02, 0x21, 0x19,
0xE0, 0x10, 0x5D, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0xE1, 0x10, 0x55, 0xE0,
0x10, 0x59, 0x21, 0x09, 0x21, 0x19, 0x22, 0x07, 0x12, 0x02, 0x22, 0x17, 0x0E, 0x30, 0xE0, 0x10,
0x5D, 0x0E, 0x20, 0xE0, 0x10, 0x5F, 0xE1, 0x10, 0x5D, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0,
0x10, 0x53, 0xE0, 0x21, 0x09, 0x11, 0x46, 0x21, 0x19, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0,
0x10, 0x5D, 0xE0, 0x10, 0x5F, 0xE1, 0x10, 0x5D, 0xE0, 0x20, 0x09, 0x10, 0x83, 0x20, 0x19, 0x21,
0x09, 0x11, 0x83, 0x21, 0x19, 0x22, 0x07, 0x12, 0x83, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20,
0xE2, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE0, 0x21, 0x09, 0x11,
0x02, 0x21, 0x19, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1,
0x10, 0x87, 0xE0, 0x20, 0x09, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x09, 0x21, 0x19, 0x22, 0x07, 0x12,
0x02, 0x22, 0x17, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10,
0xAE, 0xE0, 0x10, 0xAB, 0xE0, 0x21, 0x09, 0x11, 0x46, 0x21, 0x19, 0xE0, 0x10, 0xAE, 0xE0, 0x10,
0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE1, 0x10, 0xB7, 0xE0, 0x20, 0x09, 0x10, 0x02, 0x20,
0x1B, 0x21, 0x09, 0x11, 0x83, 0x21, 0x19, 0x22, 0x07, 0x12, 0x83, 0x22, 0x17, 0x0E, 0x21, 0xE0,
0x0E, 0x20, 0xE2, 0x10, 0x05, 0xE0, 0x10, 0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC,
0xE0, 0x21, 0x09, 0x11, 0x02, 0x21, 0x19, 0xE0, 0x10, 0xFF, 0xE0, 0x10, 0x02, 0x20, 0x1B, 0xE0,
0x10, 0x05, 0xE0, 0x10, 0x07, 0xE1, 0x10, 0x05, 0xE0, 0x20, 0x0B, 0x10, 0xB3, 0x20, 0x19, 0x21,
0x09, 0x11, 0x59, 0x21, 0x17, 0x22, 0x07, 0x12, 0x59, 0x22, 0x13, 0x0E, 0x30, 0xE0, 0x0E, 0x20,
0xE2, 0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB, 0xE0, 0x21, 0x07, 0x11,
0xB3, 0x21, 0x17, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE1,
0x10, 0xB7, 0xE0, 0x10, 0xB3, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x03, 0x12, 0x02, 0x22,
0x15, 0x0E, 0x21, 0xE0, 0x10, 0xAE, 0x0E, 0x20, 0xE0, 0x10, 0xAB, 0xE1, 0x10, 0xAE, 0xE0, 0x10,
0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE0, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10,
0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB, 0xE1, 0x10, 0xAE, 0xE0, 0x20, 0x09,
0x10, 0x83, 0x20, 0x19, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0x22, 0x05, 0x12, 0x59, 0x22, 0x13,
0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10,
0x7C, 0xE0, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10,
0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x20, 0x09, 0x10, 0x59, 0x20, 0x19, 0x21, 0x07,
0x11, 0x02, 0x21, 0x19, 0x22, 0x03, 0x12, 0x02, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2,
0x10, 0x5D, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0xE0, 0x21, 0x09, 0x11, 0xB3,
0x21, 0x17, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x5D, 0xE0, 0x10, 0x5F, 0xE1, 0x10,
0x5D, 0xE0, 0x20, 0x09, 0x10, 0x46, 0x20, 0x19, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x05,
0x12, 0x83, 0x22, 0x13, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x49, 0xE0, 0x10, 0x46, 0xE0,
0x10, 0x42, 0xE0, 0x10, 0x40, 0xE0, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE0, 0x10, 0x42, 0xE0,
0x10, 0x46, 0xE0, 0x10, 0x49, 0xE0, 0x10, 0x4B, 0xE1, 0x10, 0x49, 0xE0, 0x10, 0x46, 0x21, 0x07,
0x11, 0x22, 0x21, 0x19, 0x22, 0x03, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10, 0x42, 0x0E,
0x20, 0xE0, 0x10, 0x40, 0xE1, 0x10, 0x42, 0xE0, 0x10, 0x46, 0xE0, 0x10, 0x49, 0xE0, 0x10, 0x4B,
0xE0, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE0, 0x10, 0x49, 0xE0, 0x10, 0x46, 0xE0, 0x10, 0x42,
0xE0, 0x10, 0x40, 0xE1, 0x10, 0x42, 0xE0, 0x20, 0x09, 0x10, 0x59, 0x20, 0x19, 0x21, 0x07, 0x11,
0x83, 0x21, 0x17, 0x22, 0x05, 0x12, 0x83, 0x22, 0x13, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE2, 0x10,
0x5D, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0xE0, 0x21, 0x07, 0x11, 0xCC, 0x21,
0x17, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x5D, 0xE0, 0x10, 0x5F, 0xE1, 0x10, 0x5D,
0xE0, 0x10, 0x59, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x03, 0x12, 0x22, 0x22, 0x15, 0x0E,
0x21, 0xE0, 0x10, 0x55, 0x0E, 0x20, 0xE0, 0x10, 0x53, 0xE1, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0,
0x10, 0x5D, 0xE0, 0x10, 0x5F, 0xE0, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE0, 0x10, 0x5D, 0xE0,
0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0xE1, 0x10, 0x55, 0xE0, 0x20, 0x09, 0x10, 0x83,
0x20, 0x19, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0x22, 0x05, 0x12, 0xB3, 0x22, 0x13, 0x0E, 0x30,
0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE0,
0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0,
0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x20, 0x09, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x09, 0x11, 0x46,
0x21, 0x19, 0x22, 0x03, 0x12, 0x46, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xB7,
0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB, 0xE0, 0x21, 0x09, 0x11, 0x02, 0x21, 0x19,
0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE1, 0x10, 0xB7, 0xE0,
0x20, 0x09, 0x10, 0x83, 0x20, 0x19, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0x22, 0x05, 0x12, 0xB3,
0x22, 0x13, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F,
0xE0, 0x10, 0x7C, 0xE0, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x83,
0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x10, 0x83, 0x21, 0x09, 0x11, 0x46,
0x21, 0x19, 0x22, 0x03, 0x12, 0x46, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10, 0x7F, 0x0E, 0x20, 0xE0,
0x10, 0x7C, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE0, 0x21,
0x09, 0x11, 0x02, 0x21, 0x19, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10,
0x7C, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0x21, 0x09, 0x11, 0x59, 0x21, 0x17, 0x22, 0x05, 0x12,
0x59, 0x22, 0x13, 0x0E, 0x30, 0xE0, 0x10, 0x87, 0x0E, 0x20, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87,
0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE0, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17,
0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0,
0x20, 0x09, 0x10, 0x59, 0x20, 0x19, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x03, 0x12, 0x02,
0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x5D, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55,
0xE0, 0x10, 0x53, 0xE0, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x59,
0xE0, 0x10, 0x5D, 0xE0, 0x10, 0x5F, 0xE1, 0x10, 0x5D, 0xE0, 0x20, 0x09, 0x10, 0x46, 0x20, 0x19,
0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0x22, 0x05, 0x12, 0x59, 0x22, 0x13, 0x0E, 0x28, 0xE0, 0x0E,
0x20, 0xE2, 0x10, 0x49, 0xE0, 0x10, 0x46, 0xE0, 0x10, 0x42, 0xE0, 0x10, 0x40, 0xE0, 0x21, 0x07,
0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10, 0x42, 0xE0, 0x10, 0x46, 0xE0, 0x10, 0x49, 0xE0, 0x10, 0x4B,
0xE1, 0x10, 0x49, 0xE0, 0x10, 0x46, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x03, 0x12, 0x02,
0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10, 0x42, 0x0E, 0x20, 0xE0, 0x10, 0x40, 0xE1, 0x10, 0x42, 0xE0,
0x10, 0x46, 0xE0, 0x10, 0x49, 0xE0, 0x10, 0x4B, 0xE0, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE0,
0x10, 0x49, 0xE0, 0x10, 0x46, 0xE0, 0x10, 0x42, 0xE0, 0x10, 0x40, 0xE1, 0x10, 0x42, 0xE0, 0x10,
0x46, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x30, 0xE0,
0x10, 0x49, 0x0E, 0x20, 0xE0, 0x10, 0x4B, 0xE1, 0x10, 0x49, 0xE0, 0x10, 0x46, 0xE0, 0x10, 0x42,
0xE0, 0x10, 0x40, 0xE0, 0x21, 0x09, 0x11, 0x59, 0x21, 0x19, 0xE0, 0x10, 0x42, 0xE0, 0x10, 0x46,
0xE0, 0x10, 0x49, 0xE0, 0x10, 0x4B, 0xE1, 0x10, 0x49, 0xE0, 0x10, 0x46, 0x21, 0x09, 0x11, 0xB3,
0x21, 0x19, 0x22, 0x05, 0x12, 0xB3, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10, 0x42, 0x0E, 0x20, 0xE0,
0x10, 0x40, 0xE1, 0x10, 0x42, 0xE0, 0x10, 0x46, 0xE0, 0x10, 0x49, 0xE0, 0x10, 0x4B, 0xE0, 0x21,
0x09, 0x11, 0x22, 0x21, 0x19, 0xE0, 0x10, 0x49, 0xE0, 0x10, 0x46, 0xE0, 0x10, 0x42, 0xE0, 0x10,
0x40, 0xE1, 0x10, 0x42, 0xE0, 0x20, 0x09, 0x21, 0x09, 0x21, 0x19, 0x22, 0x05, 0x12, 0x22, 0x22,
0x17, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE6, 0x21, 0x09, 0x11, 0x59, 0x21, 0x19, 0xE6, 0x21, 0x09,
0x11, 0xB3, 0x21, 0x19, 0x22, 0x07, 0x12, 0xB3, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE6,
0x21, 0x09, 0x11, 0x22, 0x21, 0x19, 0xE6, 0x10, 0x02, 0x20, 0x1B, 0x30, 0x11, 0x21, 0x09, 0x11,
0xCC, 0x21, 0x17, 0x22, 0x07, 0x12, 0xCC, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10,
0x05, 0xE0, 0x10, 0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC, 0xE0, 0x21, 0x07, 0x11,
0x22, 0x21, 0x19, 0xE0, 0x10, 0xFF, 0xE0, 0x10, 0x02, 0x20, 0x1B, 0xE0, 0x10, 0x05, 0xE0, 0x10,
0x07, 0xE1, 0x10, 0x05, 0xE0, 0x10, 0x02, 0x21, 0x09, 0x11, 0x59, 0x21, 0x19, 0x22, 0x05, 0x12,
0x59, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0x0E, 0x20, 0xE0, 0x10, 0xFC, 0xE1,
0x10, 0xFF, 0xE0, 0x10, 0x02, 0x20, 0x1B, 0xE0, 0x10, 0x05, 0xE0, 0x10, 0x07, 0xE0, 0x21, 0x09,
0x11, 0xCC, 0x21, 0x17, 0xE0, 0x10, 0x05, 0xE0, 0x10, 0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0,
0x10, 0xFC, 0xE1, 0x10, 0xFF, 0xE0, 0x10, 0x02, 0x20, 0x1B, 0x21, 0x07, 0x21, 0x17, 0x22, 0x07,
0x12, 0xCC, 0x22, 0x15, 0x0E, 0x28, 0xE0, 0x10, 0x05, 0x0E, 0x20, 0xE0, 0x10, 0x07, 0xE1, 0x10,
0x05, 0xE0, 0x10, 0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC, 0xE0, 0x21, 0x07, 0x11,
0x22, 0x21, 0x19, 0xE0, 0x10, 0xFF, 0xE0, 0x10, 0x02, 0x20, 0x1B, 0xE0, 0x10, 0x05, 0xE0, 0x10,
0x07, 0xE1, 0x10, 0x05, 0xE0, 0x20, 0x0B, 0x10, 0x22, 0x20, 0x1B, 0x21, 0x09, 0x11, 0x59, 0x21,
0x19, 0x22, 0x05, 0x12, 0x59, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x25, 0xE0,
0x10, 0x22, 0xE0, 0x10, 0x1F, 0xE0, 0x10, 0x1D, 0xE0, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE0,
0x10, 0x1F, 0xE0, 0x10, 0x22, 0xE0, 0x10, 0x25, 0xE0, 0x10, 0x27, 0xE1, 0x10, 0x25, 0xE0, 0x20,
0x0B, 0x10, 0x02, 0x20, 0x1B, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x07, 0x12, 0x02, 0x22,
0x17, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x05, 0xE0, 0x10, 0x02, 0xE0, 0x10, 0xFF, 0x20,
0x19, 0xE0, 0x10, 0xFC, 0xE0, 0x21, 0x09, 0x11, 0x46, 0x21, 0x19, 0xE0, 0x10, 0xFF, 0xE0, 0x10,
0x02, 0x20, 0x1B, 0xE0, 0x10, 0x05, 0xE0, 0x10, 0x07, 0xE1, 0x10, 0x05, 0xE0, 0x20, 0x0B, 0x10,
0xB3, 0x20, 0x19, 0x21, 0x09, 0x11, 0x83, 0x21, 0x19, 0x22, 0x07, 0x12, 0x83, 0x22, 0x17, 0x0E,
0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB,
0xE0, 0x21, 0x09, 0x11, 0x02, 0x21, 0x19, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7,
0xE0, 0x10, 0xBA, 0xE1, 0x10, 0xB7, 0xE0, 0x20, 0x09, 0x10, 0x02, 0x20, 0x1B, 0x21, 0x09, 0x21,
0x19, 0x22, 0x07, 0x12, 0x02, 0x22, 0x17, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x05, 0xE0,
0x10, 0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC, 0xE0, 0x21, 0x09, 0x11, 0x46, 0x21,
0x19, 0xE0, 0x10, 0xFF, 0xE0, 0x10, 0x02, 0x20, 0x1B, 0xE0, 0x10, 0x05, 0xE0, 0x10, 0x07, 0xE1,
0x10, 0x05, 0xE0, 0x10, 0x02, 0x21, 0x09, 0x11, 0x83, 0x21, 0x19, 0x22, 0x07, 0x12, 0x83, 0x22,
0x17, 0x0E, 0x21, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0x0E, 0x20, 0xE0, 0x10, 0xFC, 0xE1, 0x10, 0xFF,
0xE0, 0x10, 0x02, 0x20, 0x1B, 0xE0, 0x10, 0x05, 0xE0, 0x10, 0x07, 0xE0, 0x21, 0x09, 0x11, 0x02,
0x21, 0x19, 0xE0, 0x10, 0x05, 0xE0, 0x10, 0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC,
0xE1, 0x10, 0xFF, 0xE0, 0x20, 0x09, 0x10, 0x22, 0x20, 0x1B, 0x21, 0x09, 0x21, 0x19, 0x22, 0x07,
0x12, 0x02, 0x22, 0x17, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x25, 0xE0, 0x10, 0x22, 0xE0,
0x10, 0x1F, 0xE0, 0x10, 0x1D, 0xE0, 0x21, 0x09, 0x11, 0x46, 0x21, 0x19, 0xE0, 0x10, 0x1F, 0xE0,
0x10, 0x22, 0xE0, 0x10, 0x25, 0xE0, 0x10, 0x27, 0xE1, 0x10, 0x25, 0xE0, 0x10, 0x22, 0x21, 0x09,
0x11, 0x83, 0x21, 0x19, 0x22, 0x07, 0x12, 0x83, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x10, 0x1F, 0x0E,
0x20, 0xE0, 0x10, 0x1D, 0xE1, 0x10, 0x1F, 0xE0, 0x10, 0x22, 0xE0, 0x10, 0x25, 0xE0, 0x10, 0x27,
0xE0, 0x21, 0x09, 0x11, 0x02, 0x21, 0x19, 0xE0, 0x10, 0x25, 0xE0, 0x10, 0x22, 0xE0, 0x10, 0x1F,
0xE0, 0x10, 0x1D, 0xE1, 0x10, 0x1F, 0xE0, 0x10, 0x22, 0x21, 0x09, 0x21, 0x19, 0x22, 0x07, 0x12,
0x02, 0x22, 0x17, 0x0E, 0x28, 0xE0, 0x10, 0x25, 0x0E, 0x20, 0xE0, 0x10, 0x27, 0xE1, 0x10, 0x25,
0xE0, 0x10, 0x22, 0xE0, 0x10, 0x1F, 0xE0, 0x10, 0x1D, 0xE0, 0x21, 0x09, 0x11, 0x46, 0x21, 0x19,
0xE0, 0x10, 0x1F, 0xE0, 0x10, 0x22, 0xE0, 0x10, 0x25, 0xE0, 0x10, 0x27, 0xE1, 0x10, 0x25, 0xE0,
0x10, 0x22, 0x21, 0x09, 0x11, 0x83, 0x21, 0x19, 0x22, 0x07, 0x12, 0x83, 0x22, 0x17, 0x0E, 0x21,
0xE0, 0x10, 0x1F, 0x0E, 0x20, 0xE0, 0x10, 0x1D, 0xE1, 0x10, 0x1F, 0xE0, 0x10, 0x22, 0xE0, 0x10,
0x25, 0xE0, 0x10, 0x27, 0xE0, 0x21, 0x09, 0x11, 0x02, 0x21, 0x19, 0xE0, 0x10, 0x25, 0xE0, 0x10,
0x22, 0xE0, 0x10, 0x1F, 0xE0, 0x10, 0x1D, 0xE1, 0x10, 0x1F, 0xE0, 0x20, 0x0B, 0x21, 0x09, 0x11,
0x59, 0x21, 0x17, 0x22, 0x07, 0x12, 0x59, 0x22, 0x13, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE6, 0x21,
0x07, 0x11, 0xB3, 0x21, 0x17, 0xE6, 0x10, 0x02, 0x20, 0x1B, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19,
0x22, 0x03, 0x12, 0x02, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x05, 0xE0, 0x10,
0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC, 0xE0, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17,
0xE0, 0x10, 0xFF, 0xE0, 0x10, 0x02, 0x20, 0x1B, 0xE0, 0x10, 0x05, 0xE0, 0x10, 0x07, 0xE1, 0x10,
0x05, 0xE0, 0x20, 0x0B, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0x22, 0x05,
0x12, 0x59, 0x22, 0x13, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0,
0x10, 0xAE, 0xE0, 0x10, 0xAB, 0xE0, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10, 0xAE, 0xE0,
0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE1, 0x10, 0xB7, 0xE0, 0x20, 0x09, 0x10, 0x83,
0x20, 0x19, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x03, 0x12, 0x02, 0x22, 0x15, 0x0E, 0x21,
0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE0,
0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0,
0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x20, 0x09, 0x10, 0x59, 0x20, 0x19, 0x21, 0x07, 0x11, 0x83,
0x21, 0x17, 0x22, 0x05, 0x12, 0x83, 0x22, 0x13, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x5D,
0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0xE0, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17,
0xE0, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x5D, 0xE0, 0x10, 0x5F, 0xE1, 0x10, 0x5D, 0xE0,
0x10, 0x59, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x03, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x21,
0xE0, 0x10, 0x55, 0x0E, 0x20, 0xE0, 0x10, 0x53, 0xE1, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0, 0x10,
0x5D, 0xE0, 0x10, 0x5F, 0xE0, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE0, 0x10, 0x5D, 0xE0, 0x10,
0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0xE1, 0x10, 0x55, 0xE0, 0x10, 0x59, 0x21, 0x07, 0x11,
0x83, 0x21, 0x17, 0x22, 0x05, 0x12, 0x83, 0x22, 0x13, 0x0E, 0x28, 0xE0, 0x10, 0x5D, 0x0E, 0x20,
0xE0, 0x10, 0x5F, 0xE1, 0x10, 0x5D, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0xE0,
0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x5D, 0xE0,
0x10, 0x5F, 0xE1, 0x10, 0x5D, 0xE0, 0x20, 0x09, 0x10, 0x46, 0x20, 0x19, 0x21, 0x07, 0x11, 0x22,
0x21, 0x19, 0x22, 0x03, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x49,
0xE0, 0x10, 0x46, 0xE0, 0x10, 0x42, 0xE0, 0x10, 0x40, 0xE0, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17,
0xE0, 0x10, 0x42, 0xE0, 0x10, 0x46, 0xE0, 0x10, 0x49, 0xE0, 0x10, 0x4B, 0xE1, 0x10, 0x49, 0xE0,
0x20, 0x09, 0x10, 0x59, 0x20, 0x19, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0x22, 0x05, 0x12, 0xB3,
0x22, 0x13, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x5D, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55,
0xE0, 0x10, 0x53, 0xE0, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x59,
0xE0, 0x10, 0x5D, 0xE0, 0x10, 0x5F, 0xE1, 0x10, 0x5D, 0xE0, 0x10, 0x59, 0x21, 0x09, 0x11, 0x46,
0x21, 0x19, 0x22, 0x03, 0x12, 0x46, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10, 0x55, 0x0E, 0x20, 0xE0,
0x10, 0x53, 0xE1, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x5D, 0xE0, 0x10, 0x5F, 0xE0, 0x21,
0x09, 0x11, 0x02, 0x21, 0x19, 0xE0, 0x10, 0x5D, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10,
0x53, 0xE1, 0x10, 0x55, 0xE0, 0x10, 0x59, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0x22, 0x05, 0x12,
0xB3, 0x22, 0x13, 0x0E, 0x28, 0xE0, 0x10, 0x5D, 0x0E, 0x20, 0xE0, 0x10, 0x5F, 0xE1, 0x10, 0x5D,
0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0xE0, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19,
0xE0, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x5D, 0xE0, 0x10, 0x5F, 0xE1, 0x10, 0x5D, 0xE0,
0x10, 0x59, 0x21, 0x09, 0x11, 0x46, 0x21, 0x19, 0x22, 0x03, 0x12, 0x46, 0x22, 0x15, 0x0E, 0x21,
0xE0, 0x10, 0x55, 0x0E, 0x20, 0xE0, 0x10, 0x53, 0xE1, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0, 0x10,
0x5D, 0xE0, 0x10, 0x5F, 0xE0, 0x21, 0x09, 0x11, 0x02, 0x21, 0x19, 0xE0, 0x10, 0x5D, 0xE0, 0x10,
0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0xE1, 0x10, 0x55, 0xE0, 0x20, 0x09, 0x21, 0x09, 0x11,
0x59, 0x21, 0x17, 0x22, 0x05, 0x12, 0x59, 0x22, 0x13, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE6, 0x21,
0x07, 0x11, 0xB3, 0x21, 0x17, 0xE6, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x03, 0x12, 0x02,
0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE6, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE6, 0x10,
0xB3, 0x20, 0x19, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0x22, 0x05, 0x12, 0x59, 0x22, 0x13, 0x0E,
0x28, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB,
0xE0, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7,
0xE0, 0x10, 0xBA, 0xE1, 0x10, 0xB7, 0xE0, 0x10, 0xB3, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22,
0x03, 0x12, 0x02, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10, 0xAE, 0x0E, 0x20, 0xE0, 0x10, 0xAB, 0xE1,
0x10, 0xAE, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE0, 0x21, 0x09, 0x11, 0xB3,
0x21, 0x17, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB, 0xE1, 0x10,
0xAE, 0xE0, 0x10, 0xB3, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x15,
0x0E, 0x30, 0xE0, 0x10, 0xB7, 0x0E, 0x20, 0xE0, 0x10, 0xBA, 0xE1, 0x10, 0xB7, 0xE0, 0x10, 0xB3,
0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB, 0xE0, 0x21, 0x09, 0x11, 0x59, 0x21, 0x19, 0xE0, 0x10, 0xAE,
0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE1, 0x10, 0xB7, 0xE0, 0x20, 0x09, 0x10,
0x83, 0x20, 0x19, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x19, 0x22, 0x05, 0x12, 0xB3, 0x22, 0x15, 0x0E,
0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C,
0xE0, 0x21, 0x09, 0x11, 0x22, 0x21, 0x19, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87,
0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x20, 0x09, 0x10, 0x59, 0x20, 0x19, 0x21, 0x09, 0x21,
0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x5D, 0xE0,
0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0xE0, 0x21, 0x09, 0x11, 0x59, 0x21, 0x19, 0xE0,
0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x5D, 0xE0, 0x10, 0x5F, 0xE1, 0x10, 0x5D, 0xE0, 0x20,
0x09, 0x10, 0x46, 0x20, 0x19, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x19, 0x22, 0x07, 0x12, 0xB3, 0x22,
0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x49, 0xE0, 0x10, 0x46, 0xE0, 0x10, 0x42, 0xE0,
0x10, 0x40, 0xE0, 0x21, 0x09, 0x11, 0x22, 0x21, 0x19, 0xE0, 0x10, 0x42, 0xE0, 0x10, 0x46, 0xE0,
0x10, 0x49, 0xE0, 0x10, 0x4B, 0xE1, 0x10, 0x49, 0xE0, 0x20, 0x09, 0x10, 0x59, 0x20, 0x19, 0x21,
0x09, 0x11, 0xCC, 0x21, 0x17, 0x22, 0x07, 0x12, 0xCC, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x0E, 0x20,
0xE2, 0x10, 0x5D, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0xE0, 0x21, 0x07, 0x11,
0x22, 0x21, 0x19, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x5D, 0xE0, 0x10, 0x5F, 0xE1,
0x10, 0x5D, 0xE0, 0x10, 0x59, 0x21, 0x09, 0x11, 0x59, 0x21, 0x19, 0x22, 0x05, 0x12, 0x59, 0x22,
0x17, 0x0E, 0x21, 0xE0, 0x10, 0x55, 0x0E, 0x20, 0xE0, 0x10, 0x53, 0xE1, 0x10, 0x55, 0xE0, 0x10,
0x59, 0xE0, 0x10, 0x5D, 0xE0, 0x10, 0x5F, 0xE0, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE0, 0x10,
0x5D, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0xE1, 0x10, 0x55, 0xE0, 0x20, 0x09,
0x10, 0x83, 0x20, 0x19, 0x21, 0x07, 0x21, 0x17, 0x22, 0x07, 0x12, 0xCC, 0x22, 0x15, 0x0E, 0x28,
0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE0,
0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0,
0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x20, 0x09, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x09, 0x11, 0x59,
0x21, 0x19, 0x22, 0x05, 0x12, 0x59, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xB7,
0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB, 0xE0, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17,
0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE1, 0x10, 0xB7, 0xE0,
0x20, 0x09, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x07, 0x12, 0x02,
0x22, 0x17, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xD1, 0xE0, 0x10, 0xCC, 0xE0, 0x10, 0xC7,
0xE0, 0x10, 0xC4, 0xE0, 0x21, 0x09, 0x11, 0x46, 0x21, 0x19, 0xE0, 0x10, 0xC7, 0xE0, 0x10, 0xCC,
0xE0, 0x10, 0xD1, 0xE0, 0x10, 0xD4, 0xE1, 0x10, 0xD1, 0xE0, 0x10, 0xCC, 0x21, 0x09, 0x11, 0x83,
0x21, 0x19, 0x22, 0x07, 0x12, 0x83, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x10, 0xC7, 0x0E, 0x20, 0xE0,
0x10, 0xC4, 0xE1, 0x10, 0xC7, 0xE0, 0x10, 0xCC, 0xE0, 0x10, 0xD1, 0xE0, 0x10, 0xD4, 0xE0, 0x21,
0x09, 0x11, 0x02, 0x21, 0x19, 0xE0, 0x10, 0xD1, 0xE0, 0x10, 0xCC, 0xE0, 0x10, 0xC7, 0xE0, 0x10,
0xC4, 0xE1, 0x10, 0xC7, 0xE0, 0x20, 0x09, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x09, 0x21, 0x19, 0x22,
0x07, 0x12, 0x02, 0x22, 0x17, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xB7, 0xE0, 0x10, 0xB3,
0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB, 0xE0, 0x21, 0x09, 0x11, 0x46, 0x21, 0x19, 0xE0, 0x10, 0xAE,
0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE1, 0x10, 0xB7, 0xE0, 0x20, 0x09, 0x10,
0x83, 0x20, 0x19, 0x21, 0x09, 0x11, 0x83, 0x21, 0x19, 0x22, 0x07, 0x12, 0x83, 0x22, 0x17, 0x0E,
0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C,
0xE0, 0x21, 0x09, 0x11, 0x02, 0x21, 0x19, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87,
0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x20, 0x09, 0x10, 0x59, 0x20, 0x19, 0x21, 0x09, 0x11,
0x59, 0x21, 0x17, 0x22, 0x07, 0x12, 0x59, 0x22, 0x17, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10,
0x5D, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0xE0, 0x21, 0x07, 0x11, 0xB3, 0x21,
0x17, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x5D, 0xE0, 0x10, 0x5F, 0xE1, 0x10, 0x5D,
0xE0, 0x10, 0x59, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x07, 0x12, 0x02, 0x22, 0x19, 0x0E,
0x21, 0xE0, 0x10, 0x55, 0x0E, 0x20, 0xE0, 0x10, 0x53, 0xE1, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0,
0x10, 0x5D, 0xE0, 0x10, 0x5F, 0xE0, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10, 0x5D, 0xE0,
0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0xE1, 0x10, 0x55, 0xE0, 0x10, 0x59, 0x21, 0x07,
0x11, 0x59, 0x21, 0x17, 0x22, 0x09, 0x12, 0x59, 0x22, 0x17, 0x0E, 0x28, 0xE0, 0x10, 0x5D, 0x0E,
0x20, 0xE0, 0x10, 0x5F, 0xE1, 0x10, 0x5D, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53,
0xE0, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x5D,
0xE0, 0x10, 0x5F, 0xE1, 0x10, 0x5D, 0xE0, 0x20, 0x09, 0x10, 0x83, 0x20, 0x19, 0x21, 0x07, 0x11,
0x02, 0x21, 0x19, 0x22, 0x07, 0x12, 0x02, 0x22, 0x19, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10,
0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE0, 0x21, 0x09, 0x11, 0xB3, 0x21,
0x17, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87,
0xE0, 0xFF,
};
+184
View File
@@ -0,0 +1,184 @@
#include "msxgl.h"
// lVGM music track — tips/hints screen.
// Occupies the start of banked segment 27 (bank-2 window 0x8000-0x9FFF,
// via SET_BANK_SEGMENT(2, 27)). MSX-Music (YM2413/OPLL), 60 Hz, looping.
// 2792 bytes; the rest of the 8KB segment is unused padding.
// Access: LVGM_Play((const u8*)0x8000, TRUE) with segment 27 banked in.
const u8 g_MusHints[2792] = {
0x6C, 0x56, 0x47, 0x4D, 0x06, 0x02, 0xF1, 0x16, 0x20, 0xFE, 0x26, 0x05, 0x17, 0x50, 0x27, 0x05,
0x18, 0xC0, 0x28, 0x01, 0x56, 0x00, 0x0E, 0x20, 0x10, 0x83, 0x20, 0x19, 0x30, 0x71, 0x11, 0x83,
0x21, 0x17, 0x31, 0xC6, 0x12, 0x83, 0x22, 0x13, 0x32, 0xE3, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5,
0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x03, 0x12,
0x22, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE6,
0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x05, 0x12, 0x83, 0x22, 0x13, 0x0E, 0x28, 0xE0, 0x0E,
0x20, 0xE5, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0xCC, 0x20, 0x19, 0x21,
0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x03, 0x12, 0xB3, 0x22, 0x13, 0x0E, 0x21, 0xE0, 0x0E, 0x20,
0xE5, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0x22, 0x20, 0x1B, 0x21, 0x07,
0x11, 0x83, 0x21, 0x17, 0x22, 0x03, 0x12, 0x83, 0x22, 0x13, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5,
0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x03, 0x12,
0x22, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE6,
0x20, 0x0B, 0x10, 0x02, 0x20, 0x1B, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x05, 0x12, 0x83,
0x22, 0x13, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20,
0x0B, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x03, 0x12, 0xB3, 0x22,
0x13, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE7, 0x20, 0x09,
0x10, 0xB3, 0x20, 0x19, 0x21, 0x07, 0x11, 0x12, 0x21, 0x19, 0x22, 0x03, 0x12, 0x12, 0x22, 0x15,
0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0x83, 0x21, 0x17, 0xE6, 0x21, 0x07, 0x11,
0xCC, 0x21, 0x17, 0x22, 0x05, 0x12, 0x83, 0x22, 0x13, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21,
0x07, 0x11, 0x83, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0x83, 0x20, 0x19, 0x21, 0x07, 0x11, 0x12,
0x21, 0x19, 0x22, 0x03, 0x12, 0x12, 0x22, 0x15, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09,
0x11, 0x83, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x07, 0x11, 0xCC, 0x21,
0x17, 0x22, 0x05, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11,
0x83, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x07, 0x11, 0x12, 0x21, 0x19,
0x22, 0x05, 0x12, 0x12, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0x83,
0x21, 0x17, 0xE6, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0x22, 0x05, 0x12, 0x83, 0x22, 0x13, 0x0E,
0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0xE6, 0x21, 0x07, 0x11, 0x12,
0x21, 0x19, 0x22, 0x03, 0x12, 0x12, 0x22, 0x15, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09,
0x11, 0x83, 0x21, 0x17, 0xE6, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0x22, 0x05, 0x12, 0x22, 0x22,
0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0xE6, 0x20, 0x09,
0x10, 0x02, 0x20, 0x1B, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0x22, 0x05, 0x12, 0xCC, 0x22, 0x13,
0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0xE6, 0x21, 0x09, 0x11,
0x59, 0x21, 0x19, 0x22, 0x03, 0x12, 0x59, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21,
0x09, 0x11, 0x22, 0x21, 0x19, 0xE6, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0x22, 0x05, 0x12, 0xCC,
0x22, 0x13, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0xE6, 0x20,
0x0B, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x09, 0x11, 0x59, 0x21, 0x19, 0x22, 0x03, 0x12, 0x02, 0x22,
0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0x22, 0x21, 0x19, 0xE6, 0x20, 0x09,
0x10, 0xB3, 0x20, 0x19, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0x22, 0x05, 0x12, 0xCC, 0x22, 0x13,
0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0xE6, 0x21, 0x09, 0x11,
0x59, 0x21, 0x19, 0x22, 0x03, 0x12, 0x59, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21,
0x09, 0x11, 0x22, 0x21, 0x19, 0xE6, 0x20, 0x09, 0x10, 0x59, 0x20, 0x19, 0x21, 0x09, 0x11, 0xCC,
0x21, 0x17, 0x22, 0x05, 0x12, 0xCC, 0x22, 0x13, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07,
0x11, 0x22, 0x21, 0x19, 0xE6, 0x20, 0x09, 0x10, 0x83, 0x20, 0x19, 0x21, 0x09, 0x11, 0x59, 0x21,
0x19, 0x22, 0x03, 0x12, 0x02, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11,
0x22, 0x21, 0x19, 0xE7, 0x20, 0x09, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x09, 0x11, 0x59, 0x21, 0x17,
0x22, 0x05, 0x12, 0x59, 0x22, 0x13, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xB7, 0xE0, 0x10,
0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE1, 0x10, 0xAE,
0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE1, 0x10, 0xB7, 0x21, 0x07, 0x11, 0x02,
0x21, 0x19, 0x22, 0x03, 0x12, 0x02, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10, 0xB3, 0x0E, 0x20, 0xE0,
0x10, 0xAE, 0xE0, 0x10, 0xAB, 0xE1, 0x10, 0xAE, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0x21, 0x09,
0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10, 0xBA, 0xE1, 0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE,
0xE0, 0x10, 0xAB, 0xE0, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0x22, 0x05, 0x12, 0x59, 0x22, 0x13,
0x0E, 0x28, 0xE0, 0x10, 0xAE, 0x0E, 0x20, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA,
0xE1, 0x10, 0xB7, 0xE0, 0x10, 0xB3, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10, 0xAE, 0xE0,
0x10, 0xAB, 0xE1, 0x10, 0xAE, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0x21, 0x07,
0x11, 0x02, 0x21, 0x19, 0x22, 0x03, 0x12, 0xB3, 0x22, 0x13, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE0,
0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB, 0xE1, 0x10, 0xAE, 0x21, 0x09,
0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE1, 0x10, 0xB7,
0xE0, 0x10, 0xB3, 0xE0, 0x20, 0x09, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0x22, 0x03, 0x12, 0x59,
0x22, 0x13, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE6, 0x21,
0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x03, 0x12, 0x02, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20,
0xE5, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE6, 0x10, 0x22, 0x20, 0x1B, 0x30, 0x42, 0x21, 0x07,
0x11, 0x59, 0x21, 0x17, 0x22, 0x05, 0x12, 0x59, 0x22, 0x13, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE2,
0x10, 0x25, 0xE0, 0x10, 0x22, 0xE0, 0x10, 0x1F, 0xE0, 0x10, 0x1D, 0x21, 0x07, 0x11, 0xB3, 0x21,
0x17, 0xE1, 0x10, 0x1F, 0xE0, 0x10, 0x22, 0xE0, 0x10, 0x25, 0xE0, 0x10, 0x27, 0xE1, 0x10, 0x25,
0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x03, 0x12, 0xB3, 0x22, 0x13, 0x0E, 0x21, 0xE0, 0x10,
0x22, 0x0E, 0x20, 0xE0, 0x10, 0x1F, 0xE0, 0x10, 0x1D, 0xE1, 0x10, 0x1F, 0xE0, 0x10, 0x22, 0xE0,
0x10, 0x25, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10, 0x27, 0xE1, 0x10, 0x25, 0xE0, 0x10,
0x22, 0xE0, 0x10, 0x1F, 0xE0, 0x10, 0x1D, 0xE0, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x03,
0x12, 0x83, 0x22, 0x13, 0x0E, 0x30, 0xE0, 0x10, 0x1F, 0x0E, 0x20, 0xE0, 0x10, 0x22, 0xE0, 0x10,
0x25, 0xE0, 0x10, 0x27, 0xE1, 0x10, 0x25, 0xE0, 0x10, 0x22, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17,
0xE0, 0x10, 0x1F, 0xE0, 0x10, 0x1D, 0xE1, 0x10, 0x1F, 0xE0, 0x10, 0x22, 0xE0, 0x10, 0x25, 0xE0,
0x20, 0x0B, 0x10, 0x22, 0x20, 0x1B, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x03, 0x12, 0x22,
0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x25, 0xE0, 0x10, 0x22, 0xE0, 0x10, 0x1F,
0xE0, 0x10, 0x1D, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE1, 0x10, 0x1F, 0xE0, 0x10, 0x22, 0xE0,
0x10, 0x25, 0xE0, 0x10, 0x27, 0xE1, 0x20, 0x0B, 0x10, 0x02, 0x20, 0x1B, 0x21, 0x07, 0x11, 0x83,
0x21, 0x17, 0x22, 0x05, 0x12, 0x83, 0x22, 0x13, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x05,
0xE0, 0x10, 0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC, 0x21, 0x07, 0x11, 0xCC, 0x21,
0x17, 0xE1, 0x10, 0xFF, 0xE0, 0x10, 0x02, 0x20, 0x1B, 0xE0, 0x10, 0x05, 0xE0, 0x10, 0x07, 0xE1,
0x10, 0x05, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x03, 0x12, 0xB3, 0x22, 0x13, 0x0E, 0x21,
0xE0, 0x10, 0x02, 0x0E, 0x20, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC, 0xE1, 0x10, 0xFF,
0xE0, 0x10, 0x02, 0x20, 0x1B, 0xE0, 0x10, 0x05, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE0, 0x10,
0x07, 0xE1, 0x10, 0x05, 0xE0, 0x10, 0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC, 0xE0,
0x20, 0x09, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x03, 0x12, 0x83,
0x22, 0x13, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xD1, 0xE0, 0x10, 0xCC, 0xE0, 0x10, 0xC7,
0xE0, 0x10, 0xC4, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE1, 0x10, 0xC7, 0xE0, 0x10, 0xCC, 0xE0,
0x10, 0xD1, 0xE0, 0x10, 0xD4, 0xE1, 0x20, 0x09, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x07, 0x11, 0x22,
0x21, 0x19, 0x22, 0x03, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xB7,
0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE1,
0x10, 0xAE, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE1, 0x20, 0x09, 0x10, 0x83,
0x20, 0x19, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x05, 0x12, 0x83, 0x22, 0x13, 0x0E, 0x28,
0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0x21,
0x07, 0x11, 0xCC, 0x21, 0x17, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10,
0x8A, 0xE1, 0x10, 0x87, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x03, 0x12, 0xB3, 0x22, 0x13,
0x0E, 0x21, 0xE0, 0x10, 0x83, 0x0E, 0x20, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE1, 0x10, 0x7F,
0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE0, 0x10, 0x8A, 0xE1,
0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE1, 0x20, 0x09, 0x10, 0xCC,
0x20, 0x19, 0x21, 0x07, 0x11, 0x12, 0x21, 0x19, 0x22, 0x03, 0x12, 0x12, 0x22, 0x15, 0x0E, 0x30,
0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xD1, 0xE0, 0x10, 0xCC, 0xE0, 0x10, 0xC7, 0xE0, 0x10, 0xC4, 0x21,
0x09, 0x11, 0x83, 0x21, 0x17, 0xE1, 0x10, 0xC7, 0xE0, 0x10, 0xCC, 0xE0, 0x10, 0xD1, 0xE0, 0x10,
0xD4, 0xE1, 0x20, 0x09, 0x10, 0x22, 0x20, 0x1B, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0x22, 0x05,
0x12, 0x83, 0x22, 0x13, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x25, 0xE0, 0x10, 0x22, 0xE0,
0x10, 0x1F, 0xE0, 0x10, 0x1D, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0xE1, 0x10, 0x1F, 0xE0, 0x10,
0x22, 0xE0, 0x10, 0x25, 0xE0, 0x10, 0x27, 0xE1, 0x20, 0x0B, 0x10, 0x83, 0x20, 0x1B, 0x21, 0x07,
0x11, 0x12, 0x21, 0x19, 0x22, 0x03, 0x12, 0x12, 0x22, 0x15, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE2,
0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0x21, 0x09, 0x11, 0x83, 0x21,
0x17, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87,
0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0x22, 0x05, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10,
0x83, 0x0E, 0x20, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0,
0x10, 0x87, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x10,
0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE0, 0x21, 0x07, 0x11, 0x12, 0x21, 0x19, 0x22, 0x05,
0x12, 0x12, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x10, 0x7F, 0x0E, 0x20, 0xE0, 0x10, 0x83, 0xE0, 0x10,
0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x10, 0x83, 0x21, 0x09, 0x11, 0x83, 0x21, 0x17,
0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0,
0x10, 0x8A, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0x22, 0x05, 0x12, 0x83, 0x22, 0x13, 0x0E, 0x21,
0xE0, 0x0E, 0x20, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE1,
0x10, 0x7F, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10,
0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x20, 0x0B, 0x10, 0x22, 0x20, 0x1B, 0x21, 0x07,
0x11, 0x12, 0x21, 0x19, 0x22, 0x03, 0x12, 0x12, 0x22, 0x15, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE2,
0x10, 0x25, 0xE0, 0x10, 0x22, 0xE0, 0x10, 0x1F, 0xE0, 0x10, 0x1D, 0x21, 0x09, 0x11, 0x83, 0x21,
0x17, 0xE1, 0x10, 0x1F, 0xE0, 0x10, 0x22, 0xE0, 0x10, 0x25, 0xE0, 0x10, 0x27, 0xE1, 0x10, 0x25,
0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0x22, 0x05, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10,
0x22, 0x0E, 0x20, 0xE0, 0x10, 0x1F, 0xE0, 0x10, 0x1D, 0xE1, 0x10, 0x1F, 0xE0, 0x10, 0x22, 0xE0,
0x10, 0x25, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0xE0, 0x10, 0x27, 0xE1, 0x10, 0x25, 0xE0, 0x10,
0x22, 0xE0, 0x10, 0x1F, 0xE0, 0x10, 0x1D, 0xE0, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x05,
0x12, 0x02, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x10, 0x1F, 0x0E, 0x20, 0xE0, 0x10, 0x22, 0xE0, 0x10,
0x25, 0xE0, 0x10, 0x27, 0xE1, 0x10, 0x25, 0xE0, 0x10, 0x22, 0x21, 0x09, 0x11, 0x22, 0x21, 0x19,
0x0E, 0x30, 0xE0, 0x10, 0x1F, 0x0E, 0x20, 0xE0, 0x10, 0x1D, 0xE1, 0x10, 0x1F, 0xE0, 0x10, 0x22,
0xE0, 0x10, 0x25, 0xE0, 0x20, 0x0B, 0x10, 0x02, 0x20, 0x1B, 0x21, 0x09, 0x11, 0x59, 0x21, 0x19,
0x22, 0x05, 0x12, 0x83, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x05, 0xE0, 0x10,
0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC, 0x21, 0x09, 0x11, 0x22, 0x21, 0x19, 0xE1,
0x10, 0xFF, 0xE0, 0x10, 0x02, 0x20, 0x1B, 0xE0, 0x10, 0x05, 0xE0, 0x10, 0x07, 0xE1, 0x20, 0x0B,
0x10, 0xCC, 0x20, 0x19, 0x21, 0x09, 0x11, 0x02, 0x21, 0x19, 0x22, 0x05, 0x12, 0x02, 0x22, 0x15,
0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xD1, 0xE0, 0x10, 0xCC, 0xE0, 0x10, 0xC7, 0xE0, 0x10,
0xC4, 0x21, 0x09, 0x11, 0x22, 0x21, 0x19, 0xE1, 0x10, 0xC7, 0xE0, 0x10, 0xCC, 0xE0, 0x10, 0xD1,
0xE0, 0x10, 0xD4, 0xE1, 0x10, 0xD1, 0x21, 0x09, 0x11, 0x59, 0x21, 0x19, 0x22, 0x05, 0x12, 0x22,
0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10, 0xCC, 0x0E, 0x20, 0xE0, 0x10, 0xC7, 0xE0, 0x10, 0xC4, 0xE1,
0x10, 0xC7, 0xE0, 0x10, 0xCC, 0xE0, 0x10, 0xD1, 0x21, 0x09, 0x11, 0x22, 0x21, 0x19, 0xE0, 0x10,
0xD4, 0xE1, 0x10, 0xD1, 0xE0, 0x10, 0xCC, 0xE0, 0x10, 0xC7, 0xE0, 0x10, 0xC4, 0xE0, 0x20, 0x09,
0x10, 0xB3, 0x20, 0x19, 0x21, 0x09, 0x11, 0x02, 0x21, 0x19, 0x22, 0x05, 0x12, 0x02, 0x22, 0x15,
0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x10,
0xAB, 0x21, 0x09, 0x11, 0x22, 0x21, 0x19, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE0, 0x10, 0xAE, 0xE0,
0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE1, 0x20, 0x09, 0x10, 0x83, 0x20, 0x19, 0x21,
0x09, 0x11, 0x59, 0x21, 0x19, 0x22, 0x05, 0x12, 0x83, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20,
0xE2, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0x21, 0x09, 0x11, 0x22,
0x21, 0x19, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x20,
0x09, 0x10, 0x83, 0x20, 0x19, 0x21, 0x09, 0x11, 0x02, 0x21, 0x19, 0x22, 0x05, 0x12, 0x02, 0x22,
0x15, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0,
0x10, 0x7C, 0x21, 0x09, 0x11, 0x22, 0x21, 0x19, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10,
0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0x21, 0x09, 0x11, 0x59, 0x21, 0x19, 0x22, 0x05, 0x12,
0x22, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10, 0x83, 0x0E, 0x20, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C,
0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0x21, 0x09, 0x11, 0x22, 0x21, 0x19, 0xE0,
0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE1, 0x10,
0x7F, 0x21, 0x09, 0x21, 0x19, 0x22, 0x05, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x10, 0x83, 0x0E, 0x20,
0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0x21,
0x09, 0x11, 0x59, 0x21, 0x19, 0x0E, 0x30, 0xE0, 0x10, 0x7C, 0x0E, 0x20, 0xE1, 0x10, 0x7F, 0xE0,
0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE0, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x19, 0x22,
0x05, 0x12, 0xB3, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10, 0x87, 0x0E, 0x20, 0xE0, 0x10, 0x83, 0xE0,
0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0x21, 0x09, 0x11, 0x59, 0x21,
0x19, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F,
0xE0, 0x10, 0x7C, 0x21, 0x09, 0x11, 0x22, 0x21, 0x1B, 0x22, 0x05, 0x12, 0x22, 0x22, 0x15, 0x0E,
0x28, 0xE0, 0x0E, 0x20, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A,
0xE1, 0x10, 0x87, 0x21, 0x0B, 0x11, 0x59, 0x21, 0x1B, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0,
0x10, 0x7C, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0x21, 0x0B, 0x11, 0xB3, 0x21,
0x1B, 0x22, 0x05, 0x12, 0x59, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10, 0x8A, 0x0E, 0x20, 0xE1, 0x10,
0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE0, 0x21, 0x0B, 0x11, 0x59, 0x21,
0x1B, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87,
0xE0, 0x10, 0x83, 0x21, 0x0B, 0x11, 0x22, 0x21, 0x1D, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E,
0x30, 0xE0, 0x10, 0x7F, 0x0E, 0x20, 0xE0, 0x10, 0x7C, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0,
0x10, 0x87, 0xE0, 0x10, 0x8A, 0x21, 0x0D, 0x11, 0x59, 0x21, 0x1D, 0x0E, 0x30, 0xE0, 0x0E, 0x20,
0xE0, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE1, 0x10, 0x7F, 0x21,
0x0D, 0x11, 0xB3, 0x21, 0x1D, 0x22, 0x07, 0x12, 0xB3, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x10, 0x83,
0x0E, 0x20, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10,
0x7F, 0x21, 0x0D, 0x11, 0x59, 0x21, 0x1D, 0xE0, 0x10, 0x7C, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83,
0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE0, 0xFF,
};
+215
View File
@@ -0,0 +1,215 @@
#include "msxgl.h"
// lVGM music track — gameplay (the maze).
// Occupies the start of banked segment 28 (bank-2 window 0x8000-0x9FFF,
// via SET_BANK_SEGMENT(2, 28)). MSX-Music (YM2413/OPLL), 60 Hz, looping.
// 3290 bytes; the rest of the 8KB segment is unused padding.
// Access: LVGM_Play((const u8*)0x8000, TRUE) with segment 28 banked in.
const u8 g_MusMaze[3290] = {
0x6C, 0x56, 0x47, 0x4D, 0x06, 0x02, 0xF1, 0x16, 0x20, 0xFE, 0x26, 0x05, 0x17, 0x50, 0x27, 0x05,
0x18, 0xC0, 0x28, 0x01, 0x56, 0x00, 0x0E, 0x20, 0x10, 0x83, 0x20, 0x19, 0x30, 0x44, 0x11, 0x83,
0x21, 0x17, 0x31, 0xC4, 0x12, 0x83, 0x22, 0x15, 0x32, 0xE4, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5,
0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x07, 0x11,
0x22, 0x21, 0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE4, 0x21,
0x09, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0x22, 0x20, 0x1B, 0x21, 0x07, 0x11, 0x83,
0x21, 0x17, 0x22, 0x07, 0x12, 0x83, 0x22, 0x15, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07,
0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20, 0x0B, 0x10, 0x83, 0x20, 0x19, 0x21, 0x07, 0x11, 0x22, 0x21,
0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11,
0xCC, 0x21, 0x17, 0xE6, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x07, 0x12, 0x83, 0x22, 0x15,
0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE4, 0x20, 0x09, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x10,
0x22, 0x20, 0x1B, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E,
0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20, 0x0B, 0x10, 0x02,
0x20, 0x1B, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x07, 0x12, 0x83, 0x22, 0x15, 0x0E, 0x28,
0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE5, 0x20, 0x0B, 0x10, 0xCC, 0x20,
0x19, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x21, 0xE0,
0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17,
0x22, 0x07, 0x12, 0x83, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0xCC,
0x21, 0x17, 0xE6, 0x20, 0x09, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x05, 0x12, 0x22, 0x22,
0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE5, 0x10, 0xB3,
0x20, 0x19, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x07, 0x12, 0x83, 0x22, 0x15, 0x0E, 0x28,
0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0xCC, 0x20,
0x19, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x21, 0xE0,
0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0x83, 0x20, 0x19,
0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0x22, 0x07, 0x12, 0x59, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x0E,
0x20, 0xE4, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE6, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22,
0x05, 0x12, 0x02, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x20, 0x09, 0x21, 0x09, 0x11,
0xB3, 0x21, 0x17, 0xE6, 0x20, 0x19, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0x22, 0x07, 0x12, 0x59,
0x22, 0x15, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE6, 0x20,
0x09, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x05, 0x12, 0x02, 0x22,
0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE4, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE6, 0x20, 0x09,
0x10, 0xCC, 0x20, 0x19, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0x22, 0x07, 0x12, 0x59, 0x22, 0x15,
0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE6, 0x21, 0x07, 0x11,
0x02, 0x21, 0x19, 0x22, 0x05, 0x12, 0x02, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21,
0x09, 0x11, 0xB3, 0x21, 0x17, 0xE5, 0x20, 0x09, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0x22, 0x07,
0x12, 0x59, 0x22, 0x15, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17,
0xE6, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x05, 0x12, 0x02, 0x22, 0x17, 0x0E, 0x21, 0xE0,
0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE6, 0x10, 0x59, 0x20, 0x19, 0x21, 0x07,
0x11, 0x22, 0x21, 0x17, 0x22, 0x07, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5,
0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0xE5, 0x20, 0x09, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x07, 0x11,
0xB3, 0x21, 0x17, 0x22, 0x05, 0x12, 0xB3, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21,
0x07, 0x11, 0x59, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0x02, 0x20, 0x1B, 0x21, 0x07, 0x11, 0x22,
0x21, 0x17, 0x22, 0x05, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07,
0x11, 0x59, 0x21, 0x17, 0xE6, 0x20, 0x0B, 0x10, 0x59, 0x20, 0x19, 0x21, 0x07, 0x11, 0xB3, 0x21,
0x17, 0x22, 0x05, 0x12, 0xB3, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE4, 0x21, 0x07, 0x11,
0x59, 0x21, 0x17, 0xE6, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x05, 0x12, 0x83, 0x22, 0x15,
0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x20, 0x09, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x10,
0x02, 0x20, 0x1B, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E,
0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20, 0x0B, 0x10, 0xCC,
0x20, 0x19, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x07, 0x12, 0x83, 0x22, 0x15, 0x0E, 0x28,
0xE0, 0x0E, 0x20, 0xE4, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0xB3, 0x20,
0x19, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x21, 0xE0,
0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17,
0x22, 0x07, 0x12, 0x59, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0xB3,
0x21, 0x17, 0xE5, 0x20, 0x09, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x05, 0x12, 0x02, 0x22,
0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE6, 0x10, 0x83,
0x20, 0x19, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0x22, 0x07, 0x12, 0x59, 0x22, 0x15, 0x0E, 0x28,
0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0xB3, 0x20,
0x19, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x05, 0x12, 0x02, 0x22, 0x17, 0x0E, 0x21, 0xE0,
0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE5, 0x20, 0x09, 0x10, 0x59, 0x20, 0x19,
0x21, 0x07, 0x11, 0x22, 0x21, 0x17, 0x22, 0x07, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x0E,
0x20, 0xE5, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0xE6, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0x22,
0x05, 0x12, 0xB3, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x20, 0x09, 0x21, 0x07, 0x11,
0x59, 0x21, 0x17, 0xE6, 0x20, 0x19, 0x21, 0x07, 0x11, 0x22, 0x21, 0x17, 0x22, 0x05, 0x12, 0x22,
0x22, 0x15, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE4, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0xE6, 0x20,
0x09, 0x10, 0x46, 0x20, 0x19, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0x22, 0x05, 0x12, 0xB3, 0x22,
0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0xE6, 0x20, 0x09,
0x10, 0x22, 0x20, 0x19, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x05, 0x12, 0x83, 0x22, 0x15,
0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x21, 0x07, 0x11,
0x22, 0x21, 0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE4, 0x21,
0x09, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x07,
0x12, 0x83, 0x22, 0x15, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17,
0xE6, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x21, 0xE0,
0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE5, 0x10, 0x83, 0x20, 0x19, 0x21, 0x07,
0x11, 0x83, 0x21, 0x17, 0x22, 0x07, 0x12, 0x83, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2,
0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0x21, 0x07, 0x11, 0xCC, 0x21,
0x17, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87,
0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x10,
0x83, 0x0E, 0x20, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0,
0x10, 0x87, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x10,
0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE0, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x07,
0x12, 0x83, 0x22, 0x15, 0x0E, 0x28, 0xE0, 0x10, 0x7F, 0x0E, 0x20, 0xE0, 0x10, 0x83, 0xE0, 0x10,
0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x10, 0x83, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17,
0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x20, 0x09, 0x10,
0xB3, 0x20, 0x19, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E,
0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB,
0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE1, 0x10, 0xAE, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0,
0x10, 0xBA, 0xE1, 0x20, 0x09, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22,
0x07, 0x12, 0x83, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xD1, 0xE0, 0x10, 0xCC,
0xE0, 0x10, 0xC7, 0xE0, 0x10, 0xC4, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE1, 0x10, 0xC7, 0xE0,
0x10, 0xCC, 0xE0, 0x10, 0xD1, 0xE0, 0x10, 0xD4, 0xE1, 0x10, 0xD1, 0x21, 0x07, 0x11, 0x22, 0x21,
0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x10, 0xCC, 0x0E, 0x20, 0xE0, 0x10,
0xC7, 0xE0, 0x10, 0xC4, 0xE1, 0x10, 0xC7, 0xE0, 0x10, 0xCC, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17,
0xE0, 0x10, 0xD1, 0xE0, 0x10, 0xD4, 0xE1, 0x10, 0xD1, 0xE0, 0x10, 0xCC, 0xE0, 0x10, 0xC7, 0xE0,
0x20, 0x09, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x07, 0x12, 0x83,
0x22, 0x15, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE,
0xE0, 0x10, 0xAB, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE1, 0x10, 0xAE, 0xE0, 0x10, 0xB3, 0xE0,
0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE1, 0x20, 0x09, 0x10, 0x83, 0x20, 0x19, 0x21, 0x07, 0x11, 0x22,
0x21, 0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x87,
0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE1,
0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x20, 0x09, 0x10, 0x59,
0x20, 0x19, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0x22, 0x07, 0x12, 0x59, 0x22, 0x15, 0x0E, 0x30,
0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x5D, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0x21, 0x07, 0x11, 0xB3,
0x21, 0x17, 0xE0, 0x10, 0x53, 0xE1, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x5D, 0xE0, 0x10,
0x5F, 0xE0, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x05, 0x12, 0x02, 0x22, 0x17, 0x0E, 0x21,
0xE0, 0x10, 0x5D, 0x0E, 0x20, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0xE1, 0x10,
0x55, 0xE0, 0x10, 0x59, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10, 0x5D, 0xE0, 0x10, 0x5F,
0xE1, 0x10, 0x5D, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0x21, 0x07, 0x11, 0x59,
0x21, 0x17, 0x22, 0x07, 0x12, 0x59, 0x22, 0x15, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE0, 0x10, 0x55,
0xE0, 0x10, 0x59, 0xE0, 0x10, 0x5D, 0xE0, 0x10, 0x5F, 0xE1, 0x10, 0x5D, 0x21, 0x07, 0x11, 0xB3,
0x21, 0x17, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0xE0, 0x10, 0x53, 0xE1, 0x10, 0x55, 0xE0, 0x20,
0x09, 0x10, 0x83, 0x20, 0x19, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x05, 0x12, 0x02, 0x22,
0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0,
0x10, 0x7C, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10,
0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x20, 0x09, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x07, 0x11, 0x59, 0x21,
0x17, 0x22, 0x07, 0x12, 0x59, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xB7, 0xE0,
0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE1, 0x10,
0xAE, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE1, 0x10, 0xB7, 0x21, 0x07, 0x11,
0x02, 0x21, 0x19, 0x22, 0x05, 0x12, 0x02, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x10, 0xB3, 0x0E, 0x20,
0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB, 0xE1, 0x10, 0xAE, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0x21,
0x09, 0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10, 0xBA, 0xE1, 0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10,
0xAE, 0xE0, 0x20, 0x09, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0x22, 0x07, 0x12, 0x59, 0x22, 0x15,
0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE6, 0x10, 0xCC, 0x20,
0x19, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x05, 0x12, 0x02, 0x22, 0x17, 0x0E, 0x21, 0xE0,
0x0E, 0x20, 0xE2, 0x10, 0xD1, 0xE0, 0x10, 0xCC, 0xE0, 0x10, 0xC7, 0xE0, 0x10, 0xC4, 0x21, 0x09,
0x11, 0xB3, 0x21, 0x17, 0xE1, 0x10, 0xC7, 0xE0, 0x10, 0xCC, 0xE0, 0x10, 0xD1, 0xE0, 0x10, 0xD4,
0xE1, 0x20, 0x09, 0x10, 0x02, 0x20, 0x1B, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x07, 0x12,
0x83, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x05, 0xE0, 0x10, 0x02, 0xE0, 0x10,
0xFF, 0x20, 0x19, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE0, 0x10, 0xFC, 0xE1, 0x10, 0xFF, 0xE0,
0x10, 0x02, 0x20, 0x1B, 0xE0, 0x10, 0x05, 0xE0, 0x10, 0x07, 0xE0, 0x21, 0x07, 0x11, 0x22, 0x21,
0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x10, 0x05, 0x0E, 0x20, 0xE0, 0x10,
0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC, 0xE1, 0x10, 0xFF, 0xE0, 0x10, 0x02, 0x20,
0x1B, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE0, 0x10, 0x05, 0xE0, 0x10, 0x07, 0xE1, 0x10, 0x05,
0xE0, 0x10, 0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC, 0x21, 0x07, 0x11, 0x83, 0x21,
0x17, 0x22, 0x07, 0x12, 0x83, 0x22, 0x15, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE0, 0x10, 0xFF, 0xE0,
0x10, 0x02, 0x20, 0x1B, 0xE0, 0x10, 0x05, 0xE0, 0x10, 0x07, 0xE1, 0x10, 0x05, 0x21, 0x07, 0x11,
0xCC, 0x21, 0x17, 0xE0, 0x10, 0x02, 0xE0, 0x10, 0xFF, 0x20, 0x19, 0xE0, 0x10, 0xFC, 0xE1, 0x10,
0xFF, 0xE0, 0x10, 0x02, 0x20, 0x1B, 0xE0, 0x20, 0x0B, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x07, 0x11,
0x22, 0x21, 0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10,
0xD1, 0xE0, 0x10, 0xCC, 0xE0, 0x10, 0xC7, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE0, 0x10, 0xC4,
0xE1, 0x10, 0xC7, 0xE0, 0x10, 0xCC, 0xE0, 0x10, 0xD1, 0xE0, 0x10, 0xD4, 0xE0, 0x20, 0x09, 0x10,
0xB3, 0x20, 0x19, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0x22, 0x07, 0x12, 0xCC, 0x22, 0x15, 0x0E,
0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB,
0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0xE1, 0x10, 0xAE, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0,
0x10, 0xBA, 0xE1, 0x10, 0xB7, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x05, 0x12, 0x83, 0x22,
0x15, 0x0E, 0x21, 0xE0, 0x10, 0xB3, 0x0E, 0x20, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB, 0xE1, 0x10,
0xAE, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE0, 0x10, 0xBA,
0xE1, 0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x20, 0x09, 0x10, 0x83, 0x20, 0x19,
0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x28, 0xE0, 0x0E,
0x20, 0xE2, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0x21, 0x07, 0x11,
0xB3, 0x21, 0x17, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1,
0x20, 0x09, 0x10, 0x59, 0x20, 0x19, 0x21, 0x07, 0x11, 0x02, 0x21, 0x19, 0x22, 0x07, 0x12, 0xCC,
0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x5D, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55,
0xE0, 0x10, 0x53, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x17, 0xE1, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0,
0x10, 0x5D, 0xE0, 0x10, 0x5F, 0xE1, 0x20, 0x09, 0x10, 0x22, 0x20, 0x19, 0x21, 0x07, 0x11, 0x22,
0x21, 0x17, 0x22, 0x05, 0x12, 0x59, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x25,
0xE0, 0x10, 0x22, 0xE0, 0x10, 0x1F, 0xE0, 0x10, 0x1D, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0xE1,
0x10, 0x1F, 0xE0, 0x10, 0x22, 0xE0, 0x10, 0x25, 0xE0, 0x10, 0x27, 0xE0, 0x21, 0x07, 0x11, 0xB3,
0x21, 0x17, 0x22, 0x05, 0x12, 0x02, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x10, 0x25, 0x0E, 0x20, 0xE0,
0x10, 0x22, 0xE0, 0x10, 0x1F, 0xE0, 0x10, 0x1D, 0xE1, 0x10, 0x1F, 0xE0, 0x10, 0x22, 0x21, 0x07,
0x11, 0x59, 0x21, 0x17, 0xE0, 0x10, 0x25, 0xE0, 0x10, 0x27, 0xE1, 0x10, 0x25, 0xE0, 0x10, 0x22,
0xE0, 0x10, 0x1F, 0xE0, 0x10, 0x1D, 0x21, 0x07, 0x11, 0x22, 0x21, 0x17, 0x22, 0x07, 0x12, 0x59,
0x22, 0x15, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE0, 0x10, 0x1F, 0xE0, 0x10, 0x22, 0xE0, 0x10, 0x25,
0xE0, 0x10, 0x27, 0xE1, 0x10, 0x25, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0xE0, 0x10, 0x22, 0xE0,
0x10, 0x1F, 0xE0, 0x10, 0x1D, 0xE1, 0x10, 0x1F, 0xE0, 0x10, 0x22, 0xE0, 0x20, 0x09, 0x10, 0x59,
0x20, 0x19, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0x22, 0x05, 0x12, 0x02, 0x22, 0x17, 0x0E, 0x21,
0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x5D, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x55, 0x21, 0x07, 0x11, 0x59,
0x21, 0x17, 0xE0, 0x10, 0x53, 0xE1, 0x10, 0x55, 0xE0, 0x10, 0x59, 0xE0, 0x10, 0x5D, 0xE0, 0x10,
0x5F, 0xE0, 0x20, 0x09, 0x10, 0x83, 0x20, 0x19, 0x21, 0x07, 0x11, 0x22, 0x21, 0x17, 0x22, 0x07,
0x12, 0xB3, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0,
0x10, 0x7F, 0xE0, 0x10, 0x7C, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0xE1, 0x10, 0x7F, 0xE0, 0x10,
0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17,
0x22, 0x05, 0x12, 0x59, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10, 0x83, 0x0E, 0x20, 0xE0, 0x10, 0x7F,
0xE0, 0x10, 0x7C, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0x21, 0x07, 0x11, 0x59,
0x21, 0x17, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10,
0x7C, 0xE0, 0x21, 0x07, 0x11, 0x22, 0x21, 0x17, 0x22, 0x05, 0x12, 0x02, 0x22, 0x17, 0x0E, 0x28,
0xE0, 0x10, 0x7F, 0x0E, 0x20, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10,
0x87, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C,
0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0x21, 0x07, 0x11, 0xB3, 0x21, 0x17, 0x22,
0x07, 0x12, 0xB3, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x10, 0x8A, 0x0E, 0x20, 0xE1, 0x10, 0x87, 0xE0,
0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0, 0x10, 0x7C, 0xE0, 0x21, 0x07, 0x11, 0x59, 0x21, 0x17, 0xE0,
0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x10, 0x87, 0xE0, 0x20,
0x09, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x05, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x30, 0xE0,
0x0E, 0x20, 0xE5, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE5, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19,
0x22, 0x05, 0x12, 0xB3, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0xCC,
0x21, 0x17, 0xE6, 0x10, 0xCC, 0x20, 0x19, 0x30, 0x64, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22,
0x05, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xD1, 0xE0, 0x10, 0xCC,
0xE0, 0x10, 0xC7, 0xE0, 0x10, 0xC4, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE1, 0x10, 0xC7, 0xE0,
0x10, 0xCC, 0xE0, 0x10, 0xD1, 0xE0, 0x10, 0xD4, 0xE1, 0x20, 0x09, 0x10, 0x22, 0x20, 0x1B, 0x21,
0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x07, 0x12, 0xB3, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20,
0xE2, 0x10, 0x25, 0xE0, 0x10, 0x22, 0xE0, 0x10, 0x1F, 0xE0, 0x10, 0x1D, 0x21, 0x09, 0x11, 0xCC,
0x21, 0x17, 0xE1, 0x10, 0x1F, 0xE0, 0x10, 0x22, 0xE0, 0x10, 0x25, 0xE0, 0x10, 0x27, 0xE0, 0x20,
0x0B, 0x10, 0x83, 0x20, 0x1B, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x05, 0x12, 0x59, 0x22,
0x15, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0x87, 0xE0, 0x10, 0x83, 0xE0, 0x10, 0x7F, 0xE0,
0x10, 0x7C, 0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE1, 0x10, 0x7F, 0xE0, 0x10, 0x83, 0xE0, 0x10,
0x87, 0xE0, 0x10, 0x8A, 0xE1, 0x20, 0x0B, 0x10, 0xCC, 0x20, 0x1B, 0x21, 0x07, 0x11, 0x22, 0x21,
0x19, 0x22, 0x05, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE2, 0x10, 0xD1, 0xE0,
0x10, 0xCC, 0xE0, 0x10, 0xC7, 0xE0, 0x10, 0xC4, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE1, 0x10,
0xC7, 0xE0, 0x10, 0xCC, 0xE0, 0x10, 0xD1, 0xE0, 0x10, 0xD4, 0xE1, 0x20, 0x0B, 0x10, 0xB3, 0x20,
0x1B, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17, 0x22, 0x05, 0x12, 0xB3, 0x22, 0x15, 0x0E, 0x28, 0xE0,
0x0E, 0x20, 0xE2, 0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0x21, 0x07, 0x11, 0xCC, 0x21,
0x17, 0xE0, 0x10, 0xAB, 0xE1, 0x10, 0xAE, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA,
0xE0, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x05, 0x12, 0x59, 0x22, 0x15, 0x0E, 0x21, 0xE0,
0x10, 0xB7, 0x0E, 0x20, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0x10, 0xAB, 0xE1, 0x10, 0xAE,
0xE0, 0x10, 0xB3, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE0, 0x10, 0xB7, 0xE0, 0x10, 0xBA, 0xE1,
0x10, 0xB7, 0xE0, 0x10, 0xB3, 0xE0, 0x10, 0xAE, 0xE0, 0xFF,
};
+132
View File
@@ -0,0 +1,132 @@
#include "msxgl.h"
// lVGM music track — victory screen.
// Occupies the start of banked segment 29 (bank-2 window 0x8000-0x9FFF,
// via SET_BANK_SEGMENT(2, 29)). MSX-Music (YM2413/OPLL), 60 Hz, looping.
// 1966 bytes; the rest of the 8KB segment is unused padding.
// Access: LVGM_Play((const u8*)0x8000, TRUE) with segment 29 banked in.
const u8 g_MusWin[1966] = {
0x6C, 0x56, 0x47, 0x4D, 0x06, 0x02, 0xF1, 0x16, 0x20, 0xFE, 0x26, 0x05, 0x17, 0x50, 0x27, 0x05,
0x18, 0xC0, 0x28, 0x01, 0x56, 0x00, 0x0E, 0x20, 0x10, 0x83, 0x20, 0x19, 0x30, 0x44, 0x11, 0x83,
0x21, 0x17, 0x31, 0xC4, 0x12, 0x83, 0x22, 0x13, 0x32, 0xE4, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5,
0x21, 0x07, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x07, 0x11,
0x22, 0x21, 0x19, 0x22, 0x03, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE4, 0x21,
0x09, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0x22, 0x20, 0x1B, 0x21, 0x07, 0x11, 0x83,
0x21, 0x17, 0x22, 0x05, 0x12, 0x83, 0x22, 0x13, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07,
0x11, 0xCC, 0x21, 0x17, 0xE6, 0x20, 0x0B, 0x10, 0x83, 0x20, 0x19, 0x21, 0x07, 0x11, 0x22, 0x21,
0x19, 0x22, 0x03, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11,
0xCC, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0x22, 0x20, 0x1B, 0x21, 0x07, 0x11, 0x83, 0x21, 0x17,
0x22, 0x05, 0x12, 0x83, 0x22, 0x13, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE4, 0x21, 0x07, 0x11, 0xCC,
0x21, 0x17, 0xE6, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19, 0x22, 0x03, 0x12, 0x22, 0x22, 0x15, 0x0E,
0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0xCC, 0x21, 0x17, 0xE6, 0x21, 0x07, 0x11, 0x83,
0x21, 0x17, 0x22, 0x05, 0x12, 0x83, 0x22, 0x13, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x07,
0x11, 0xCC, 0x21, 0x17, 0xE5, 0x20, 0x0B, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x07, 0x11, 0x22, 0x21,
0x19, 0x22, 0x03, 0x12, 0x22, 0x22, 0x15, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11,
0xCC, 0x21, 0x17, 0xE6, 0x20, 0x09, 0x10, 0x02, 0x20, 0x1B, 0x21, 0x07, 0x11, 0x22, 0x21, 0x19,
0x22, 0x05, 0x22, 0x15, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0x59, 0x21, 0x19,
0xE6, 0x21, 0x09, 0x11, 0xB3, 0x21, 0x19, 0x22, 0x05, 0x12, 0xB3, 0x22, 0x15, 0x0E, 0x21, 0xE0,
0x0E, 0x20, 0xE5, 0x21, 0x09, 0x11, 0x59, 0x21, 0x19, 0xE5, 0x20, 0x0B, 0x10, 0xB3, 0x20, 0x19,
0x21, 0x09, 0x11, 0x22, 0x21, 0x1B, 0x22, 0x05, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x28, 0xE0, 0x0E,
0x20, 0xE5, 0x21, 0x0B, 0x11, 0x59, 0x21, 0x1B, 0xE6, 0x20, 0x09, 0x10, 0x83, 0x20, 0x19, 0x21,
0x0B, 0x11, 0xB3, 0x21, 0x1B, 0x22, 0x07, 0x12, 0xB3, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20,
0xE5, 0x21, 0x0B, 0x11, 0x59, 0x21, 0x1B, 0xE6, 0x20, 0x09, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x0B,
0x11, 0x22, 0x21, 0x1D, 0x22, 0x07, 0x12, 0x22, 0x22, 0x17, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE4,
0x21, 0x0D, 0x11, 0x59, 0x21, 0x1D, 0xE6, 0x21, 0x0D, 0x11, 0xB3, 0x21, 0x1D, 0x22, 0x07, 0x12,
0xB3, 0x22, 0x17, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0D, 0x11, 0x59, 0x21, 0x1D, 0xE6,
0x21, 0x0D, 0x11, 0x22, 0x21, 0x1F, 0x22, 0x07, 0x12, 0x22, 0x22, 0x19, 0x0E, 0x28, 0xE0, 0x0E,
0x20, 0xE5, 0x21, 0x0F, 0x11, 0x59, 0x21, 0x1F, 0xE6, 0x21, 0x0F, 0x11, 0xB3, 0x21, 0x1F, 0x22,
0x09, 0x12, 0xB3, 0x22, 0x19, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE4, 0x21, 0x0F, 0x11, 0x59, 0x21,
0x1F, 0xE6, 0x20, 0x09, 0x21, 0x0F, 0x11, 0x22, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x83, 0x22, 0x17,
0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x11, 0x59, 0x21, 0x1F, 0xE6, 0x10, 0x22, 0x20,
0x1B, 0x21, 0x0F, 0x11, 0xB3, 0x21, 0x1F, 0x22, 0x07, 0x12, 0x22, 0x22, 0x19, 0x0E, 0x21, 0xE0,
0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x11, 0x59, 0x21, 0x1F, 0xE5, 0x20, 0x0B, 0x10, 0x59, 0x20, 0x1B,
0x21, 0x0F, 0x11, 0xFF, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x83, 0x22, 0x17, 0x0E, 0x28, 0xE0, 0x0E,
0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x0B, 0x10, 0x22, 0x20, 0x1B, 0x21, 0x0F, 0x21,
0x1F, 0x22, 0x07, 0x12, 0x22, 0x22, 0x19, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21,
0x1F, 0xE6, 0x20, 0x0B, 0x10, 0x02, 0x20, 0x1B, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x83,
0x22, 0x17, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE5, 0x21, 0x0F, 0x21,
0x1F, 0x22, 0x07, 0x12, 0x22, 0x22, 0x19, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21,
0x1F, 0xE6, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x83, 0x22, 0x17, 0x0E, 0x28, 0xE0, 0x0E,
0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x0B, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x0F, 0x21,
0x1F, 0x22, 0x07, 0x12, 0x22, 0x22, 0x19, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE4, 0x21, 0x0F, 0x21,
0x1F, 0xE6, 0x20, 0x09, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x83,
0x22, 0x17, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x21, 0x0F, 0x21,
0x1F, 0x22, 0x07, 0x12, 0x22, 0x22, 0x19, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21,
0x1F, 0xE6, 0x20, 0x09, 0x10, 0x83, 0x20, 0x19, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x83,
0x22, 0x17, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE4, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x09, 0x10,
0xB3, 0x20, 0x19, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x07, 0x12, 0x22, 0x22, 0x19, 0x0E, 0x21, 0xE0,
0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x09, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x0F,
0x21, 0x1F, 0x22, 0x09, 0x12, 0x83, 0x22, 0x17, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F,
0x21, 0x1F, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x07, 0x12, 0x22, 0x22, 0x19, 0x0E, 0x21, 0xE0,
0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x83,
0x22, 0x17, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x21, 0x0F, 0x21,
0x1F, 0x22, 0x07, 0x12, 0x22, 0x22, 0x19, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21,
0x1F, 0xE5, 0x20, 0x09, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x02, 0x22, 0x19, 0x0E, 0x30,
0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12,
0x83, 0x22, 0x19, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x10, 0x22,
0x20, 0x1B, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x02, 0x22, 0x1B, 0x0E, 0x28, 0xE0, 0x0E,
0x20, 0xE4, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x0B, 0x12, 0x83, 0x22,
0x1B, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x21, 0x0F, 0x21, 0x1F,
0x22, 0x0B, 0x12, 0x59, 0x22, 0x19, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F,
0xE6, 0x20, 0x0B, 0x20, 0x1B, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x02, 0x22, 0x1B, 0x0E,
0x21, 0xE0, 0x0E, 0x20, 0xE4, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x0B, 0x10, 0x02, 0x20, 0x1B,
0x21, 0x0F, 0x21, 0x1F, 0x22, 0x0B, 0x12, 0x59, 0x22, 0x19, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5,
0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x0B, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x0F, 0x21, 0x1F, 0x22,
0x09, 0x12, 0x02, 0x22, 0x1B, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE5,
0x20, 0x09, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x0B, 0x12, 0x59, 0x22, 0x19,
0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x09, 0x10, 0x83, 0x20,
0x19, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x02, 0x22, 0x1B, 0x0E, 0x21, 0xE0, 0x0E, 0x20,
0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x09, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x0F, 0x21, 0x1F,
0x22, 0x0B, 0x12, 0x59, 0x22, 0x19, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F,
0xE5, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x02, 0x22, 0x1B, 0x0E, 0x21, 0xE0, 0x0E, 0x20,
0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x09, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x0F, 0x21, 0x1F,
0x22, 0x0B, 0x12, 0x83, 0x22, 0x19, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F,
0xE6, 0x20, 0x09, 0x10, 0x02, 0x20, 0x1B, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x22, 0x22,
0x1B, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE4, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x21, 0x0F, 0x21, 0x1F,
0x22, 0x0B, 0x12, 0x83, 0x22, 0x19, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F,
0xE6, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x22, 0x22, 0x1B, 0x0E, 0x21, 0xE0, 0x0E, 0x20,
0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x0B, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x0F, 0x21, 0x1F,
0x22, 0x0B, 0x12, 0x83, 0x22, 0x19, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE4, 0x21, 0x0F, 0x21, 0x1F,
0xE6, 0x20, 0x09, 0x10, 0xB3, 0x20, 0x19, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x22, 0x22,
0x1B, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x21, 0x0F, 0x21, 0x1F,
0x22, 0x0B, 0x12, 0x83, 0x22, 0x19, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F,
0xE5, 0x20, 0x09, 0x10, 0x83, 0x20, 0x19, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x22, 0x22,
0x1B, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x09, 0x10, 0x59,
0x20, 0x19, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x0B, 0x12, 0x83, 0x22, 0x19, 0x0E, 0x30, 0xE0, 0x0E,
0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x09, 0x10, 0x83, 0x20, 0x19, 0x21, 0x0F, 0x21,
0x1F, 0x22, 0x09, 0x12, 0x22, 0x22, 0x1B, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21,
0x1F, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x0B, 0x12, 0x83, 0x22, 0x19, 0x0E, 0x28, 0xE0, 0x0E,
0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x22, 0x22,
0x1B, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x21, 0x0F, 0x21, 0x1F,
0x22, 0x0B, 0x12, 0x83, 0x22, 0x19, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE4, 0x21, 0x0F, 0x21, 0x1F,
0xE6, 0x20, 0x09, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x22, 0x22, 0x1B, 0x0E, 0x21, 0xE0,
0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x0B, 0x12, 0x83,
0x22, 0x19, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x19, 0x21,
0x0F, 0x21, 0x1F, 0x22, 0x09, 0x12, 0x22, 0x22, 0x1B, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE4, 0x21,
0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x09, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x0B,
0x22, 0x1B, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x09, 0x10,
0x22, 0x20, 0x1B, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x0B, 0x12, 0xB3, 0x22, 0x1B, 0x0E, 0x21, 0xE0,
0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE5, 0x20, 0x0B, 0x10, 0x59, 0x20, 0x1B, 0x21, 0x0F,
0x21, 0x1F, 0x22, 0x0B, 0x12, 0x22, 0x22, 0x1D, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F,
0x21, 0x1F, 0xE6, 0x20, 0x0B, 0x10, 0x83, 0x20, 0x1B, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x0D, 0x12,
0xB3, 0x22, 0x1D, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x21, 0x0F,
0x21, 0x1F, 0x22, 0x0D, 0x12, 0x22, 0x22, 0x1D, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F,
0x21, 0x1F, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x0D, 0x12, 0xB3, 0x22, 0x1D, 0x0E, 0x21, 0xE0,
0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x0B, 0x10, 0x22, 0x20, 0x1B, 0x21, 0x0F,
0x21, 0x1F, 0x22, 0x0D, 0x12, 0x22, 0x22, 0x1F, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F,
0x21, 0x1F, 0xE6, 0x20, 0x0B, 0x10, 0x02, 0x20, 0x1B, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x0F, 0x12,
0xB3, 0x22, 0x1F, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE4, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x21, 0x0F,
0x21, 0x1F, 0x22, 0x0F, 0x12, 0x02, 0x22, 0x1F, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F,
0x21, 0x1F, 0xE6, 0x20, 0x0B, 0x10, 0xCC, 0x20, 0x19, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x0F, 0x12,
0x83, 0x22, 0x1F, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x09,
0x10, 0xB3, 0x20, 0x19, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x0F, 0x12, 0xFF, 0x22, 0x1F, 0x0E, 0x28,
0xE0, 0x0E, 0x20, 0xE4, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x20, 0x09, 0x10, 0x83, 0x20, 0x19, 0x21,
0x0F, 0x21, 0x1F, 0x22, 0x0F, 0x22, 0x1F, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21,
0x1F, 0xE6, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x0F, 0x12, 0x83, 0x22, 0x1F, 0x0E, 0x30, 0xE0, 0x0E,
0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x0F, 0x12, 0xFF, 0x22,
0x1F, 0x0E, 0x21, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0x21, 0x0F, 0x21, 0x1F,
0x22, 0x0F, 0x12, 0x83, 0x22, 0x1F, 0x0E, 0x28, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F,
0xE6, 0x20, 0x09, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x0F, 0x12, 0xFF, 0x22, 0x1F, 0x0E, 0x21, 0xE0,
0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0x22, 0x0F, 0x12, 0x83,
0x22, 0x1F, 0x0E, 0x30, 0xE0, 0x0E, 0x20, 0xE5, 0x21, 0x0F, 0x21, 0x1F, 0xE6, 0xFF,
};
+23
View File
@@ -48,3 +48,26 @@
#define INPUT_KB_UPDATE FALSE
#define INPUT_KB_UPDATE_MIN 0
#define INPUT_KB_UPDATE_MAX 8
// Music: screen tracks are lVGM (MSX-Music / YM2413 / OPLL); sound effects are
// ayFX on the PSG, mixed over the FM music each frame. PSG is in buffered
// (indirect) mode so ayFX writes g_PSG_Regs and PSG_Apply() flushes it once a
// frame (the tracks carry no PSG data, so the PSG belongs entirely to the SFX).
#define PSG_CHIP PSG_INTERNAL
#define PSG_ACCESS PSG_INDIRECT
#define PSG_USE_NOTES FALSE
#define PSG_USE_EXTRA TRUE
#define PSG_USE_RESUME FALSE
// MSX-Music (the chip the music actually uses).
#define MSXMUSIC_USE_RESUME FALSE
// ayFX sound effects: use the PSG module's register buffer (no PT3 player here).
#define AYFX_BUFFER AYFX_BUFFER_DEFAULT
// lVGM replayer: enable only the chips these tracks target (PSG parser + MSX-Music).
#define LVGM_USE_PSG TRUE // parser kept for clean linking; tracks carry no PSG data
#define LVGM_USE_MSXMUSIC TRUE // MSX-Music (OPLL) — the music format used
#define LVGM_USE_MSXAUDIO FALSE
#define LVGM_USE_SCC FALSE
#define LVGM_USE_SCCI FALSE
#define LVGM_USE_PSG2 FALSE
#define LVGM_USE_OPL4 FALSE
#define LVGM_USE_NOTIFY FALSE
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+5 -1
View File
@@ -2,7 +2,7 @@
ProjName = "mazegame";
ProjModules = [ ProjName ];
LibModules = [ "system", "bios", "vdp", "input" ];
LibModules = [ "system", "bios", "vdp", "input", "psg", "msx-music", "vgm/lvgm_player", "ayfx/ayfx_player" ];
Machine = "2P";
Target = "ROM_KONAMI_SCC";
@@ -15,4 +15,8 @@ AppSignature = true;
AppCompany = "MZ";
AppID = "01";
// The screen music is MSX-Music (YM2413/OPLL). Give openMSX the FM-PAC extension
// so `./build.sh run` has an FM chip (real MSX2+ machines have MSX-Music built in).
EmulMSXMusic = true;
Verbose = true;
Binary file not shown.
+28
View File
@@ -0,0 +1,28 @@
; MSX ayFX sound-effect bank
; sfx_bank.afb — 8 effects, 330 bytes total
; Generated by the MSX Music Generator — https://github.com/jurjen74/msx-music-generator
; Play on MSXgl with: ayFX_PlayBank(id, priority);
id name bytes frames ~time shape
0 catpaws 26 20 0.33s noise + decay
1 catpaws_2 26 20 0.33s noise + decay
2 catpaws_3 26 20 0.33s noise + decay
3 heartbeat_2 38 18 0.30s tone sweep + decay
4 fanfare 39 27 0.45s tone + decay
5 mapunfold 50 29 0.48s noise + decay
6 manilaugh 70 25 0.42s tone sweep + noise + decay
7 tadaaa_2 38 30 0.50s tone sweep + decay
; C names array for msxgl-example/s_mysfx.c (order matches the bank):
const char* g_Names[] = { "catpaws", "catpaws_2", "catpaws_3", "heartbeat_2", "fanfare", "mapunfold", "manilaugh", "tadaaa_2" };
#define SFX_COUNT 8
; Source descriptors (reproducible JSON, one per effect):
[0] catpaws: {"name":"catpaws","segments":[{"frames":2,"tone":null,"toneCurve":"lin","noise":18,"noiseCurve":"lin","vol":[6,3],"volCurve":"exp"},{"frames":3,"tone":null,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":0,"volCurve":"lin"},{"frames":2,"tone":null,"toneCurve":"lin","noise":20,"noiseCurve":"lin","vol":[5,2],"volCurve":"exp"},{"frames":3,"tone":null,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":0,"volCurve":"lin"},{"frames":2,"tone":null,"toneCurve":"lin","noise":19,"noiseCurve":"lin","vol":[7,3],"volCurve":"exp"},{"frames":3,"tone":null,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":0,"volCurve":"lin"},{"frames":2,"tone":null,"toneCurve":"lin","noise":21,"noiseCurve":"lin","vol":[5,1],"volCurve":"exp"},{"frames":3,"tone":null,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":0,"volCurve":"lin"}]}
[1] catpaws_2: {"name":"catpaws","segments":[{"frames":2,"tone":null,"toneCurve":"lin","noise":12,"noiseCurve":"lin","vol":[6,3],"volCurve":"exp"},{"frames":3,"tone":null,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":0,"volCurve":"lin"},{"frames":2,"tone":null,"toneCurve":"lin","noise":14,"noiseCurve":"lin","vol":[5,2],"volCurve":"exp"},{"frames":3,"tone":null,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":0,"volCurve":"lin"},{"frames":2,"tone":null,"toneCurve":"lin","noise":11,"noiseCurve":"lin","vol":[7,3],"volCurve":"exp"},{"frames":3,"tone":null,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":0,"volCurve":"lin"},{"frames":2,"tone":null,"toneCurve":"lin","noise":13,"noiseCurve":"lin","vol":[5,1],"volCurve":"exp"},{"frames":3,"tone":null,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":0,"volCurve":"lin"}]}
[2] catpaws_3: {"name":"catpaws","segments":[{"frames":2,"tone":null,"toneCurve":"lin","noise":8,"noiseCurve":"lin","vol":[6,3],"volCurve":"exp"},{"frames":3,"tone":null,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":0,"volCurve":"lin"},{"frames":2,"tone":null,"toneCurve":"lin","noise":10,"noiseCurve":"lin","vol":[5,2],"volCurve":"exp"},{"frames":3,"tone":null,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":0,"volCurve":"lin"},{"frames":2,"tone":null,"toneCurve":"lin","noise":9,"noiseCurve":"lin","vol":[7,3],"volCurve":"exp"},{"frames":3,"tone":null,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":0,"volCurve":"lin"},{"frames":2,"tone":null,"toneCurve":"lin","noise":11,"noiseCurve":"lin","vol":[5,1],"volCurve":"exp"},{"frames":3,"tone":null,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":0,"volCurve":"lin"}]}
[3] heartbeat_2: {"name":"heartbeat","segments":[{"frames":2,"tone":[80,55],"toneCurve":"exp","noise":null,"noiseCurve":"lin","vol":[13,15],"volCurve":"lin"},{"frames":3,"tone":[55,40],"toneCurve":"exp","noise":null,"noiseCurve":"lin","vol":[15,9],"volCurve":"exp"},{"frames":2,"tone":[110,75],"toneCurve":"exp","noise":null,"noiseCurve":"lin","vol":[12,14],"volCurve":"lin"},{"frames":4,"tone":[75,38],"toneCurve":"exp","noise":null,"noiseCurve":"lin","vol":[14,6],"volCurve":"exp"},{"frames":7,"tone":[38,38],"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":[6,0],"volCurve":"exp"}]}
[4] fanfare: {"name":"fanfare","segments":[{"frames":3,"tone":523,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":[12,14],"volCurve":"lin"},{"frames":3,"tone":659,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":14,"volCurve":"lin"},{"frames":3,"tone":784,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":14,"volCurve":"lin"},{"frames":2,"tone":1047,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":15,"volCurve":"lin"},{"frames":5,"tone":1047,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":[15,11],"volCurve":"lin"},{"frames":3,"tone":1319,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":15,"volCurve":"lin"},{"frames":8,"tone":1319,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":[15,0],"volCurve":"exp"}]}
[5] mapunfold: {"name":"mapunfold","segments":[{"frames":2,"tone":null,"toneCurve":"lin","noise":3,"noiseCurve":"lin","vol":[8,11],"volCurve":"lin"},{"frames":3,"tone":null,"toneCurve":"lin","noise":[3,6],"noiseCurve":"lin","vol":[11,6],"volCurve":"lin"},{"frames":4,"tone":null,"toneCurve":"lin","noise":5,"noiseCurve":"lin","vol":[5,9],"volCurve":"lin"},{"frames":3,"tone":null,"toneCurve":"lin","noise":[4,7],"noiseCurve":"lin","vol":[9,5],"volCurve":"lin"},{"frames":5,"tone":null,"toneCurve":"lin","noise":[6,9],"noiseCurve":"lin","vol":[6,10],"volCurve":"lin"},{"frames":4,"tone":null,"toneCurve":"lin","noise":[8,12],"noiseCurve":"lin","vol":[10,4],"volCurve":"lin"},{"frames":5,"tone":null,"toneCurve":"lin","noise":[12,18],"noiseCurve":"lin","vol":[4,1],"volCurve":"lin"},{"frames":3,"tone":null,"toneCurve":"lin","noise":18,"noiseCurve":"lin","vol":[1,0],"volCurve":"lin"}]}
[6] manilaugh: {"name":"manilaugh","segments":[{"frames":3,"tone":[280,420],"toneCurve":"exp","noise":null,"noiseCurve":"lin","vol":[10,14],"volCurve":"lin"},{"frames":2,"tone":[420,200],"toneCurve":"exp","noise":null,"noiseCurve":"lin","vol":[14,8],"volCurve":"lin"},{"frames":3,"tone":[300,460],"toneCurve":"exp","noise":null,"noiseCurve":"lin","vol":[11,15],"volCurve":"lin"},{"frames":2,"tone":[460,210],"toneCurve":"exp","noise":null,"noiseCurve":"lin","vol":[15,9],"volCurve":"lin"},{"frames":3,"tone":[320,500],"toneCurve":"exp","noise":null,"noiseCurve":"lin","vol":[12,15],"volCurve":"lin"},{"frames":2,"tone":[500,220],"toneCurve":"exp","noise":null,"noiseCurve":"lin","vol":[15,10],"volCurve":"lin"},{"frames":4,"tone":[350,600],"toneCurve":"exp","noise":null,"noiseCurve":"lin","vol":[13,15],"volCurve":"lin"},{"frames":6,"tone":[600,150],"toneCurve":"exp","noise":18,"noiseCurve":"lin","vol":[15,0],"volCurve":"exp"}]}
[7] tadaaa_2: {"name":"tadaaa","segments":[{"frames":3,"tone":523,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":[13,15],"volCurve":"lin"},{"frames":6,"tone":523,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":15,"volCurve":"lin"},{"frames":2,"tone":null,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":0,"volCurve":"lin"},{"frames":3,"tone":[523,784],"toneCurve":"exp","noise":null,"noiseCurve":"lin","vol":[13,15],"volCurve":"lin"},{"frames":10,"tone":784,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":15,"volCurve":"lin"},{"frames":6,"tone":784,"toneCurve":"lin","noise":null,"noiseCurve":"lin","vol":[15,0],"volCurve":"exp"}]}
+29
View File
@@ -0,0 +1,29 @@
#include "msxgl.h"
// ayFX sound-effect bank (8 effects, PSG). Lives in fixed main ROM so the
// H_TIMI music/SFX interrupt can read it without any bank switching.
// ids: 0-2 catpaws (movement), 4 fanfare (finish), 5 mapunfold (M),
// 6 manilaugh (C), 7 tadaaa (screen keypress). See sfx/sfx_bank.txt.
const u8 g_SFXBank[330] = {
0x08, 0x0F, 0x00, 0x27, 0x00, 0x3F, 0x00, 0x57, 0x00, 0x7B, 0x00, 0xA0, 0x00, 0xD0, 0x00, 0x14,
0x01, 0x56, 0x12, 0x13, 0x90, 0x90, 0x90, 0x55, 0x14, 0x12, 0x90, 0x90, 0x90, 0x57, 0x13, 0x13,
0x90, 0x90, 0x90, 0x55, 0x15, 0x11, 0x90, 0x90, 0x90, 0xD0, 0x20, 0x56, 0x0C, 0x13, 0x90, 0x90,
0x90, 0x55, 0x0E, 0x12, 0x90, 0x90, 0x90, 0x57, 0x0B, 0x13, 0x90, 0x90, 0x90, 0x55, 0x0D, 0x11,
0x90, 0x90, 0x90, 0xD0, 0x20, 0x56, 0x08, 0x13, 0x90, 0x90, 0x90, 0x55, 0x0A, 0x12, 0x90, 0x90,
0x90, 0x57, 0x09, 0x13, 0x90, 0x90, 0x90, 0x55, 0x0B, 0x11, 0x90, 0x90, 0x90, 0xD0, 0x20, 0xAD,
0x76, 0x05, 0xAF, 0xF2, 0x07, 0x8F, 0xAC, 0x51, 0x09, 0xA9, 0xED, 0x0A, 0xAC, 0xF9, 0x03, 0xAE,
0xD3, 0x05, 0x8E, 0xAB, 0x4F, 0x07, 0xA8, 0x2B, 0x09, 0xA6, 0x80, 0x0B, 0x86, 0x85, 0x84, 0x83,
0x82, 0x81, 0x80, 0xD0, 0x20, 0xAC, 0xD6, 0x00, 0x8D, 0x8E, 0xAE, 0xAA, 0x00, 0x8E, 0x8E, 0xAE,
0x8F, 0x00, 0x8E, 0x8E, 0xAF, 0x6B, 0x00, 0x8F, 0x8F, 0x8E, 0x8D, 0x8C, 0x8B, 0xAF, 0x55, 0x00,
0x8F, 0x8F, 0x8F, 0x8D, 0x8B, 0x89, 0x86, 0x84, 0x82, 0x80, 0xD0, 0x20, 0x58, 0x03, 0x1B, 0x1B,
0x59, 0x05, 0x56, 0x06, 0x55, 0x05, 0x16, 0x18, 0x19, 0x59, 0x04, 0x57, 0x06, 0x55, 0x07, 0x56,
0x06, 0x57, 0x07, 0x58, 0x08, 0x19, 0x5A, 0x09, 0x5A, 0x08, 0x58, 0x09, 0x56, 0x0B, 0x54, 0x0C,
0x14, 0x53, 0x0E, 0x53, 0x0F, 0x52, 0x11, 0x51, 0x12, 0x11, 0x11, 0x10, 0xD0, 0x20, 0xAA, 0x90,
0x01, 0xAC, 0x46, 0x01, 0xAE, 0x0A, 0x01, 0x8E, 0xA8, 0x2F, 0x02, 0xAB, 0x75, 0x01, 0xAD, 0x2D,
0x01, 0xAF, 0xF3, 0x00, 0x8F, 0xA9, 0x15, 0x02, 0xAC, 0x5E, 0x01, 0xAE, 0x18, 0x01, 0xAF, 0xE0,
0x00, 0x8F, 0xAA, 0xFC, 0x01, 0xAD, 0x40, 0x01, 0xAE, 0x0B, 0x01, 0xAE, 0xDF, 0x00, 0xAF, 0xBA,
0x00, 0x4F, 0x12, 0x2C, 0xF6, 0x00, 0x29, 0x45, 0x01, 0x26, 0xAC, 0x01, 0x23, 0x35, 0x02, 0x20,
0xEA, 0x02, 0xD0, 0x20, 0xAD, 0xD6, 0x00, 0x8E, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x90,
0x90, 0x8D, 0xAE, 0xAF, 0x00, 0xAF, 0x8F, 0x00, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F,
0x8F, 0x8F, 0x8F, 0x8C, 0x89, 0x86, 0x83, 0x80, 0xD0, 0x20,
};