~/kaustubh-lall

~/projects $ cat projects/pulse-court.md

Pulse Court

active

Personal

A from-scratch 1v1 arena game where fixed-point math replaces floats so the same input tape always reproduces the same state hash, built as a foundation for future RL, not a finished game.

Two players fight over a bounceable "core" with one of three original characters. The whole simulation runs on integer fixed-point math instead of floats, resolved in a fixed deterministic order every tick, so a replay hashes identically no matter when or where it runs. Ships a headless C++20 core, a CLI benchmark and replay-verify tool, a native Win32 viewer for human play, and a 929-line test suite, but no AI, no networking, and no team sizes past 1v1 yet.

  • 929-line test suite (tests/sim_tests.cpp): 113 check() call sites across 19 documented categories, source-verified, not re-run this session
  • Fixed 120 Hz deterministic core: int32 fixed-point math (kFixedScale = 1024), FNV-1a state_hash over every authoritative field
  • 800-line core simulation (pulse_sim.cpp) drives both a headless CLI (pulse_headless, 252 lines) and a Win32/GDI viewer (pulse_viewer, 2,445 lines across viewer_win32.cpp + viewer_assets.cpp)
  • 32 original sprite sheets (24 character-state + 8 shared effect/UI), zero third-party art per assets/sprites/README.md

Overview

Pulse Court is a from-scratch, deterministic 1v1 arena game built to be a foundation for reinforcement learning, not a finished game yet. Two players fight over a bounceable "core" instead of a ball, playing one of three original characters (Kite, Vale, Bastion), but the real engineering point sits underneath the gameplay. Every tick of the simulation runs on integer fixed-point math instead of floating point, so the same input tape always produces the same state hash, on any machine, every time, exactly the bit-for-bit reproducibility a reinforcement-learning pipeline needs from its environment and an ordinary floating-point game engine does not hand you for free.

The repo splits that guarantee across three pieces that never touch each other's job: a headless C++20 core (pulse_core) holding the only authoritative simulation state, a command-line tool (pulse_headless) that drives it with scripted input for benchmarking and hash-verifying replays, and a native Win32/GDI viewer (pulse_viewer) for human play and visual QA that reads simulation state but never writes back into it. A 929-line test suite backs the determinism claim. What is not here yet: no AI, no networking, and no team sizes beyond 1v1, and that absence is the point of building the foundation first.

The problem and motivation

A normal game engine does not promise that the same match, replayed from the same recorded inputs, produces the same outcome twice. Frame-rate variance, floating-point rounding that differs across compilers or CPUs, and any wall-clock-driven timer are all enough to make two "identical" replays diverge. That is invisible to a human player and fatal to a reinforcement-learning pipeline, where training and evaluation depend on millions of episodes that all have to mean the same thing when they are replayed, hashed, or compared later. Pulse Court's stated design goal is to build that guarantee before building any AI at all: fixed-point math, a fixed 120 Hz tick, and an explicit deterministic resolution order, so a policy trained later can actually trust the environment underneath it.

The project's own naming history points at where it came from. Two older local folders referenced in its AGENTS.md, "AI Omega Strikers" and a "pulse-zero" variant, predate this repository and were not inspected for this write-up, but the name change and the repo's own disclaimer, "not affiliated with Omega Strikers... uses no copied characters, maps, ability names, or commercial-game tuning," together suggest Pulse Court likely began as an Omega-Strikers-inspired prototype and was deliberately rewritten into original IP, three original characters and an original core-and-goals arena, before this repository's first commit. That is the project's likely journey, not a confirmed one.

Architecture

pulse_core is a pure library with no OS, window, or rendering dependencies. It runs a fixed 120 Hz tick, does all math in int32 fixed-point (kFixedScale = 1024), resolves every tick in an explicit deterministic order (players 0 then 1, gates before players, goals before wall bounces), and folds every authoritative field into an FNV-1a state_hash after each step. A 10 Hz "decision boundary" flag fires every 12 ticks for a future AI policy, but nothing in the codebase reads it yet. pulse_sim.cpp, the core simulation itself, is 800 lines.

Two independent applications consume that same library and nothing else authoritative. pulse_headless links only the core and drives it with a scripted input tape to benchmark, record, and hash-verify replays, no rendering, no AI. pulse_viewer links the core plus user32 and gdi32 only, running its own 60 FPS render loop against the 120 Hz fixed-step accumulator. It can read an optional, non-authoritative StepEvents telemetry buffer (a 16-event fixed-capacity buffer covering strikes, dashes, abilities, bounces, and goals) to trigger animation and VFX, but it never writes back into simulation state. The viewer is the larger of the two consumers by line count, viewer_win32.cpp at 1,275 lines and viewer_assets.cpp at 1,170, against 252 for the headless entry point. A manifest-driven sprite pipeline (32 original sheets, 24 character-state plus 8 shared effect and UI, cleaned by scripts/clean_sprites.py) rounds out the tree, with a procedural fallback if a PNG is missing, so an art or rendering bug can never affect what the simulation actually decided.

