Part 02 — Knockback and push
You will build: the impulse + damping (the shove that bleeds out, the survivors stage 03 machinery as a feel system) and the composition — the frame where the chase, the crowd, and the hit all push one enemy at once, and the order that makes the hit read. You'll learn: why knockback is a velocity, not a position · the damping rate as the push's personality · the clamp-once rule · and the dead zone the push has to respect (or not).
Why this exists#
The survivors stage 03 installed the machinery — a knockback vector, damped by
move_toward toward zero, added to the chase — and stage 04 gave it a pusher (the hitbox's
shove). This part is the feel of that machinery: why the shove reads as damage, why the
damping rate is a design number, and what happens when three forces share a frame. The
steering shelf's three rules (seek, the weight, the
limit) are the movement version; this is the impact version, where the limit is the hit
and the weight is how hard it lands.
Build it#
A — The impulse, and why it's a velocity#
A knockback is an impulse: a change in velocity, applied once, that the entity then decays. The survivors stage 03 shape, as the feel system:
# in the Enemy (stage 03's machine, the feel reading)
var knockback: Vector2 = Vector2.ZERO
var knockback_resistance := 1 # the damping's divisor — the push's personality
# in _physics_process, beside the chase:
knockback = knockback.move_toward(Vector2.ZERO, 60.0 / knockback_resistance * delta)
fake_velocity += knockback
The three facts that make it read:
- It's a velocity, added to the movement, not a position set. A knockback that moves the
entity (
position += shove) teleports — the golem jumps three metres and the player's eye lost it. A knockback that's a velocity (thefake_velocity += knockback) integrates over frames — the golem is pushed, frame by frame, and the eye tracks the push. The teleport-vs-push distinction is the position-vs-velocity one, the same as the audio shelf's fact-vs-promise: the position is the lie, the velocity is the truth. - It decays, at a rate. The
move_towardtoward zero is the damping — the shove bleeds out over frames instead of persisting. A knockback with no decay is a permanent velocity offset (the golem keeps drifting at the shove's speed forever — the "why is it floating" bug); one with too-fast a decay is a twitch (the shove is over before the eye registers it). The decay rate is the push's length, and the length is the feel. - The resistance divides the decay.
knockback_resistanceis the per-species data (the data-driven shelf's part 01, question 2 — per-kind): a golem at resistance 1 decays at 60 px/s², a brute at resistance 3 decays at 20 — the same shove lands hard on the golem and barely registers on the brute. The resistance is the mass, in the data, and the shove's reading (how far it goes) is the mass made visible. A hit that moves every enemy the same distance is a hit the player can't read; one that moves the golem three metres and the brute one is a hit that reports the target.
B — The composition: three forces, one frame#
Now the frame the part is named for: a golem being chased, in a crowd, hit by the sword —
three forces on one entity, in one _physics_process. The chase (the stage 03 seek, toward
the player), the separation (the stage 03 nudge, off the crowd), and the knockback (the
hit's shove, off the player). The naive composition adds them and clamps each — and the
clamp is the bug:
Predict before you run: the knockback is 90 px/s, the chase is 40, the separation is 30 (weighted). If you clamp each force to a
max_forceof 120 before summing, what's the shove's effective strength — and if you clamp the sum instead, what's it worth?
Each force is under 120, so the per-force clamp does nothing this frame — but the principle
is the bug: a per-force clamp caps every force at the limit, so a hit's shove (the one
force that's supposed to exceed the steady-state forces) is capped at the same value as the
chase. The shove's excess — the part that makes it read as a hit — is the part the per-force
clamp removes. The clamp-once rule: the forces compose unclamped, and the sum is
clamped to max_force at the end. The shove can exceed the chase (its excess is the feel),
and the sum's clamp is the speed limit (the golem doesn't leave the scene), not the force
limit (the hit doesn't get capped at the chase's value).
The frame, drawn — the golem heading east (toward the player, heading: 0), the chase pulling
east, the knockback shoving west (off the player, the hit's direction), the separation pushing
south (off the crowd below):
Read it the way the eye reads the hit: the knockback (90 west) is the hit, the chase (40 east) is the golem still trying to get to you, and the separation (45 south, weighted) is the crowd. The sum is down-and-away — the golem slides off the hit and off the crowd, and the slide is the readability: a golem hit in a pile that slides through the pile is a golem the eye lost; one that slides off it is a golem the eye tracked through the hit. The separation's weight (1.5) is what buys the "off the crowd" — the steering shelf's part 02 "the weight changes the direction of the answer," on a frame where the direction is the feel.
The composition's order, in code:
var steering := Vector2.ZERO
steering += chase # the seek, stage 03
steering += separation # the nudge, stage 03 (weighted)
steering += knockback # the impulse, part A (decaying)
steering = steering.limit_length(max_force) # the clamp-once, part B
fake_velocity = steering # the velocity, integrated, not set
The limit_length is the clamp-once — one limit, on the sum, at the end. The fake_velocity = steering is the velocity (part A's fact) — the golem is steered, not placed.
C — The dead zone, and the push that respects it (or doesn't)#
The survivors stage 03 dead zone: the chase stops at 15 px (the if distance > 15 gate), so
a golem at rest is 15 px from the player, not inside the hitbox. Now the push: a hit at the
dead zone's edge shoves the golem through the 15 px and back — the knockback is an impulse
(part A), and the impulse isn't gated by the chase's dead zone. The golem flies past the
player, the chase re-engages on the far side, and the golem comes back.
That's correct, and it's the feel's honesty: the dead zone gates the chase (the steady- state seek), not the forces (the impulses). A push that respected the dead zone (a shove that stopped at 15 px) would be a shove that couldn't shove — the hit's excess (part B's clamp-once, the feel) is the part that crosses the zone, and gating it is gating the feel. The survivors stage 03's stuck-list named it: "the knockback shoves enemies through the dead zone and they resume chasing from inside — correct behaviour! The dead zone only gates the chase term; the impulse is independent." The push and the zone are different layers — the zone is the movement's rest point, the push is the force's transient — and the layering is the steering shelf's "the seek is steady, the forces are events" made concrete on a hit.
D — The push's direction, and the space it's in#
The shove's direction is from the hit, toward the target — the survivors stage 04's hitbox:
global_position.direction_to(hurtbox.global_position) * 10.0. The direction is the hit's
direction (where the sword came from), not the target's facing (where the golem was
looking) — a golem hit from the left shoves right, regardless of which way it faced. The space
is the global (both the hitbox's and the hurtbox's global_position), because the shove is
a world-direction and a local-direction shove is the stage 03 "orbit instead of chase" bug,
wearing a knockback costume. The direction's home is the hitbox (the thing that knows where
the hit came from), and the target receives it — the data-driven shelf's "the event has a home, the consequence
follows it," the shove following the hit.
E — Commit#
git add . && git commit -m "feel 02: knockback impulse, the clamp-once, the dead zone's layer"
Checkpoint — definition of done#
- The knockback is a velocity (the
fake_velocity += knockback, part A) — a hit pushes the golem over frames, and the eye tracks it; the position-set version (the teleport) is met and refused - The decay is at a rate (the
move_towardtoward zero), and the resistance is per- species data (part A's mass) — the same shove moves the golem far and the brute near, and the difference is the data, not a branch - The clamp is once, on the sum (part B) — the shove's excess survives (a hit reads harder than the chase), and the per-force clamp is met and refused (the capped shove, the feel removed)
- The composition's frame is drawn (the vector-composition) and read the way the eye reads it — the separation's weight buys the "off the crowd," the 11-degree delta is the weight's direction
- The dead zone gates the chase, not the push (part C) — a hit at the zone's edge shoves through and the golem comes back, and you can say why that's the feel's honesty
- The shove's direction is the hit's direction, in global space (part D) — a golem hit from the left goes right, faced or not
- Zero warnings; committed
Stretch (no instructions)#
A push that stacks, with a cap: two hits in the same frame (the survivor's clump, the
weapon's double-swing) — the knockbacks add (the second shove is on top of the first's
velocity), and the sum's clamp (part B) is the cap. The feel is the stack (a double-hit
shoves further than a single, the damage made visible) and the cap is the scene (the golem
doesn't leave). Find the cap where the stack reads as impact and the golem stays on screen
— the tuning loop, part 04, on the max_force.
If you get stuck#
- The golem teleports on a hit (the eye loses it) → the knockback is a position set
(
position += shove), part A's lie. The shove is a velocity (fake_velocity += knockback), integrated over frames. The position is the jump; the velocity is the push. - The shove persists (the golem drifts forever after the hit) → the decay is missing (the
move_towardtoward zero), or the rate is 0 (theknockback_resistanceis infinite, part A's divisor). The impulse decays; a shove that doesn't is a velocity offset, not a knockback. - Every enemy shoves the same distance (the mass is invisible) → the
knockback_resistanceis the same on every species (part A's data, question 2, answered "per-instance" when it's per-kind). The resistance is on the stats (the species' definition), and the brute's 3 is the data that makes the same shove read as "that was a brute." - The hit caps at the chase's value (the shove never exceeds the seek) → the per-force
clamp (part B's bug), the
limit_lengthon each force before the sum. The clamp is once, on the sum; the shove's excess is the feel, and the per-force limit is the feel removed. - The golem shoves into the crowd (through the separation) → the separation's weight is 1.0 (the naive composition, the diagram's dashed arrow) — the shove's direction is the pure knockback, straight into the pile. The weight (1.5, part B) buys the "off the crowd"; the steering shelf's part 02 is the argument, the diagram is the frame.