The Unity port of KURO KISHI finished its task list today. Hazards and set-pieces went in, then rush enemies, nine behaviour-style attacks, option firing, checkpoints, stars and achievements, three more screens, the music, and the thirteen synthesised sound cues. The player build, which had not linked before, now produces KuroKishi.exe: 191 MB, x64 release, built in 14 seconds.
The useful part, as usual, is what the port found.
Four set-pieces
Each stage's hazard timer now fires its own signature moment, not the same four rocks with a different flash over them.
| set-piece | what it does | evidence |
|---|---|---|
| Crust vice | The crust layers grind inward, hold 5.2s, retract. The lane is a hard bound on the ship. | squeeze curve verified against sampled TypeScript, 66 new parity checks |
| Shatter boulders | Three 20 HP rocks. Each bursts into seven fragments at three damage, into the player's own bullet list. | fragment count, damage, fan angles and origin all asserted |
| Waterfall run | Five curtains marching in from the right. The ship is pushed down while any of them has it. | per-frame push measured at 46.6 to 49.4 px/s against an authored 48 plus or minus 1.4 |
| Dune cruiser | A slow barge with nine deck guns. The hull is armoured until the last gun dies, then slows and can be killed. | fired on with the deck alive to prove it takes nothing, then again once clear |
The bug this feature was built not to repeat
In the browser build, the waterfall's downforce never once worked. The ship's velocity is set every frame from the input vector, and the push was applied before that write, so it was overwritten and discarded on every frame for the life of the feature. A five-curtain set-piece with no gameplay effect, and a code comment asserting the opposite.
When the player was ported, a late-forces hook was added and left deliberately empty, with the trap written down beside it. The waterfall is the first feature to use it. Making it work also exposed a one-frame version of the same mistake: the port wrote position inside the movement step, so any force added afterwards would have landed a frame late. Movement now writes velocity, forces are added, and only then is position integrated, which is the order the original engine uses.
The check failed on all three things it was written to catch
Three times in this port a check has passed without exercising the thing it named. So every assertion in the hazard check is paired with the reason it cannot pass by accident. On its first run it failed on all three.
Two were real. The crust vice's lane was applied a frame late, because the driver moves the player before it steps the world. On the closing ramp that is the difference between riding the rock down and being overtaken by it.
One was the test itself. The waterfall test compared a run with curtains against a control run that had been pushed into the floor clamp just like the run it was controlling for. Both ended at the same Y and the difference was zero. It now asserts the push magnitude directly, which no control run could have done better.
Later checks in the same batch failed the same way on their first run. A gated-rusher test ran Level 3 for archetypes Level 3 does not contain, counted zero, compared zero against zero, and passed. A homing test parked the ship in a corner, where homing at a stationary target draws a straight line and cannot be told from a locked rush. A boulder test counted the whole stage's bullets, so "the ring fired exactly once" came back as eighteen.
One honest gap: the crust vice's dumped samples stop at 6,000 ms, inside the hold, which ends at 8,600. Its close ramp and both ends of the null window are verified to 1e-6, but its retract is reproduced from the source's shape, not compared against a number. Writing a check row for it would mean inventing the expected value from the code under test, which is the definition of decoration.
Found in the original
A second implementation asks questions the first one stopped asking. None of these were changed. Each is reproduced as the browser build has it, and written down.
The pyramid fires one laser, not a fan. Its volley schedules two or three beams a stagger apart, but the fire helper applies the minimum shot gap to each beam, and the gap is several times the stagger, so every beam after the first is swallowed. This is the same bug the turret, warden and accretor comments describe finding and fixing with a bypass. The pyramid was never given it. The check asserts the count is one, and its failure message says what to update if that ever changes deliberately.
Two of the three rush enemies have never rushed. The charger's and the puller's rush is wrapped in a check for "has no authored path", and both of them are given one. That branch has never executed in the shipping game. Both are ported anyway, behind the same shut gate, and the check asserts they stay shut across 15 chargers and 23 pullers. Opening it would be a balance change invented in the port.
Level 7 has no music of its own. There are nine track names and eight files. The resolver walks down to the highest lower-numbered track that exists, so Level 7 plays Level 6's. The check asserts that by name, so a missing track would be reported as "level3 resolved to level2" and not found as silence in a playtest.
Found in the port
- Level 1's parallax never scrolled. The editor bakes the opening backdrop into the scene as real quads, but the runner's layer list holds live references Unity does not serialise. A loaded scene came up with the backdrop drawn and the list empty. Every later stage was fine, because a stage change rebuilds the layers. A static opening sky reads as a style choice, which is why it lasted. It took the crust vice needing to move those layers to expose it.
- Then Level 1's sky stayed behind Level 2. Fixing the first bug surfaced this one. A stage change destroys the previous stage's parallax root, but on the first change there is no root, because the opening backdrop is owned by nothing. Level 1's sky was drawn behind every later stage for the rest of the run.
- Three lives gave three attempts. The counter was clamped at zero and the run failed at zero. The browser build decrements past it and fails at -1. The port had been eating a life.
- Stars were clamped at three. The maximum is five, so a perfect run would have been recorded as a good one.
- Formation clears were never counted. The rule had been ported and parity-checked for weeks and nothing ever called it, so perfect-clear bonuses and their guaranteed capsules did not exist.
- The power key was X, not V. A comment claimed X was how the web build's activate key worked. The shipped default is V. X is on the key's choice list, so it was a legal binding and not a broken one, which is exactly why nothing caught it.
The memory leak that never leaked
Every screenshot this port had produced carried a warning: orphaned enemy views. Six of them, then two, then six again. The detector had been built after a real incident, so it was taken seriously.
It looked for GameObjects whose name began with enemy_ and compared two counts. The enemy bullet views were called enemy_shot. Six orphans, six shots in flight. Two orphans, two shots. Every time, exactly.
Two fixes, because the name collision is the cause and the count comparison is why it survived. Bullets are now shot_enemy, a prefix an enemy view cannot share. And the detector compares by reference against the views the runner actually owns, and names the orphans it finds. Two numbers that happen to agree prove nothing about which objects they are, which is the entire question a leak detector exists to answer.
Four animation helpers that do nothing
Key rebinding turned out to be smaller than expected. The browser build's rebinding is not free rebinding: four actions each cycle a list the game authorises, and anything else snaps back to the default on load. So the options screen cycles with arrows instead of prompting for any key, because binding Q to fire when Q is not on fire's list would be inventing a feature.
The per-frame enemy animation was meant to be the largest remaining visual gap. It is not, because four of the helpers are deliberate no-ops in the shipping game.
| helper | what it actually does |
|---|---|
| holdSteadyScale | sets the base scale |
| animateScalePulse | sets the base scale, ignoring all five arguments |
| animateScuttle | sets the base scale |
| animateRecoilKick | sets the base scale |
Their docblocks say so ("idle swell/shrink disabled") and the arguments are underscore-prefixed as unused. Someone turned the size pulsing off and left the call sites, so seven archetypes have no idle animation at all. The port includes them as holds, because a table with gaps invites someone to fill them in, and the check asserts they stay inert.
What does move, 21 archetypes over nine effects, is checked on its curve and not on its existence. The tentacle axes must stay out of phase (2.6 against 3.1, which makes it a sway and not a throb), and the engine flicker must stay inside 0.8 to 1.0 while never settling, being two incommensurate sines at 14 and 23.
Where the port stands
| check | result |
|---|---|
| Core parity | 1,192 checks |
| Combat rules parity | 372 checks |
| Path parity | 25,636 checks |
| Hazards | 8,299 checks |
| Behaviours | 1,002 checks |
| Stage runner | 3,693 checks, 3,077 spawns |
| Front end | 39 checks |
| Music library | 22 checks |
| Audio coverage | 39 of 39 kinds audible |
| Mission | all 8 stages, run completes |
| Player build | KuroKishi.exe, 191 MB, 14s |
Everything the set-pieces and enemies do is ported and checked, but not everything is drawn. The player ship is still a deliberately ugly magenta placeholder, and the waterfall curtain and the cruiser hull run invisibly, because the browser build draws them procedurally and there is no art to import. A stand-in rectangle would look like finished art in a screenshot, so there deliberately isn't one.
