Simple, low-latency Taiko rhythm game compatible with OpenTaiko song libraries
Find a file
joe d04c25e6eb
Release 0.8.0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 21:39:28 +01:00
crates Step 17: the last four verbs, and the wrong path for the million 2026-08-18 21:39:17 +01:00
docs Plan: the arcade's screen, charts that are not TJA, and a note for the modules 2026-08-18 21:39:18 +01:00
scripts Add a deploy script for the Steam Deck 2026-08-17 19:55:54 +01:00
.gitignore Ignore Claude Code's local settings 2026-08-16 22:25:25 +01:00
Cargo.lock Release 0.8.0 2026-08-18 21:39:28 +01:00
Cargo.toml Release 0.8.0 2026-08-18 21:39:28 +01:00
CHANGELOG.md Release 0.8.0 2026-08-18 21:39:28 +01:00
implementation-plan.md Release 0.8.0 2026-08-18 21:39:28 +01:00
LICENSE Step 0: groundwork — format research, toolchain proof, timing spike 2026-08-15 05:51:44 +01:00
plan.md Step 0: groundwork — format research, toolchain proof, timing spike 2026-08-15 05:51:44 +01:00
README.md Release 0.8.0 2026-08-18 21:39:28 +01:00

Simple Taiko

A simple, stable, low-latency Taiko rhythm game that plays songs from an existing OpenTaiko library, in place and unmodified.

Status: v0.8.0 — playable. Browse a library, pick a chart, play it, see your score. Scoring matches OpenTaiko exactly, scores persist, and it plays on keyboard or pad with latency calibration. Song select is a wheel of coloured bars that auditions whatever the cursor rests on. Not yet done: rebinding from inside the game, and the modes listed in docs/v1-scope.md.

See CHANGELOG.md for what each release contains, and implementation-plan.md for where it is going.

Docs

plan.md The idea and the feature list
implementation-plan.md Phases, gates, decisions
docs/format-notes.md TJA / box.def / library layout, measured from a real 506-chart corpus; skin and hit-sound investigation
docs/shapeshifter-jist.md One chart's gimmicks explained by the person who wrote them — the source for how branching really behaves
docs/timing-notes.md Toolchain, audio clock measurements, latency budget
docs/reference-constants.md Judge windows, score formula, OFFSET semantics, read from OpenTaiko source
docs/v1-scope.md What v1 does and, more importantly, does not do
CHANGELOG.md What changed in each release

The format itself is documented outside this repository, and better than we could: IepIweidieng's tja.md gives each command's scope and effect time and records where the simulators disagree. It is the first thing to read before changing the parser.

Layout

crates/tja/       TJA parsing            — pure, no raylib
crates/library/   song tree scanning     — pure, no raylib
crates/timing/    the master clock       — pure, no raylib
crates/play/      judgement and scoring  — pure, no raylib
crates/taiko/     the game               — the only crate that may touch graphics/audio/input

The split is deliberate: it makes the compiler enforce that the parser stays headless and testable against the whole chart corpus without opening a window.

Getting songs

Simple Taiko needs two things: charts, and a pair of drum samples. Neither is bundled — they belong to other people — but both are a download away, and you do not need to install OpenTaiko unless you want its hit sounds.

Songs

The OpenTaiko community soundtrack is a plain git repository of chart folders, which is exactly the layout the scanner expects:

git clone --depth 1 https://github.com/OpenTaiko/OpenTaiko-Soundtrack Songs

That is around 500 songs. --depth 1 matters: the full history is roughly 3.4 GB on top of a 1.2 GB working tree, and none of it is any use for playing.

Hit sounds

The drum samples are not in either OpenTaiko repository — they ship inside the release archive, under publish/Global/HitSounds/. Either take them from a copy of that archive, or supply your own: any directory holding dong.wav and ka.wav works, and the game runs (silently) without them.

The layout

Put both next to the binary and it runs with no arguments:

