Replay-verified Touhou 6 gameplay semantics for zero-knowledge execution.
The goal is to prove that a private frame-input stream advances a committed Touhou Koumakyou v1.02h gameplay state machine from a canonical initial state to an accepted result. Before freezing a zkVM guest, the project must first make that state machine agree frame-for-frame with the shipped 32-bit Windows game.
Evidence boundary: an address-bound Wine probe now matches the shipped executable and Linux reference for a 50-field semantic gameplay projection over the first 2,000 frames of one tracked replay. An enlarged raw-bit projection now also matches Enemy/ECL, Item, RNG, Player-collision, and active Enemy-bullet state over 1,200 frames, crossing the first seven Enemy bullets at frame 1180. Three presentation-only fields are excluded with explicit next-read arguments: collided-bullet sprite z, Item offscreen-indicator state, and ANM sprite load handles. This is finite differential evidence, not whole-state equivalence. The current runner remains an instrumented reference harness, not final proof semantics.
The first proof-oriented executable slice is now present under zkvm/: an
integer-only implementation of the PC24 player-position update whose inputs and
outputs remain raw binary32 bits. It matches 1,999 consecutive transitions
derived from the retail anchor. The current enclosing transition no longer
accepts its eleven movement-environment words per frame: from one fixed
character/shot configuration and the preceding state it derives life state,
time-stop carry, invulnerability timer, character speeds, movement bounds,
full-speed rate, and inactive-bomb multipliers. An OpenVM v2.0.1 RV32IM guest
executes that transition and publishes a full SHA-256 commitment to its private
input masks and computed endpoint. Its tracked application proof verifies
against the exact guest commitment. This freezes a narrow, fail-closed
full-speed/no-bomb/no-hit profile, not a complete Player guest or a formal
refinement theorem.
The next Player refinement is also executable and proven through the shooting
cadence boundary. A separate integer-only crate derives focus,
previousFrameInput, fire-timer start/tick/reset behavior, and 1,590
SpawnBullets(player, timer) requests from the same anchor and replay masks.
All 1,999 transitions agree with the 46-field retail/reference oracle. Its
OpenVM proof keeps the private payload at 4,018 bytes and meters 211,737,944
cells. Bullet-slot allocation and the four character/shot callback geometries
are deliberately not part of that enclosing cadence claim.
The callback boundary is now refined one step further for Reimu A ranks 1--3.
An address-bound retail hook and matching reference instrumentation record all
80 pre/post slot states, dormant carry fields, active bullet geometry, and ANM
request projection at every callback. All 1,590 calls and 422 initialized
bullets match exactly. A local integer-only transition derives timer gates,
power rank, lowest-free-slot allocation, and raw geometry; a fourth OpenVM
application proof executes the complete batch and publicly commits every
initialized output. This is intentionally a collection of local function
transitions: within that local proof, independently observed pre-call states
are not linked by UpdatePlayerBullets, ANM reclamation, or Enemy collision.
The first persistent Player/bullet state boundary is now closed for a smaller prefix. Starting from the fixed empty pool at gameplay frame 1, a fifth OpenVM guest consumes only 206 replay masks and derives Player motion, shooting cadence, rank-1 straight-bullet movement, 14-by-14 bounds reclamation, fixed nonterminating ANM/timer effects, and allocation through frame 207. All 35 initializations and 30 reclamations agree with the 50-field retail/reference oracle at every frame. The 436-byte private payload contains no per-frame slot state. The synchronized proof costs 12,192,123 instructions and 474,516,189 metered cells. It deliberately rejects frame 208, where EnemyManager first changes a fired bullet to collided; Enemy/ECL composition is the next soundness boundary.
That boundary is now crossed by an enclosing early-gameplay kernel. A sixth
OpenVM guest consumes 207 replay masks and derives the first five Stage-1
Sub0 timeline spawns, movement, ECL time and angle, in-bounds gating, 200
damage calls, the unique slot-2 bullet collision, first-enemy death, target
selection, and score 390 through frame 208. Retail Wine and the independently
built reference agree over a 225-frame raw Enemy/collision projection. The
438-byte private payload contains no Player, bullet, Enemy, ECL, collision,
life, target, or score witnesses. Its tracked proof costs 19,787,280
instructions and 769,444,525 metered cells. The curved-axis table is selected
only by derived ECL time, but proving it refines the pinned x87 fsincos path
and proving omitted Sub0 shooting effects noninterfering remain explicit
soundness obligations.
The enclosing successor now closes the entire first Stage-1 wave rather than stopping after one hit. A seventh OpenVM guest derives persistent collided- bullet motion and ANM timers plus the remaining four collisions and deaths at frames 213, 219, 224, and 229. Its 480-byte private payload is still only a fixed header and 228 replay masks; the public digest commits every active Player-bullet and Enemy raw-bit projection at every frame. The final state has score 1950, five collided bullets, six active bullets, and no live first-wave Enemy. The proof costs 28,276,048 instructions and 1,104,665,874 metered cells. Retail Wine and the independent reference match the enlarged projection over 260 frames. The seventh kernel stops at frame 229 because death-spawned Item state is not yet retained and first feeds score/power at frame 249; this is the next enforced subsystem boundary rather than an assumed noninterference claim.
The eighth enclosing kernel now closes that feedback boundary. It derives the random-drop cadence across all five deaths, the frame-219 small-power Item spawn, 30 frames of raw-bit movement, Player collection, collision-script slot reclamation, and the frame-249 score/power/subrank writes. Retail Wine and the independent reference match the enlarged Item projection for 300 frames. The 520-byte OpenVM payload still contains only a fixed header and 248 replay masks; the endpoint has score 1960, power 1, subrank 1, two collided bullets, three active bullets, and no active Item. The proof costs 29,288,817 instructions and 1,146,265,201 metered cells. It fails closed before the second Enemy wave, whose RNG/ECL context and Enemy bullets are the next subsystem boundary.
The ninth enclosing kernel crosses that boundary through frame 350. From the
same fixed frame-1 anchor and only 350 replay masks it derives the second
timeline group, six Enemy spawns, ECL timers and movement, five deaths, exact
death-effect RNG consumption, two random-drop Items, allocator cursors, and
score 3910. The Enemy-bullet manager and pool are retained explicitly and are
derived empty throughout this checkpoint; the retail oracle independently
continues to frame 1200 and observes the first seven live Enemy bullets at
frame 1180. The replay-only OpenVM workload executes 28,003,469 instructions
and 1,089,931,880 metered cells and exposes digest
7d70502eb31b1ad8fc13947b8ae68185df92f3799ee62ca033b21c78fd114281.
A first owner-local Lean model now machine-checks why active-only Effect slots
violate one-step noninterference and how a narrow dormant reuse shadow repairs
that modeled allocation step. It is a proof-design result, not a whole-game or
C++-binding theorem. A second small definition decodes the audited target and
D3DX toward-zero x87 profiles and their SoftFloat configuration correspondence
without yet claiming complete arithmetic semantics.
A finite-input exception model separately checks why denormal signaling must
follow opcode priority rather than a single operand-class rule.
Comparison truth tables now cover all eleven status-mask/branch forms at the
244 mapped game comparison sites. A local extraction probe also executes the
verified 117-byte __ftol2 body without retaining proprietary bytes and checks
its EDX:EAX truncation result over a bounded canonical domain; neither result
is yet a code-binding or reachable-range proof.
The 244 comparison addresses and 77 conversion-call addresses are now frozen
in a hash-bound, proprietary-byte-free obligation ledger. Every site remains
explicitly unclassified until it receives either a retained-operation
refinement or an omitted-path noninterference proof.
A derived source/sink ledger conservatively identifies 68 helper calls as
omission candidates and nine as gameplay-retained candidates. For the retained
ECL call, a pinned dispatch audit and Lean bit-vector theorem reduce the
observable result to one of 25 variable IDs (-10025..-10001) or the unchanged
literal path. This narrows a future proof obligation; it does not yet discharge
source/binary correspondence, helper semantics, reachability, or guest binding.
The other eight retained calls are now bound to four point-item score blocks.
A conditional Lean model derives a -4..452 collected-item interval and safe
score arithmetic, while a sealed Linux probe found 2,081 finite corpus
collections inside that interval. A total rational/exceptional model now avoids
assuming that item positions are finite: infinities cannot overlap a finite
player box, while NaN reaches the helper's modeled invalid low-EAX result zero
and selects the bounded top-score branch. A new 95-instruction player-position
audit and total Lean clamp model show that the retail lower/upper clamp restores
the 16..432 center invariant even from either infinity; only NaN bypasses both
assignments. This replaces the blanket finite-player premise with explicit
candidate-not-NaN, writer-completeness, scheduling, binary/x87-binding, and
guest-refinement obligations. A dependent fixed-data audit independently walks
8,384 subroutine instructions from the seven hash-pinned retail ECL files. It
finds no player-position output among 1,844 candidate writers; importantly, all
80 increment/decrement instructions using the two handlers that bypass the
readonly type guard target only four local-variable IDs. Archive extraction,
non-ECL alias completeness, and guest binding are still explicit obligations.
The zkTH06 work lives at the repository root. The reconstructed game engine is kept as a supporting snapshot rather than as this repository's history:
zkvm/contains the first sliced kernel and defines the future guest/public- input boundary, including the OpenVM adapter;formal/records the refinement theorem and machine-checking obligations;arithmetic/contains exact-arithmetic experiments and their evidence boundaries;evidence/contains compact, proprietary-byte-free summaries of sealed local retail/reference comparisons;docs/records the differential methodology and milestone evidence;paper/is the living English LaTeX research paper and decision history;tools/contains format and comparison tooling owned by zkTH06;replays/contains redistributable fixtures and provenance for local-only inputs;reference/contains the adapted TH06 engine and compatibility runner.
The reference/ snapshot has no imported Git ancestry. Its exact upstream
revisions, modifications, licenses and limitations are recorded in
reference/PROVENANCE.md.
This repository contains no original game executable, DAT archive, music, third-party replay file, or other proprietary game asset.
Given public commitments to:
- the target executable/data version and gameplay-kernel revision;
- the initial gameplay state and requested route/difficulty;
- a private replay or equivalent frame-input stream;
prove that one canonical state machine applies every input in authentic frame order and reaches an accepted terminal state. Stage transitions, resources, score, rank, RNG and retry state must be derived from live state rather than trusted from replay checkpoints.
This proves valid execution of an input sequence. It does not prove that the inputs were produced by a human or without external tools.
- Strictly parse and validate TH06 v1.02h
.rpyfiles. - Inject the complete replay input mask at the authentic calc-chain priority.
- Export canonical binary frame snapshots using stable slot identities and raw floating-point bits.
- Compare a pinned replay frame by frame against an original/exact-reference exporter and report the first differing subsystem and field.
- Pass Normal no-miss/no-bomb, death/bomb, dialogue, all shot types, Lunatic, spell-heavy and Extra cases.
The first 2,000-frame Normal anchor is complete for its enhanced 50-field
projection. Its enclosing player-state subprojection drives 1,999 transitions
from a fixed post-calc anchor, checks the life-state change at frame 240, and
now checks focus/previous-input/fire-timer recurrence and the nested local
SpawnBullets and Player-bullet lifecycle projections. A linked prefix now
carries the complete 80-slot pool through all five deaths of the first Enemy
wave at frame 229 without slot-, collision-, or Enemy-state witnesses.
The full canonical subsystem projection and broader replay matrix remain open.
- Constrain stage 2–6 replay snapshots to state derived from prior stages.
- Remove rendering/audio/presentation work only after dependency and shared-RNG shadow behavior has been proven equivalent.
- Freeze a versioned transition function and acceptance predicate.
- Profile entity/state width, x87-compatible arithmetic/trigonometry and proof length independently.
- Prove one spell, then Stage 6, then a complete stage.
- Chunk or recursively aggregate a full six-stage run only after the smaller statements pass differential tests.
The stronger shooting-cadence 1,999-transition profile is now proven with
OpenVM v2.0.1. Its private payload is still 4,018 bytes rather than the earlier
95,976-byte environment transcript, and the hash-bound statement costs
211,737,944 metered cells. Local application proving took 75.57 seconds with
49,103,488 KiB peak RSS, and verification took 0.11 seconds. The tracked proof,
verifying key, vm executable, source bindings, negative checks, and explicit
claim boundary are in
evidence/openvm-player-shooting-1999-v1.json.
The next local slice is also proven: 1,590 Reimu-A callback invocations produce
422 hash-committed bullets in 7,394,181 instructions and 309,751,316 metered
cells. Proving took 98.60 seconds with 51,626,064 KiB peak RSS and verification
took 0.12 seconds. The exact proof bundle and its cross-call limitation are in
evidence/openvm-player-bullets-1590-v1.json.
The enclosing successor now derives 206 consecutive Player/bullet transitions
from a fixed empty pool and 436 bytes of replay-only private input. It initializes
35 bullets, reclaims 30, meters 474,516,189 cells, and has a tracked application
proof described in
evidence/openvm-player-bullet-lifecycle-206-v1.json.
The next enclosing successor crosses the frame-208 boundary with only two more
private bytes. It derives five early Stage-1 enemies, 200 damage calls, one
collision and death, target selection, and score 390 in 19,787,280 instructions
and 769,444,525 cells. Proving took 203.58 seconds with 52,632,376 KiB peak RSS;
verification took 0.22 seconds. The exact bundle and remaining x87/slicing
obligations are in
evidence/openvm-early-gameplay-207-v1.json.
The complete first-wave successor executes 228 transitions from the same fixed
anchor using 480 replay-only private bytes. It derives five collisions/deaths,
253 damage calls, score 1950, and an empty first-wave Enemy pool, while publicly
committing every active bullet and Enemy projection. It meters 1,104,665,874
cells; proving took 299.07 seconds with 51,442,624 KiB peak RSS and verification
took 0.28 seconds. The exact bundle is in
evidence/openvm-first-wave-228-v1.json.
The next successor closes the death-Item feedback at frame 249: one derived
small-power Item increases score to 1960, power to 1, and subrank to 1. Its 248
replay-only transitions meter 1,146,265,201 cells, and the exact proof bundle is
in evidence/openvm-first-item-248-v1.json.
The second-wave successor now reaches frame 350 with derived Enemy/ECL,
death-effect RNG, random-drop Item, and explicitly empty Enemy-bullet state.
Its 724-byte replay-only input meters 1,089,931,880 cells; the exact proof
bundle is in
evidence/openvm-second-wave-349-v1.json.
The next kernel extension targets the first live Enemy-bullet spawn at frame
1180; Player death/respawn and bomb paths remain subsequent subsystem
boundaries.
Deriving the post-calc anchor, external time-stop writers, and
non-full-speed timer paths remain soundness gates rather than hidden premises.
The source audit and compatibility risks are in
docs/initial-analysis.md. Current corpus results
and open gates are in docs/m0-progress.md. Replay
fixture sources, redistribution status and hashes are in
replays/README.md. The required retail-data identity and
local import procedure are in data/README.md. The evolving
canonical digest protocol and its evidence boundary are in
docs/canonical-trace.md; the proof-oriented field
and reuse audit is in
docs/state-projection-audit.md.
The pinned executable's x87/XMM census, control-word evidence, helper-call
surface, and exact-versus-refined arithmetic plan are in
docs/arithmetic-audit.md. The reproducible pinned
SoftFloat differential probe and its deliberately narrower claim are in
arithmetic/README.md; the address-to-proof bridge and
its still-open premises are in
docs/arithmetic-obligations.md. Research
questions, method and evaluation are maintained in
paper/main.tex.
Build the adapted engine on Debian or Ubuntu:
sudo apt install build-essential libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev
cd reference
premake5 gmake --no-asoundlib
make -C build config=release -j4Validate a v1.02h replay without proprietary game data:
reference/th06 --replay-info /path/to/th6_*.rpyCompatibility playback must be launched with a user-supplied Japanese game data directory as its working directory:
/path/to/zkTH06/reference/th06 --headless \
--replay /path/to/th6_ud0001.rpy --max-ticks 200000 \
--canonical-trace trace.canonical.binThe C++ validator checks the byte transform, checksum, version, metadata,
stage bounds, full input masks, frame ordering and playback sentinels. Playback
uses the original callback order, but still restores untrusted per-stage replay
snapshots in compatibility mode. See reference/README.md
for the full protocol.
The target executable is Japanese TH06 v1.02h with SHA-256:
9f76483c46256804792399296619c1274363c31cd8f1775fafb55106fb852245
Never commit or redistribute the supplied game directory, generated traces, or third-party replay corpus.
With a verified local retail directory and a windowed launcher configuration, the address-bound smoke/differential probe is:
python3 tools/run_retail_anchor.py \
--retail-dir /path/to/th06 \
--replay replays/samples/th6_ud002677.rpy \
--output local/retail-anchor.jsonl --frames 2000
(cd /path/to/th06 && /path/to/zkTH06/reference/th06 --headless \
--replay /path/to/zkTH06/replays/samples/th6_ud002677.rpy \
--max-ticks 2000 --trace /path/to/zkTH06/local/reference-anchor.jsonl)
python3 tools/compare_retail_anchor.py \
local/retail-anchor.jsonl local/reference-anchor.jsonl --player-bulletsThe probe requires Wine, Xvfb, GDB, passwordless local sudo for ptrace, and a
Japanese UTF-8 locale. It verifies the executable/DAT hashes before launch and
keeps the commercial files in an isolated temporary runtime directory.
Distributed under GPL-3.0. The imported reference snapshot also contains work
derived from the CC0 reconstruction; see
reference/PROVENANCE.md and LICENSE.