Skip to content
Northforge Interactive
← Back to journal
PULSE//ZERO

Devlog #002 — Designing PULSE//ZERO's Enemies

How we build enemies in PULSE//ZERO: a behaviour-object architecture, five archetypes that each ask a different question of the player, and an elite system that remixes them. Bosses come later — this is the rank and file.

In Devlog #001 we said the vertical slice exists to prove the Living Grid carries the game. The grid is the stage. This entry is about the cast — the enemies you actually fight. Not bosses yet; those get their own devlog. Today it’s the rank and file: the five archetypes that fill the arena, and the thinking behind them.

The player ship fans a spread of fire across a cyan arena grid as enemies close in from several angles

Reading the floor, not the projectiles — spread fire clears a lane through the swarm.

One question per enemy

A twin-stick arena lives or dies on how its enemies read. In the heat of a run you don’t parse stats — you parse motion. So every enemy in PULSE//ZERO is designed to ask the player exactly one clear question, and to telegraph that question through movement alone.

  • Hunter — “Can you keep moving?” The baseline threat. It accelerates straight at you and never stops. Everything else is designed to contrast against the Hunter, so we tuned it to be perfectly, boringly readable on purpose.
  • Swarm — “Can you control space?” Weak alone, lethal in numbers. It’s a Hunter with random angular jitter layered onto its heading, so a cloud of them feels chaotic and shifting rather than a neat line you can sweep once.
  • Charger — “Are you paying attention?” It drifts slowly until it locks on, then commits to a fast straight-line dash for a fixed window before it has to re-acquire. The whole threat is in the wind-up: sidestep the commit and it sails harmlessly past.
  • Orbiter — “Will you stand still?” It circles you at range and refuses to flee, periodically lunging inward. It punishes camping without ever forcing a panic — the danger is patient.
  • Splitter — “Did you think that was over?” A tankier Hunter that breaks into smaller foes on death. It turns a clean kill into a follow-up, and teaches you to mind your spacing when the thing you just shot was crowding a wall.

Five enemies, five distinct verbs: flee, zone, dodge, reposition, finish. None of them shoot in the slice — the challenge is geometry and timing, not bullet soup. That’s deliberate. We want you reading the floor, not the projectiles.

Behaviour objects: the chassis vs. the brain

Under the hood, an enemy is two things glued together. The chassis (enemy.gd) owns the universal stuff: health, collision, drawing, death, the split-on-death hook. The brain is a separate behaviour object that answers a single question each frame — “what velocity should I have right now?”

class_name EnemyBehavior
extends RefCounted

## Return the velocity the enemy should move with this frame.
func get_velocity(_enemy, _target_position: Vector2, _delta: float) -> Vector2:
    return Vector2.ZERO

That’s the entire contract. The Hunter brain is four lines — steer toward the player. The Swarm brain is the Hunter plus a jitter angle. The Charger is a tiny three-state machine (drift → charge → re-acquire). The Orbiter maintains an orbit angle and rolls a timer for its lunges.

The payoff: a new enemy type is a new behaviour script plus a data file, with zero edits to the chassis. Stats — health, speed, score, radius, the neon colour, how many fragments it drops — all live in a .tres resource:

display_name = "Charger"
max_health = 5.0
move_speed = 420.0
score_value = 175
spawn_cost = 2.0
codex_description = "Winds up, then commits to a fast linear charge. Sidestep it."

This split keeps balance changes honest. Re-tuning the Orbiter’s HP is a one-line data edit, not a code change, so we can iterate on feel without touching logic and re-introducing bugs.

Elites: remixing the roster

Rather than authoring a second tier of bespoke enemies, the slice layers elite modifiers onto the ones we have. An elite is a remix, not a new asset:

  • Fast — markedly quicker.
  • Shielded — soaks the first hits before it starts taking damage.
  • Explosive — detonates on death, so the kill itself becomes a threat.
  • Regenerating — slowly heals if you leave it alone.

Each wears an identifying aura colour so you can read the modifier at a glance, the same way you read the base archetype. A Shielded Orbiter and an Explosive Swarm-pack are genuinely different problems — and we got both for the cost of a colour and a few stat tweaks. Bosses are a separate effort; elites are how the ordinary roster keeps surprising you in the meantime.

Who shows up, and when

Enemies don’t spawn from a fixed script. Each definition carries a spawn_cost — a Swarm is cheap (0.6), a Splitter is expensive (3.0) — and the Adaptive Director spends a budget that grows with the run. That’s what lets pacing breathe: early waves are a few Hunters, later ones are a calculated mix the Director can afford. We’ll dig into the Director’s brain in a future devlog; for now the point is that the roster and the pacing are separate systems, so we can tune one without disturbing the other.

The first time you meet a new type, the game pauses for a beat and shows its codex blurb — one line of character, written into the same data file as its stats. Small touch, but it’s the moment an enemy stops being “a red shape” and becomes “the one that charges.”

The art is the hitbox

One last note on look. Every enemy is drawn with raw vector geometry — no sprites — in over-bright neon that blooms under HDR. That’s not only an aesthetic choice: the silhouette you see is the collision shape. What reads as dangerous on screen is exactly what’s dangerous in the simulation. In a game about reading motion, that honesty matters more than any texture would.

Next time

Five archetypes, an elite remix layer, and a clean seam between data and behaviour — that’s the foundation the rest of the roster grows from. Next we’ll either open up the Adaptive Director that decides who shows up, or step up to the mini-bosses. Either way, we’ll report back. As always: we build in the open.