simple-taiko/
├── taiko
├── Songs/
│   ├── 01 OpenTaiko Chapter I/
│   │   ├── box.def                 category name, colours, description
│   │   ├── 001 - CITYSCAPE/
│   │   │   ├── CITYSCAPE.tja       the chart
│   │   │   ├── CITYSCAPE.ogg       the audio, named by the chart's WAVE: header
│   │   │   ├── cityscape.png       jacket, named by its PREIMAGE: header
│   │   │   └── uniqueID.json       stable song id — what your scores are keyed on
│   │   └── 002 - .../
│   └── 02 OpenTaiko Chapter II/
└── Global/
    └── HitSounds/
        └── Taiko/
            ├── dong.wav
            └── ka.wav

Categories may nest as deeply as you like, and a flat folder of song folders works too.

If OpenTaiko is already installed you need not lay any of this out: its publish/ directory is this layout, with Songs/ and Global/HitSounds/ both in place. Point at it with --base /path/to/OpenTaiko/publish, as below, or drop the taiko binary into publish/ and run it with no arguments — a binary's own directory is one of the places it looks for content. Either way you get the hit sounds too, which --songs on its own does not. Dropping the binary in is the only thing that puts a file in OpenTaiko's folder; the game itself still writes nothing there.

Or point at what you already have

If OpenTaiko is already installed, use it in place — nothing is copied and nothing is written to it:

taiko --base ~/Applications/OpenTaiko/publish

Or mix sources, which is the point of --songs being repeatable:

taiko --songs ~/OpenTaiko-Soundtrack --songs ~/my-charts \
      --hitsounds ~/Applications/OpenTaiko/publish/Global/HitSounds/Taiko

Skins are not used. OpenTaiko's skin repository is another 1.4 GB and Simple Taiko draws its own playfield, so there is no reason to clone it — see docs/format-notes.md for why that format was turned down.

Play

cargo run --bin taiko -- --base ~/Applications/OpenTaiko/publish
cargo run --bin taiko -- --songs "<songs folder>"        # one or more libraries
cargo run --bin taiko -- "<song folder>" oni             # jump straight into one chart

The main menu offers Play, Settings and Quit. Settings holds calibration, audio, video, bindings and an input test.

In song select: Up/Down move, Enter open, Backspace back, C calibrate, Esc back. Playing: F/J hit the drum centre (don), D/K the rim (ka), Esc returns to the list.

The drum works the menus too, as it does on the cabinet: the rim moves — D up, K down — and either don key, F or J, chooses. That matters most to anyone playing on a real drum, where those four inputs are the whole interface. It is why D no longer moves the cursor right: one key cannot both move and adjust on a screen that reads them together.

On a controller the arcade layout applies: every face button is the drum — d-pad for the left hand, A/B/X/Y for the right — and the shoulders are the rim, L1/L2 left and R1/R2 right. The pad drives the menus too.

A library can be nested in categories or a flat folder of song folders; both work.

Settings

~/.config/taiko/config.toml is written on first run, with comments. It holds key and gamepad bindings, the latency offset, colours, volumes and video settings. --list-keys prints every name it accepts.

Calibrate before playing seriously: press C in song select and tap along with the metronome. Every audio setup is late by some constant amount, and that is what the offset removes.

Every control is an action with a list of inputs bound to it, under [input.bindings]. Actions are don_left, don_right, ka_left, ka_right, menu_up, menu_down, menu_page_up, menu_page_down, confirm and back. Inputs are written F, pad:RIGHT_FACE_DOWN, pad2:MIDDLE for one specific device, or axis:LEFT_Y+. Anything you omit keeps its default. --save-config rewrites the file with every binding spelled out, and --list-keys prints everything accepted.

input.gamepad defaults to "all", which listens to every device the system offers. That is deliberate: a controller shows up as several devices — an 8BitDo Ultimate 2C presents its keyboard interface first and the actual joystick second — and the indices shift as things are plugged in and out. Set it to "none", an index, or part of a device name if you need to pin it.

If input is not doing what you expect, run --test-input: it lists every device being listened to and names each key and button as it arrives.

Fonts

