framedata
The arithmetic every fighter's numbers obey, and the checker that holds them to it. The roster modules call `F.build(spec)`; everyone else calls the range and punish helpers.
emberline.fighters.framedata
The arithmetic every fighter's numbers obey, and the checker that holds them
to it. The roster modules call F.build(spec); everyone else calls the
range and punish helpers.
The identity
A move connects on its first active frame. The attacker is then busy for
(active - 1) + recovery frames and the defender for blockstun, so
onBlock = blockstun - (active - 1) - recovery
onHit = onBlock + (hitstun - blockstun)
A move is authored with the advantage it is meant to have and the recovery
is solved for. That is why no move in this game can claim frame advantage its
frames do not give — and why validate refuses a move whose derived recovery
comes out under three frames, which is the sign that the authored advantage
was not payable at that active/blockstun pair.
Unblockable moves (throws, Bell's command grab) have no block advantage to
solve for, so they write recovery directly.
Reach
move.reach = hitbox.x + hitbox.w * fighter.reach
effectiveReach(m) = move.reach + projectile.speed * projectile.life
connectsAt(m, d) = effectiveReach(m) >= d - HURT_HALF
HURT_HALF is 0.40 — a standing body's hurtbox half-width. POINT_BLANK is
Rules.PUSH_WIDTH * 2 = 1.24, the distance between two origins when the push
boxes touch. A move needs 0.84 of reach to work at point blank.
The reach multiplier scales a hitbox's width, not its offset: a
long-armed fighter's arm is longer, their shoulder is not further forward.
Calls
| call | gives you |
|---|---|
F.build(spec) | a finished fighter from a compact spec |
F.move(slot, reachMul, raw) | one finished move |
F.validate(fighter) | a list of problems; empty means the numbers hold |
F.connectsAt(move, distance) | does it touch a body that far away |
F.maxRange(move) | the furthest apart two origins may be |
F.effectiveReach(move) | reach including a projectile's travel |
F.punishOptions(defender, blocked) | every move that punishes it at point blank |
F.links(first, second) | does first leave enough on hit to link into second |
F.frameTable(fighter) | one fighter's numbers as readable text |
punishOptions is honest about distance: it takes the blocked move's
blockPushback into account, so a move that is minus on paper but shoves you
out of range comes back with nothing. That is a real property of several of
these specials and it is meant to be.
What validate enforces
Every slot in Rules.MOVE_SLOTS present · hit levels in Rules.HIT_LEVELS ·
clips in Rules.CLIPS · recovery payable · positive damage and a non-empty
hitbox · onBlock equal to its own frames · cancel targets known · every
move able to connect at point blank · lights never worse than −3 on block ·
a light that links into a medium, standing and crouching · a medium that
cancels into a special · special1 carrying a projectile · a super costing a
full meter.
Scoped to this part · feeds back into the world's score.