Game · JavaScript

recall

One blade. It always comes back.

A small browser arcade game. You have one throwing blade: throw it, call it back early, and catch it. Catching a blade that hit something raises your multiplier; catching it empty while enemies are on the board drops it. Every kill leaves ink on the floor, so each run ends as a painting you can save. Plain JavaScript on a 2D canvas, no engine and no asset files; every sound is synthesized in the browser.

Language: JavaScript Released: v1.1.0, 2026-10-27 Licence: MIT
A game that ships with the tests that break it on purpose.

Play it in the browser on itch.io or on GitHub Pages, or run it locally and verify the task pack:

npm start  ·  npm test  ·  npm run tasks

Walkthrough

The blade flies out, cuts through enemies and curves back to the player.
Out, through the pack, and back. Recorded from a scripted bot run with player damage switched off.
Wave 4: brutes with hit-point pips, ink stains from earlier kills.
Wave 4. Brutes show their remaining hits as pips; every earlier kill is still on the floor.
Charge ring filling around the player, then turning red at full charge.
Task feat-02's reference solution: the charge ring filling, then full. The task asks for this from a producer's brief.

Task pack

Six tasks in the shape used to evaluate AI agents on game development. Each has a brief written as it would arrive, acceptance criteria (input sequence, expected visual outcome, verifiable game state), a reference solution with root-cause notes, and an automated check that runs the real game in headless Chromium. A verifier proves every check fails on the starting state and passes on the solution.

The blade only cuts once

Bug · player report

Hit tracking isn't reset when the blade turns home, so the return leg passes through enemies without hitting them. Invisible against one-hit enemies; obvious on a brute's pips.

Wave clears with enemies alive

Bug · QA ticket

A refactor moved the wave-clear check before queued spawns merge, so killing a splitter last clears the wave while its two children are still chasing you.

Darters are undodgeable

Bug · design review

A "helpful" line re-aims the darter every frame of its wind-up, so the telegraph follows the player and the lunge ignores it.

Throws get eaten after a hit

Bug · player report

The input buffer is cleared every frame, so presses made during hitstop (when the simulation is frozen) are lost. Only reproducible right after hits.

Bank shots

Feature · design spec

Kills after a wall bounce score double with their own feedback. Touches the blade, scoring and the popup system.

Charged throw

Feature · producer request

Hold to charge: twice the distance, no brute slowdown, slower movement while charging. Touches input, the blade, player movement, UI and audio, and moves the throw from press to release without changing a tap.

How the checks see the game

Loading the game with ?harness freezes its clock and exposes a test API: place the player, spawn enemies, script movement, aim and button holds, and step frames at a fixed 1/60 s. Checks drive the same input layer a player does and get the same answer every run.

Honest limits

No touch controls or rebinding yet. Tested in Chromium; Firefox and Safari use only standard canvas and WebAudio APIs but haven't been verified. Built without an engine.