Japanese titles need a font that has Japanese in it, and no such font is shipped: they are several megabytes with their own licences, and every desktop already has one. On startup the game asks fontconfig what is installed and picks two — the sans the rest of your desktop uses for the alphabet, and whichever installed font covers the most of your song titles for everything else. Each is checked after loading, so a font that turns out not to have the characters it advertises is dropped rather than drawn as boxes. The startup line names what was chosen:

font       NotoSans-Regular.ttf + HanaMinA.ttf (461 glyphs)

To choose yourself, set path under [font] in the config. It must be a single font file — .ttf or .otf, not a .ttc collection, which cannot be read. Anything it lacks still falls back to the automatic choice.

Steam Deck

scripts/build-deck.sh produces a binary that runs there. Built on a current distribution it will not: Arch's glibc gives the binary a requirement of 2.43 and the Deck has 2.41, so it refuses to start with a message about a version not found. The script builds inside Valve's Steam Runtime "sniper" SDK instead, which targets glibc 2.31, and refuses to finish if the result ever asks for more than the Deck provides.

Confirmed running on a Deck as of 0.5.0. It needs Docker, and downloads about 1.4 GB of SDK the first time. docs/steam-deck-build.md covers how the container is put together and how to check a binary yourself.

scripts/build-deck.sh              # build, and report the glibc it needs
scripts/build-deck.sh --package    # tarball in dist/, ready to copy across

Steam is not required to run the result. Old glibc is a link-time floor rather than a runtime dependency, and X11 and GL are loaded by name rather than linked, so the binary needs nothing on the Deck but libc, libm and libgcc_s. Copy it across with a song library and run it.

Where it looks for things

Content — songs, hit sounds — comes from a content root, the first of these that exists and contains Songs/ or Global/HitSounds/:

  1. --base <dir>
  2. $TAIKO_BASE
  3. the directory holding the executable — so a copied folder is a self-contained install
  4. ~/.local/share/taiko

--songs <dir> (repeatable) and --hitsounds <dir> override individual pieces, and pointing --base at an existing OpenTaiko installation works without copying anything.

Scores and config are always written to ~/.local/share/taiko and ~/.config/taiko, never into the content root — which may be read-only, or somebody else's game folder. The game prints every resolved path at startup.

During development, set TAIKO_BASE once rather than passing --base each time; cargo clean deletes target/, so assets next to the debug binary would not survive.

Development flags: --auto plays it back automatically (a correct parse → clock → judge chain scores every note GOOD), --screenshot <file> captures a frame, --seconds <n> exits after a while, --pick opens the difficulty picker at startup. --help lists everything.

The song-reachability test needs a real library:

TAIKO_BASE=~/Applications/OpenTaiko/publish cargo test --bin taiko

Releasing

scripts/release.sh <version> builds, packages, tags and publishes. Two binaries go out: one built here, and one built against glibc 2.31 in the Steam Runtime SDK for the Steam Deck and older systems. Add --dry to stop before tagging, or --no-deck to skip the second (it needs Docker).

When SteamOS has had a major update, check the Deck's glibc with pacman -Q glibc on the device and update DECK_GLIBC in scripts/build-deck.sh — that number is what the Deck build is checked against. See docs/steam-deck-build.md.

Build

cargo build
cargo test

raylib-sys builds raylib from vendored source, so the first build is slow and needs cmake and a C toolchain.

Tools

# Measure the raw audio clock's resolution and drift.
cargo run --bin clockprobe -- <file.ogg> [seconds] [poll_hz] [buffer_frames]

# Check the smoothed master clock against real hardware (Step 2's gate).
cargo run --bin clocktest -- <file.ogg> [seconds] [poll_hz]

# Parse every chart in a library and report failures (Step 1's gate).
cargo run -p tja --example corpus -- <songs_dir>

# Scan a library into categories and songs.
cargo run -p library --example scan -- <songs_dir> [--tree]

Licence

MIT. No OpenTaiko assets are redistributed here — hit sounds and any skin art are read from the user's own installation at runtime.