Verification and testing

The clearest evidence of the project's priorities is where its test suite spends its effort. Per docs/verification.md, the 929-line suite (tests/sim_tests.cpp) has 113 check() call sites spread across 19 documented categories, and most of them are about proving sameness rather than gameplay balance: fixed-point math, hash stability across at least 100,000 ticks of a scripted input tape, state-restore continuation, mirroring round-trips, replay round-trip and version rejection, and isolating the non-authoritative StepEvents buffer from anything that could affect the hash. Gameplay-specific checks exist too (goals, wall bounces, tunneling prevention, strike and dash rules, per-character abilities, all 324 decoded actions, court geometry), but they sit alongside, not instead of, the correctness-of-replay machinery.

None of this was independently rebuilt or re-run for this write-up. No local build/ directory existed and the CMake configure step needed an approval that did not resolve in session, so every number above is source-verified, read directly out of the test file and docs/verification.md, not confirmed by a fresh run. What exists today is a foundation and a set of tools built to measure it, pulse_headless's benchmark mode, the replay format, the hash-verification path, not a set of results. There is no trained agent yet to benchmark against, so there is nothing to report in a results table beyond the tooling itself.

Decisions and tradeoffs

The single biggest decision in the project is doing all simulation math in integer fixed-point instead of floating point. Floating-point arithmetic is not guaranteed to produce identical results across different compilers, optimization flags, or CPUs, small rounding differences that are irrelevant to a human watching a match are fatal to a system that needs to hash a game state and compare it byte-for-byte later, whether that comparison is a replay-verification check today or a reward signal in a training run tomorrow. Fixed-point math (kFixedScale = 1024, int32 throughout) trades away some numerical convenience, and any future AI-facing observation vector will have to work in the same fixed-point terms as the simulation, for a guarantee that would otherwise be very hard to retrofit afterward: two runs of the same input tape produce the same state_hash, always.

The other real decision was procedural, not architectural: keep correctness fixes and presentation experiments in separate branches so neither blocks the other. On 2026-07-13 a single checkpoint commit held both a set of hitbox, reach, and cooldown correctness fixes and a speculative isometric, pseudo-3D rendering experiment together. The project split them explicitly, the branch-only checkpoint commit says so directly: the correctness fixes went to main, and the isometric work became its own commit on an unmerged origin/isometric-experiment branch, left there because, in that commit's own message, "the arena background art was not authored for true isometric projection." It is a real, working, self-contained diff (64 lines changed in viewer_assets.cpp, 24 in viewer_win32.cpp) that was consciously shelved rather than abandoned mid-effort, and the pattern repeats: two separate "Checkpoint: ... (pre-X)" commits inside a two-day history exist specifically so a risky follow-up change has a known-good state to diff against.

Current limitations

No AI or RL policy exists yet, and that is the point of building the foundation first rather than after. The 10 Hz decision-boundary hook and the headless benchmark tooling are scaffolding for a policy that does not exist, not a working one. Only 1v1 is implemented: GameState holds a fixed-size player array, so adding team sizes is a structural change to the array, the input frame, and the collision loop, not a config flag. There is no networking layer. The game is local-only, two players on one machine over a split WASD and IJKL keyboard. And, as noted above, none of the test-suite or runtime-correctness claims in this write-up were independently rebuilt or re-run this session, they trace to docs/verification.md, README.md, and direct source reading.

Where it is heading

Every direction here comes from the project's own docs, not outside speculation. docs/architecture.md names the 10 Hz decision-boundary flag as existing "for future AI policy refresh," the only concrete forward-looking hook actually in the codebase today. README.md's own "Deferred intentionally" list, repeated verbatim in docs/verification.md, names AI and training, additional team sizes, networking, gamepad support, audio, polish tuning, and commercial-game comparison as the acknowledged backlog. The isometric-perspective experiment has a concrete, stated blocker rather than an open-ended one: it needs the arena background art re-authored for true isometric projection before the existing commit can be rebased onto main.

notes

  • Not independently rebuilt or re-run this session: no local build/ directory existed and the CMake configure step required an approval that did not resolve, so all test-suite and runtime-correctness claims here trace to docs/verification.md, README.md, and direct source reading, not a fresh build.
  • The Omega-Strikers-inspired origin described below is inferred from the project's naming history and its own AGENTS.md references to two older, uninspected local folders, not confirmed by reading those folders directly.
  • No AI/RL policy, no networking, and no team sizes beyond 1v1 exist yet. The decision-boundary hook and headless benchmark tooling are scaffolding for later work, not a working policy or a results table.
← All projects