The first dev update for KURO KISHI. A balance pass that started as one enemy firing too fast and ended somewhere larger, plus a death screen rebuilt from a flat panel.
A burst that was hiding a second fire rate
One enemy type on the second stage was re-firing every 540ms inside a burst. That is 1.85 shots per second, on a stage whose authored floor is nowhere near that fast.
Burst gaps were written straight onto the next-attack timestamp, which bypasses the stage's minimum-shot-gap floor entirely. The floor was set correctly. Nothing was consulting it.
That is the more expensive half of the bug: a value that is right, in a file that is right, that no code path reads. It looks tuned when you open it.
The same shape, three more times
While tracing it, three regular-enemy burst paths turned out to be using a single global salvo-stagger constant — the value authored for the first stage — on every stage, ignoring each stage's own authored stagger.
| enemy | authored | actually firing at |
|---|---|---|
| turret | per stage | 240ms everywhere |
| warden | 380ms | 240ms |
| accretor | ~360ms | 240ms |
The turret was correct on the first stage by coincidence, and wrong anywhere else it appears — the most durable way for a bug to survive review. All three now route through a helper that already existed in the same file and simply was not being called from these sites.
One shot per second, campaign-wide
With the floors finally being read, they turned out to be set too low everywhere. The direction was that most enemies should fire once a second or slower; the later stages were running at more than twice that, per enemy.
| stage | floor before | after | rate before | rate after |
|---|---|---|---|---|
| 1 | 560ms | 1200ms | 1.79/s | 0.83/s |
| 2 | 820ms | 1150ms | 1.22/s | 0.87/s |
| 3 | 520ms | 1080ms | 1.92/s | 0.93/s |
| 4 | 700ms | 1150ms | 1.43/s | 0.87/s |
| 5 | 500ms | 1060ms | 2.00/s | 0.94/s |
| 6 | 480ms | 1040ms | 2.08/s | 0.96/s |
| 7 | 460ms | 1020ms | 2.17/s | 0.98/s |
| 8 | 440ms | 1000ms | 2.27/s | 1.00/s |
The difficulty ladder is intact — earliest stage slowest, last stage tightest. It now tightens above the cap rather than straight through it.
Some things are deliberately exempt. Bosses and mini-bosses never consult the floor; their phase patterns are authored separately. Enemies that fire one ring or one arc once per lifetime are not firing at a rate at all, so a floor would be meaningless on them. Pellets inside a single salvo are shape, not rate.
Arithmetic is not proof
The table above is what the numbers say. Per-enemy inter-shot gaps were then sampled during real runs to check that it is also what the game does.
| stage | enemies tracked | fastest repeat | peak rate |
|---|---|---|---|
| 2 | 14 | 1416ms | 0.71/s |
| 7 | 24 | 1283ms | 0.78/s |
One stage could not be sampled the same way — no regular enemies fired during the window — so its floor is verified by test rather than by observation.
Thinning two windows, not the whole stage
The second stage was reported as too dense in two specific places: the opening, and the window around the mini-boss. The cut was made at those two places rather than spread across the stage.
| wave | at | count | gap | stagger |
|---|---|---|---|---|
| ABYSS MEDUSA | 2s | 8 to 4 | 0.50 to 0.72 | 0.16 to 0.26 |
| TORPEDO SHOAL | 52s | 6 to 4 | 0.78 to 1.02 | 0.17 to 0.24 |
| PRESSURE MINES | 58s | 10 to 5 | 0.42 to 0.66 | 0.10 to 0.16 |
| INK CLOUD | 63s | 6 to 4 | 0.55 to 0.80 | 0.11 to 0.17 |
Gap and stagger were loosened alongside the counts, because a lot of the pressure came from everything attacking at once rather than from the number of bodies on screen. Stage population drops from 98 to 84, concentrated in those two windows.
One wave was left exactly as it was: the densest one in the stage, sitting in neither window anybody complained about. Being the biggest number is not the same as being the problem.
The boss flash was erasing the boss
The hit flash used a fill tint, which paints the sprite one flat colour and removes the silhouette completely. On small enemies that is the point — they pop white and die.
On a boss soaking dozens of hits across half the screen, it reads as the sprite blanking out, and the outline you are aiming at disappears at exactly the moment you are aiming at it.
| before | after | |
|---|---|---|
| duration | 120ms | 72ms |
| flat white pop | 42ms | 26ms |
| remainder of blink | flat red fill | red multiply tint |
| alpha while tinted | 1.00 | 0.72 |
The multiply tint is the real fix rather than the shorter duration: it shifts the boss toward red while preserving its own shading, so the outline survives the flash instead of being painted over. Small-enemy behaviour is untouched.
Found alongside it: a boss killed mid-blink never runs its restore callback, so the next boss inherited the leftover alpha. Alpha is now cleared at spawn rather than trusted to be restored on death.
The death screen shows you where you died
It was a flat dark panel reading KNIGHT FALLEN. It now replays the stage the run actually ended on — the authored parallax, still scrolling, with the cloud layer drifting over it. Stages still in art review fall back to a starfield, and the list of which stages are cleared moved out of a private constant inside the game scene into the shared stage data, so the level and the death screen light up together instead of separately.
The wordmark is YOU'RE DEAD at 38px against the previous 22px — 233 pixels wide on a 480-pixel playfield, up from 137.
The apostrophe had to be drawn as three positioned pieces. The UI face is monospace, so the glyph claimed a full character cell and the wordmark read as YOU ' RE — a hole in the middle of the largest text on screen.
Legibility took two passes, and the first one is the instructive one. A single flat scrim at 0.62 opacity looked right against the brightest stage and crushed the darker ones to near-black, which defeats the entire point of showing you where you died. It is now a light 0.34 tint over the whole frame, plus a bordered panel behind the text block only — darkening what is under the words instead of darkening the picture.
Attract mode plays the campaign
Watch mode used to stop dead at the stage-complete menu, with nobody present to press Continue. Clearing a stage now rolls straight into the next one carrying its powerups, skipping the interlude and the tally, and returns to the title after the finale.
The routing decision was pulled out into a pure function and covered with tests — six cases, watch mode and normal play, with and without an interlude. Stated plainly: the routing is proven and its wiring into the scene is typechecked, but the handoff has not yet been watched end to end. Dying in watch mode still parks on the game-over menu.
