← Devlog
v0.3.1-pre-alpha

Ultraviolet v0.3.1-pre-alpha: enemies fight as a group

Short cycle, one change: the enemies know about each other now.

v0.2.9 made missions procedural — a fresh room graph every time you leave the hub. This build doesn’t touch that. It’s about what happens once you’re in a room with three or four enemies. Until now each of them was a private automaton on its own attack cycle: four next to you meant four independent 1.1-second swing timers firing whenever they came up, and a group fight was a pile-on, not something you could read. v0.3.1 puts a referee in the middle of it.

(There’s also a v0.3.0 in between, tagged separately — a pure refactor, no gameplay change. More on why below.)


An attack referee (EnemyDirector)

The rule is one line: an enemy needs a token to swing, or to close in. There are two tokens (max_attackers, default 2). Everyone else holds a ring at standoff distance and waits.

The token rotates. An enemy claims it on the way in, swings, and drops it the moment the swing ends — so the floor passes around instead of belonging to the same two for the whole fight. A holder that can’t reach you (a ledge, a wall, an ally in the way) loses it on a 3-second lease so it can’t jam the queue, and the enemy that just yielded can’t re-grab it for 0.8 s.

So a crowd reads as a circle you work rather than a heap: two enemies pressing, the third and fourth circling for their turn. Whether two-at-once is right, or whether it should be one — duels with an audience — is the main thing I want feedback on. It’s a knob, not a decision I want to make from the code.

This is also the piece the real boss needs. Legion (Koncept §8) doesn’t attack at all — he only summons — so the whole fight is the crowd. Without a referee he’s just a spawner flooding the screen. That’s v0.3.2.


Honest telegraphs

The blocking enemy used to read the player’s internal “am I attacking” flag — which flips before anything shows on screen. You couldn’t out-read something that wasn’t looking at the screen either.

Now it reacts to what’s visible: a started wind-up, or a combo already in progress from the second hit on. Which means the first hit always lands — the block is a response to the fact that you’re already swinging, not a prediction. There’s a reaction window now (~0.2 s) instead of an instant guard, and per-type knobs for reaction delay and block chance, so a type can be made readable without making it blind.


The dead zone in your own attack

A bug report — “the normal attack feels unresponsive” — turned out to be real and specific. Holding the attack button between the tap window and a full charge — roughly a tenth of a second to half a second, very easy to hit by accident — routed to “cancel” and produced no attack at all. Same for releasing a ready charge with no stamina. Pressing attack and getting silence is the worst thing an attack button can do.

New rule: every release does something. A full charge with stamina gives the charged hit; anything else gives a normal one. The tap window went from 0.12 s to 0.18 s. (This is pure gameplay logic now — a plain function that a test walks across the whole range of hold durations, 0.02 s at a time.)


Spacing, kiting, ledges


Enemy stats are data now

Everything that makes one enemy fight differently from another — detection and attack ranges, speeds, the three attack timings, the whole defence profile — is an exported field on the base enemy now, not a subclass override. A new enemy type should be a scene with different numbers, not a new class. This is the groundwork for elites (§4.2): tougher, faster, longer-reaching versions of what already exists. What stays code is behaviour that’s genuinely different — a second attack mode (the archer’s shot), and Legion not attacking at all. Numbers are data; behaviour is a hook.


Why there’s a v0.3.0 you can’t see

The enemy code got split in two — locomotion and combat — before any of the above. That restructure went out as its own tagged build, v0.3.0, with one correctness criterion: it plays exactly the same. That’s the opposite of this build’s criterion (“it plays better”), and keeping them in separate versions means a regression in v0.3.1 can’t be mistaken for an intended change. v0.3.0 also brought the project from zero automated tests to 15 headless suites, each checked that it fails when the thing it guards is broken — which is how the referee’s one real bug got caught (a deadlock where the token could only be claimed from melee range, but a tokenless enemy was held out at the standoff ring — so once the referee shipped, no aware enemy ever attacked again; every existing test stayed green because each one either spawned the enemy already in range or only checked that it started walking).


Controls

Unchanged from v0.2.9.

ActionKeyboardPad (Xbox)
Move← / →left stick / d-pad
Jump (double)Space / EnterA
Melee (3-hit combo)A — tapRB
Charged attackA — hold ≥ ~0.5 sRB — hold
Third-hit varianthold ↓ heavy · nothing = fast · ↑ = air launchersame
Block / parryD — hold / tap just before the hitLT
Dodge (dash, i-frames)ShiftLB
Shoot (8-way)SRT
ReloadR (auto on empty)Y
Interact / throw-outEX
Pause / menuEscStart
Overview (relic + tattoos, read-only)Tab — holdSelect — hold
Ledge grabautomatic; climb = jump / ↑, drop = ↓same

How to run it

SystemFileNotes
Windowsultraviolet.exe (+ ultraviolet.pck beside it)SmartScreen may warn about an unsigned file — “More info” → “Run anyway”.
Linuxultraviolet.x86_64 (+ .pck beside it)chmod +x, then ./ultraviolet.x86_64.
macOSUltraviolet.appUnsigned: right-click → Open → confirm, or xattr -dr com.apple.quarantine Ultraviolet.app.

Quitting: Esc in the main menu closes the game. In a run, Esc opens the pause menu — quit to the main menu from there.


Still pre-alpha — what’s not here


How to report feedback

BUG — what happened, where (menu / hub / which room), how to reproduce, which platform. For a fight, say how many enemies and which colors.

FEEL — it works, but how does it play? This build is almost entirely feel:

Leave feedback in the comments under the game or this post. Testers with the full guide have a focused checklist there.

Thanks for playing something that’s still mostly rectangles.

— SolarNine · v0.3.1-pre-alpha