Skip to content

feat(hands): lightning-fast pacing, mega gore, a killable referee, responsive controls and power punches - #295

Merged
user1303836 merged 57 commits into
mainfrom
feature/hands-arcade
Oct 17, 2026
Merged

user1303836 merged 57 commits into
mainfrom
feature/hands-arcade

Conversation

@user1303836

@user1303836 user1303836 commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

Playtest feedback on the live Hands Activity, all in one batch:

  • "make stamina more forgiving"
  • "this game needs to be way faster paced ... lightning fast"
  • "more gore ... mega gore ... totally over the top and tasteless"
  • "needs to be more responsive" (movement sluggish, stuck in animations)
  • "no doctors' stoppage, just let the match keep going"
  • "give the referee a hitbox ... hidden health bar ... if he gets hit enough he just dies ... damage model with gore ... a replay of it"
  • "sometimes the movement controls get ... broken ... i cant move in certain directions" (keyboard, Discord desktop, macOS)
  • "blocking is also too forgiving, people can just hold block forever"
  • "its impossible to tell the difference between a 'power' punch and a regular punch"

Engine

  • Pace: 3 rounds of 1 minute, 6 s rests and a 3 s opening.
    • Punch timings: jab 3/2/4 ticks, straight 4/2/6, hook 5/2/7, uppercut 5/2/8.
    • Footwork: max speed 10, a 75% velocity blend, turning at 110 deg/s.
    • Knockdowns: a quicker count (20 ticks a number) and easier get-ups.
  • Stamina: costs are about 60% of what they were, it regenerates faster, and fatigue curves are softer. Tired punches are slower and weaker but never flash knock out.
  • Blocking: a held guard drains 3 a tick and only recovers while lowered.
    • Held until it gives out, it breaks (guard_break "held", 24-tick stun) and stops nothing until it recovers to 250 (guard_broken in the snapshot).
    • A parry never breaks the parrier's guard before its counter window runs out, and a guard held into a clinch is frozen there.
  • Power punches: +3 startup, +1 active and +3 recovery, for x1.85 impact, x2.2 guard damage and x1.75 poise damage.
  • Responsiveness:
    • Fighters walk at 60% speed through a punch's recovery.
    • A slip, weave or pull cancels a recovery once the punch has landed or missed.
    • A press made in the last 8 ticks of a stun is kept and fires when the stun ends.
    • No dead tick between punches; the action buffer is 8 ticks.
    • A guard raised just after a punch no longer cancels that punch.
  • No doctor stoppage. FinishMethod.DOCTOR_STOPPAGE stays for recorded history. A stoppage ends the bout on the spot, with no bell or rest after the result.
  • Referee: an authoritative entity with a body.
    • A punch thrown through him hits him, and the nearer of him and the opponent takes it.
    • He has hidden health (450), trauma, staggers, and bleeding that drains his health.
    • At 0 health he dies: referee_down starts a 5 s REFEREE_DOWN phase with the clock paused and both fighters frozen, then the bout goes on without him (nobody breaks clinches).
    • Fighters can't walk through him.
    • Protocol v5 adds the snapshot's referee (no health) and guard_broken.
  • Computer opponent: retuned for the faster rules. It rests a worn guard, never raises a broken one, and its levels were re-measured.

Client

  • Mega gore:
    • Every solid punch is a geyser; sprays, pools and canvas soak are far larger.
    • Faces drench; blood lands on the lens.
    • Mist, flesh, teeth and gum shields fly.
    • Every stoppage gets a gory finisher: geysers, brain, teeth, both eyeballs on the canvas.
    • Device budgets apply (phones half), and reduced and off blood stay tame.
    • Measured on an M4 under a gore stress: 60 fps on desktop and phone (CPU throttled 4x).
  • The referee:
    • He is drawn where the engine has him while punches can land; punches that find him land on his head.
    • He bleeds, swells and staggers, and his face spurts like a fighter's.
    • On death he ragdolls and pours blood from his mouth; "THE REFEREE IS DEAD!"
    • A slow-motion REFEREE DOWN replay plays inside the engine's pause, then he lies dead in a spreading pool for the rest of the bout.
    • The commentary calls him killed, or bled out.
  • Movement bug: two causes were confirmed with failing tests.
    • Opposite keys cancelled to zero; the newest of A/D, W/S, Q/E now wins.
    • macOS drops key-ups while Command is held, and Discord's Chromium 148 lacks Chrome 149's fix; Command's key-up now releases held keys.
  • Responsiveness:
    • Your own punch plays at full speed from the press; press to glove at the target went from 362 to 134 ms median at 60 ms each way.
    • Movement and guard changes go out at once.
    • Walking out of a recovery and slips behind your punch are predicted.
    • Guards and evasions snap in at 40/s.
  • Power punches:
    • A deep load pose and a glowing glove with a hot trail.
    • On landing: an impact flash, a camera kick and a 140 ms freeze.
    • Heavier whoosh, thud and block sounds.
    • A POWER ARMED tag while the modifier is held.

Verification

  • Checks: ruff, ruff format, mypy, pytest (Hands and the full suite), tsc, Vitest, the golden replay check, the client parity fixtures against the engine, and the end-to-end scenarios against the real server.
  • Reviews: four independent ones (engine; gore and referee client; feel; then a final pass over every fix made for the first three). Every finding was fixed, with a test, or answered here.
  • Deliberate breakages: each new behaviour was broken on purpose and confirmed to fail a test.

Known limits

  • The 3 s opening leaves no time for the ring announcer's introductions (the cards still show them).
  • A jab can still reach the target before a refusal arrives at high latency, and then snaps back.
  • In a round's first second, 20+ direction changes a second together with 15+ presses a second can still hold one or two presses back 50 to 100 ms.
  • Pre-existing: keys used on the Settings selects also reach the fighter (the window keydown handler ignores focus).
  • Fighters can walk through the dead referee's body.
  • The client could work out the referee's remaining health from the manifest and the hit amounts.
  • Not measured on a real phone GPU, a physical gamepad or real Mac keyboards in Discord.

Cuts and swelling no longer end a bout: the engine drops the doctor
thresholds and both stoppage checks, and the client drops the doctor
commentary, result label and finish method. FinishMethod keeps
DOCTOR_STOPPAGE so bouts recorded before this still count in records.
Every clean punch now throws dozens of drops off an uncut face (a jab about
60, a hook about 115, up to 360 from an open wound) with only a little sweat
at full blood. The drop pool grows from 900 to 2400 on a desktop and a phone
keeps half of that and throws half the blood; mist and debris pools grow with
it. A full pool gives up the drop that has flown longest instead of reusing
slots in turn, so a big event no longer overwrites drops just thrown.

At full blood a hard punch splashes the canvas cut or not, stains soak in a
quarter darker and larger, most fine spray stains where it lands, and the
pools under downed fighters and finisher wounds spread to 0.7 m and 1.3 m.
Reduced blood keeps its old, tame canvas.
The nose bleeds after a couple of clean head shots, a brow bleeds as soon as
it is cut, cheeks and the forehead split far sooner, and every wound runs up
to three wider rivulets that reach most of the way down the face. Blood
smears the neck, chest and shoulders twice as readily, the trunks soak from
the first bleeding and the gloves from the first punches that bloody the
opponent's face. Reduced blood shows a fifth of it on the skin and gear.
At full blood any punch that lands hard (45 damage or more) or lands on an
open cut mists the blood it throws, thicker the worse the cut. A punch that
rocks a fighter tears flesh from any cut and a hard one (85 or more) from any
face, up to twelve pieces. Teeth fly from clean counters of 60 and hard shots
of 75, up to three a punch and three more with a knockdown, and the gum
shield from counters of 65 and shots of 80.

A man going down pours blood from his nose and mouth with every beat of his
heart as he falls: a new gushing wound emitter, kept on his mouth every frame.
The bloodless jaw and shoulder dislocations become wounds torn open: the jaw
breaks loose with eight teeth and pours blood from the mouth, a shoulder put
out tears open at the joint, and caved-in ribs burst at the side where they
lie, standing or on the canvas. Each throws a burst of blood, flesh and mist,
splashes the canvas and keeps pouring with every heartbeat, held on the wound
every frame, and pools like any finisher's wound. The knockout replay now
puts every finisher back to do it again at its impact.

Thresholds drop so the big four happen far more often: a hard counter (70)
bursts the head, any straight of 50 or uppercut of 40 takes it off, a hook or
jab of 40 forces an eye out and a body hook of 50 takes the hand off. Every
finisher throws more: up to 480 drops, 24 skull and brain pieces, ten teeth,
longer and heavier arterial fountains, and an eye socket that pours for six
seconds.
Blood thrown at the camera now lands on its lens: a hard punch, a knockdown
or a finisher in the picture within seven metres spatters ragged drops of
blood over it, more and bigger the nearer and the heavier, and the big ones
run down the glass before every drop fades out within four seconds. It is
drawn over the picture and under the broadcast graphics, a few small drops
with reduced blood and none with blood off or reduced motion.

Every big clean punch and every knockdown flashes the screen red at full
blood, fainter than when the viewer's own fighter is hit (now a full flash
when he is floored); the hurt vignette starts at the first damage and goes
almost twice as dark; hits, knockdowns and finishers shake the camera harder.
A punch's blood now leaves from the side of the face or chest that faced the
puncher, where the fighter is drawn, so a slipped, ducked or fallen fighter
sprays from his head rather than from a standing man's height over his spot.

Every finisher's voice now comes over a wet crack of bone and flesh giving
way and three spurts of blood a heartbeat apart, at full blood only.
A full pool gave up its oldest drop by scanning every live drop for each new
one, 860 thousand steps for one big spray into a full desktop pool. It now
finds the 64 oldest in one pass and gives them up in turn, finding the next
batch only when those run out or a drop lands and the slots move.
Teeth now fly from far lighter blows, so reduced blood knocks out half as
many and none from a blow that would take only one, which keeps it to about
what it showed before.
…g, heavier power punches

Pacing: three one-minute rounds with six-second rests and a three-second
opening; every punch is quicker (jab 3/2/4 ticks, straight 4/2/6, hook
5/2/7, uppercut 5/2/8), footwork is faster (10 units a tick, velocity
blends 75% a tick, turning 110 degrees a second), and the referee counts
every 20 ticks with a mandatory five and a short box pause. Clinches,
foul recoveries, taunts and evasions are shorter.

Responsiveness: a fighter walks at 60% speed while his punch recovers, a
slip, weave or pull cuts any recovery short once the punch has landed or
missed, the next buffered action starts on the tick the previous punch
ends, a press in the last moments of a stun fires as it wears off, and
the action buffer is eight ticks.

Stamina: punches and misses cost far less, breath comes back faster,
conditioning drains more slowly and weighs less on stamina and speed, and
a punch thrown out of breath is a 70% arm punch instead of a 40% one.

Blocking: a held guard wears down every tick and only comes back lowered.
Held until it gives out, or worn through by a block, it breaks with a
24-tick stun and stops nothing until it is back past the rearm mark; the
snapshot carries guard_broken and the HUD says so. Blocks wear the guard
by the punch's full guard damage and more of a blocked punch leaks.

Power punches wind up three ticks longer and land far harder (x1.85
impact, x1.75 poise, x2.2 guard damage). A punch thrown out of breath is
marked tired and can never knock a man out cold.

Gore: blood per hit grows with damage and bleeding, cuts open on less
swelling and bleed faster.

The computer rests a worn guard until most of it is back, the champion
keeps its guard up more and attacks more patiently, the rookie is busier,
and the scripted players in hands_balance.py manage their guard (except
the turtle). Client prediction mirrors the new movement, fatigue and
timing rules, and the parity fixtures are regenerated.
The referee is now part of the engine instead of a client-side figure.
He follows the action close up on his own side of the fighters' line,
stands over a man on the canvas for the count and keeps to the middle
between rounds. A fighter walking into him shoves him aside; pinned on
the ropes, he stops the fighter instead.

A punch that finds him before the other man (thrown through his body,
within its reach) lands on him: he never guards, he reels where he
stands, his face and body take cuts, swelling and bleeding like a
fighter's, and nothing scores for it. He has a hidden pool of health;
punches and his own bleeding wear it down, and at zero he goes down for
good. The bout then stops for five seconds (a new referee_down phase,
clock paused) so the clients can replay it, and goes on without him:
counts, bells and clinches still happen, a clinch simply coming apart.

Snapshots carry his position, facing, velocity, stagger, injuries and
whether he is still standing, but never his health; the checksum covers
all of it. Events: referee_hit, referee_down, bleed from the referee and
clinch_break. Protocol version 5. The client decodes the new snapshot
and phase; drawing him from it comes next.
Arcade splatter, not realism. A clean jab now throws about 150 drops and a
clean hook 270, up to 800 from an open wound, drawn a third bigger at full
blood, and a man going down throws 250 more. Any solid punch (50 to the head,
70 to the body) makes the face spurt blood from the nose and mouth for a
second or two, longer and harder the worse he is cut, and a knockdown pours
for three seconds. Mist comes off nearly every punch, flesh off any face hit
for 60, teeth fly from any solid punch (up to five, five more on a knockdown)
and the gum shield from counters of 50 and shots of 65.

The canvas soaks: stains 60 per cent darker and larger, eight in ten fine
drops stain, hard hits splash up to 24 blots, knockdowns 30, pools spread to
1.1 m and finisher wounds to 2 m. The drop pool doubles to 4096 on a desktop
and 2048 on a phone, and a client at the lowest quality tier throws half.

Reduced blood keeps its old canvas and pools, and a single tooth from only
the hardest blows.
Every finisher throws far more: up to 1,200 drops, 36 pieces of skull, 40 of
brain, 16 teeth and 60 lumps of flesh, with mist and canvas splashes to
match. Severed necks, burst heads and wrists now geyser three metres high for
12 to 16 seconds at up to 340 drops a second, and torn wounds and the empty
eye socket pour for ten.

Loose eyeballs: a burst head throws both eyes, and forcing one eye out pops
the other clean out too, so both sockets are left empty. They bounce across
the canvas on a pool of their own, drawn with the eye's texture and compiled
with the scene, and lie there for the rest of the bout.
…lens

At full blood every wound shows half as much again, so faces, chests and
trunks are drenched, and its rivulets run wider and the length of the face.
A cut face drips up to 60 drops a second.

Blood now reaches the lens from 12 metres, so the broadcast camera gets
spattered too: any punch of 40 or more, a knockdown (heavier) and every
finisher (a burst head soaks it with 24 drops), drops that last up to five
seconds, 96 on the glass at once. The red flash for the opponent's punches
goes up to 0.75 and 0.85 for a knockdown, and the hurt vignette starts at 100
trauma and goes to 0.65.
…and replayed

While punches can land, the client stands the referee where the engine
has him (interpolated like the fighters, a quick step behind), so a
punch thrown through him visibly lands on him; over counts, fouls and
the rest he keeps going where the picture needs him. His face and body
now shade cuts, swelling and bleeding from the engine's trauma at the
player's blood level, a punch snaps him away on top of his posture, and
while he reels he staggers where he stands.

A referee_hit fires on the render clock with blood off his head, the
puncher's hit-stop and the punch sound; his bleeding drips from his
head. On referee_down the knockout physics takes him, the broadcast
shouts that the referee is down, and a second later his fall is
replayed in slow motion from in front of him (REFEREE DOWN replay
label), after which he stays where he fell; a new bout stands him up
again. The commentary calls punches on him, his fall and clinches with
nobody left to part them, and the crowd reacts.

The count's arm now beats at the engine's count interval, which moves
into the combat manifest. The presentation fixture stages it with
?fixture=1&referee=down.
…s it changes

Opposite keys held together no longer cancel to a standstill: the newest of
A/D, W/S and Q/E wins, so a roll from one to the other keeps the fighter
moving and a key whose release was lost cannot block the other way.

macOS delivers no keyup for a key let go while Command is held (Chrome 149
makes one up, Discord's Electron does not), so a direction released behind
Command stayed held for good and, with the old cancel, blocked the opposite
direction. Command's own keyup now lets go of every held key but Shift and
Alt. A punch takes Shift and Option from the key's own flags as well, so a
power or body shot still counts when the modifier's press was missed.

A move keeps the camera it was pressed under until the keys change, so a
camera change mid-hold (K sits beside J) no longer swerves the fighter.

Changes of held movement or guard (keyboard, touch stick and guards, pad)
now go out at once instead of waiting up to 33 ms for the periodic flush,
on a budget of their own that leaves presses their room under the server's
limit.
The broadcast now calls his fall a death ("THE REFEREE IS DEAD!"), and
when his bleeding rather than a punch finishes him the commentary says he
bled out.
Every stain tinted a scratch copy of its stamp and then drew it, which made
the browser flush the scratch canvas to the graphics card each time. At the
thousands of stains a second grotesque gore paints, that cost more than all
the rest of it: at 3840x2160 under a gore stress it took a frame from 19 ms
to 29 ms. The stamps are now tinted once for each of the few colours blood
is painted in, and a stain is a single draw.
Mega gore on top of the referee: the HUD line keeps the referee replay
label and gains the lens blood drawn under it, and the screen gore test's
renderer stub gets the referee fields fireContacts now reads.
The own punch used to stretch its wind-up by the whole network lead so the
glove met the hit shown on the delayed clock: 280-450 ms from press to the
glove at the target where the engine lands it in 67-167 ms. A press starts
the punch on the server the moment it arrives, so the client now plays it on
the press's own time, at full speed, and reconciles with the server's copy
without slowing it or jumping back (the server's timing still replaces the
predicted one in place).

A press the server refuses, or says nothing of well past when its answer is
due (the input latency, not the stretched lead, now sets that), goes back:
the way it went before contact, and straight back to the guard at once after
it instead of playing out as if it landed. Presses made during a punch are
held against the punch's own time, or a lead ahead of a punch played on the
server's clock. The puncher's hit freeze only holds a glove still at the
target, so the late word of an own punch's contact no longer stalls its
recovery. An opponent's punch seen early keeps its stretched wind-up.
The mega gore reached the fighters only, through their two gush slots and
their downed pools. The referee now has a gush of his own: a solid punch
on him spurts blood from his nose and mouth exactly as one on a fighter
does (punchGush), the punch that kills him pours it as a fighter going
down does and it follows his head to the canvas, and the replay of his
death pours it again. Dead, he bleeds into a pool under his head that
spreads as wide as a finisher's open wound and then stops; a new bout
starts the next one afresh. Blood off, reduced motion and his replay
keep to the same rules as the fighters'.
…ut of it

The engine now lets a fighter walk at 60% through his punch's recovery and
lets a slip, weave or pull cut that recovery short once the punch has landed
or missed. The client held the predicted feet for the whole own punch and
refused to predict an evasion behind one, so the fighter stood rooted until
the animation ended and then slid. The input history now records the share of
the footwork the server gives each input: none through the own punch's
startup and active ticks or an own evasion, the recovery's share while the
punch recovers, a share the server's copy of the same punch does not apply a
second time.

An evasion pressed during the own punch shows the moment the server will
start it on the press's time: at once behind a punch that has landed or
missed, otherwise when it does; the punch's glove then goes straight back,
the feet are free once the evasion is over, and the press it held for later
is dropped, as the server's one-slot buffer drops it. A punch press in turn
replaces an evasion still waiting.

An evasion plays for as long as the server plays it, instead of until the
server's copy has played on the delayed screen, and that copy is not shown
again. Once the client knows when a slip started (its defense event), the
replay frees the feet the tick it runs out instead of holding them for as long
as the delayed snapshot shows it. New engine traces of a slip and of a slip
out of a straight's recovery check this against the engine.
The guard, slip, weave and pull eased in at 14-16 a second, 160 ms to get
nine tenths of the way, a long time beside a 67 ms jab. They now ease at 40 a
second (58 ms), and the weave's dip and rise runs as long as the engine's
evasion (seven ticks) instead of 0.42 s, so it comes back up before the
engine lets go of it rather than ending at the bottom.
A punch is aimed at the other man's head, so one the engine stopped on
the referee went on through him to the man behind. The puncher's graph
now remembers the punch the server reported as a referee_hit and turns
its aim onto the referee's head as drawn, the way a punch that met an
evading opponent follows his head: from the verdict when it comes in
mid-flight, and from the start of the punch in the replay of his death,
which shows that punch from in front of his face.
A power punch looked like a normal one a few centimetres longer and sounded
the same. Now:

- It loads up through most of its longer startup: he sinks, turns his
  shoulders and hips away from the punch and cocks the glove back, then
  unwinds into it with a sharper snap and more follow-through.
- The throwing glove heats up through the load with an orange glow (seen
  through the body when it is cocked behind it), flares at the target and
  leaves a wider, hotter trail, for the puncher and the opponent alike.
- Landed, it flashes white-hot on the head or body it hits, throws a ring
  of heat out from it and kicks the camera; the glove holds at the target
  for 140 ms (a new hitstop_ms.power in the manifest; hit and block keep
  their 78 and 45 ms), and a punch held by any freeze now makes up the time
  afterwards instead of jumping ahead. No flash or shake with motion
  reduced.
- It whooshes lower, longer and louder with a low drop under it, lands with
  a deep boom and a sharp crack, thumps a guard that takes it, and rumbles
  harder. The player's own punch is now heard as it starts on screen rather
  than when the server's word of it arrives a round trip later.
- While Alt/Option, the POWER pad or the right trigger is held, the
  player's plate says POWER ARMED, and the controls name Option for Mac
  keyboards.
…r says so

The player's own punch now reaches the opponent on the press's time, well
before the server's word of its contact arrives; holding that contact back
for the delayed clock as well left the opponent's reaction, the blood and
the thud another interpolation delay behind the glove. A contact of the
player's own press that has already reached the target on screen is shown
on arrival (a jab's reaction about 55 ms sooner at a 120 ms round trip).
Knockdowns and the end of the bout keep to the clock, with the replay and
the finish that hang on them, and the opponent's view is unchanged.
The drawn fighter turned at a third of its usual rate for the whole of a
punch. The engine only holds his facing through the punch's startup and
active ticks and turns him again as it recovers, now that he can walk out
of it; the drawing follows the same rule instead of sliding sideways while
still facing where the punch went.
… a punch short

A press the server held behind a punch froze the predicted footwork for as
long as a snapshot showed it queued, while the engine walks the fighter out
of the punch's recovery at 60% until the press starts. The replay now holds
the feet from when the press will start: the end of the punch it waits for,
or the tick after a stun wears off. A combination that cuts a landed punch
short starts sooner; for the player's own presses the input history already
holds the feet from then.

After a slip, weave or pull cut a recovery short, snapshots go on presenting
the punch with its own timing, so the replay walked the fighter at the
recovery's 60% once the evasion was over, where the engine walks him at full
speed. A punch is now taken as cut by an evasion that started once it had
landed or missed.

A hold that starts on the first step of the replay now lets the velocity
the fighter carries blend away, as the engine does, instead of stopping him
dead. Three new engine traces: a jab pressed during a hook's recovery, a jab
pressed late in a flinch, and a slip out of a power uppercut's recovery.
- A TKO, flash knockout or disqualification no longer rings the round
  bell, writes a round card or starts the rest on the finished bout: the
  doctor stoppage's removal had taken the end-of-tick check for a result
  with it. A referee's bleeding no longer runs after the result either.
- A guard held up into a clinch is left as it was through the hold: it
  neither wears down nor breaks there, where the fighter cannot change
  his pose, so no break stun carries out past the referee's break.
- A parry wins the exchange: a perfect block that wears the guard too
  thin to stop a punch no longer breaks it, by the block or by the held
  guard's drain, before the parry's counter window runs out.
- A referee pinned against the ropes who stops one fighter no longer
  leaves him inside the other.
- Presses made while the referee's death plays again are dropped rather
  than fired as the fight resumes.

Tests pin each of these, and the reviewer's breakages that no test
caught: a winded fighter slipping out of a recovery, a punch out of the
other man's reach finding the referee, the referee's stagger, his death
stopping the fighters and only pausing the fight from the fight phase,
his walk to the middle between rounds, punches going through him once
dead, the computer raising a broken guard, and decoding guard_broken.
Responsiveness, the movement-key fixes and power punches you can see and
hear, on top of the referee and the mega gore. Conflicts were imports,
test stubs (the gore's gushing beside the feel's powerShake), the HUD line
(lens blood, the REFEREE DOWN label and the POWER ARMED argument) and the
referee's head setter beside the new landedHit and powerHeat. A punch on
the referee now passes onContact and landedHit its power, read from the
snapshot it arrived in as a fighter's is, so a power punch on him is held
and heard as one.
…client fixes from review

- The replay of his fall (0.8 s of the live fall, then 3.7 s replayed)
  fits the engine's 5 s referee_down phase with room for the playback
  delay; it starts only while the bout still waits for it and ends the
  moment the fight resumes, so it never plays over live fighting.
- In his replay he stands in the killing punch's path until it lands
  (the recording's death tick no longer pulls him aside), and the
  physics runs his recorded fall again, so he ends where the live fall
  left him, in the pool that keeps spreading through the replay.
- The drawn referee never moves faster than a brisk step: no jump at a
  bell, a count or a foul, and no dash back to the engine's place when
  the fighting resumes. He is placed straight on the engine's spot the
  first time he is drawn.
- A spectator who joins, or a client that reconnects, after he died
  sees him lying where the engine has him, not standing frozen.
- Dead, he neither waves the fight off nor walks over to raise anyone's
  arm, a decision ceremony goes on without him, and his blob shadow
  lies under his body.
- A punch on him and his death are heard once, as they are shown.
- The round clock holds through his five seconds, the controls stay
  live through them (touch pads and held input), and a punch that found
  him counts as landed for a combination's cancel.
- A fighter's gush stops when a replay starts and when his head comes
  off or bursts, and the replay of the referee's fall names his killer.
Held input now goes out the moment it changes, so a guard raised just
after a punch (punch and cover, or the neighbouring keys) usually reaches
the server before the step that would start the punch, and a guard
start cleared every waiting press: the punch was lost and the player's
predicted glove snapped back. A guard start now cancels only a stale
press: one waiting behind a punch, a stun, a taunt or a hold, or left
from an earlier tick. A press made since the last tick by a fighter free
to throw it is thrown, with the guard up after.
The hit freeze landed on whatever punch the graph was playing. With the
player's own punches on the press's time, a jab's contact is shown on
arrival a round trip after its glove got there, and by then a straight
held behind it can already have started: the straight froze in its
wind-up for 78 ms (140 for a power jab) and then lurched to catch up.
landedHit now takes the contact's action id and freezes only the punch
with that id, still only while its glove is at the target. fireContacts,
the referee's punches and the knockout replay's impact pass the id.
…ng it

Pressing Q or E fired the held-input listener before the waiting presses
were cleared, so the instant guard send drained the action buffer and sent
a press the guard was meant to cancel. The gamepad and touch guards clear
first; the keyboard now does too.
A slip, weave or pull pressed during the player's own punch cuts the
punch's recovery short on screen, as the server does once the punch has
landed or missed. If a punch press then replaced the slip in the
server's buffer before it started, or the server turned it down, the
server never cut the recovery, but the client still did: the feet were
freed early and the punch held behind it was let go four ticks early.
EvasionPrediction.drop and acknowledge now say when they dropped an
evasion that never started, and the renderer lets the punch recover in
full then (BoxingGraph.keepOwnRecovery).
A punch is a power punch when the key's own Option flag is set or an
Alt key is tracked as held, so an Option pressed before the page had
focus still counts. The tag read only the tracked keys, so it stayed
off while the punches went out as power punches. Both now read one
getter: Option as the newest key event says it is, or a tracked Alt
key. A blur forgets the flag with the held keys.
The replay's impact froze the puncher's glove for the plain 78 ms and
showed no flash or camera kick, though the punch it replays was a power
punch. It now holds it for hitstop_ms.power, fires the impact flash and
kicks the replay's shot as the live camera is kicked, leaving the flash
and kick out with reduced motion. A knockdown with no hit of its own
(the guard took the punch) names no punch, so its freeze falls back to
the punch the puncher is throwing in the replayed snapshot.
… mash

The budget test pressed four times a second, so the held sends' own
budget kept the total under the cap and the room kept for presses never
mattered: dropping SENDS_KEPT_FOR_PRESSES went unnoticed. This one
mashes at the full edge rate while the direction changes as fast as the
held budget allows, and checks that once a second of sends is in every
press goes out at once, counting every frame against the cap.
…work

Three behaviours of the own punch survived deliberate breakage:
- a power punch the server turned down cools as it is pulled back,
  rather than glowing all the way back to the guard;
- a held press the server starts before it is let go here plays on the
  server's timeline, so its contact keeps to the delayed clock instead
  of being shown on arrival before the glove gets there;
- the renderer moves the player's own fighter at the footwork share his
  punch leaves him (the recovery's 60%), not held or free through it.
…e newest

With D's keyup lost behind a newer A, pressing D again has to turn the
fighter right; kept in its old place in the order, A would hold him to
the left, the "can't move in certain directions" bug. A repeat of a
held key still leaves the order alone.
Input is off before the bell, so the second of sends the 50 a second
cap counts starts empty. Held changes passed the room kept for presses
while it was still filling, and with the periodic flush and a hard mash
the second reached the cap before anything in it was a second old:
every send, presses included, was refused until then. From the bell,
with the direction changing at the held budget's rate and presses at
the edge budget's, presses waited up to 150 ms; with faster direction
changes, up to 200 ms.

While the room kept for presses is in use (40 or more sends in the last
second), the periodic flush now skips a tick when an edge or held send
went out within the last 33 ms, as the server already has input from
less than a tick ago. It never skips while a press is waiting: one the
8 ms gap or the edge budget held back goes with the next tick. From the
bell, at both budgets' rates, no press waits now, and no second goes
over 50 sends, within the server's 60 a second budget and far from its
flood limit.

The tradeoff: while the reserve is in use, a held change that does not
go out at once (one below the send threshold, or past the held budget
or the reserve) can wait one extra tick for the flush.

Direction changes faster than the held budget's own rate, with presses
at 15 a second or more, can still hold one or two presses back 50 to
100 ms in the first second; skipping ticks within two flush periods
instead of one clears those too, at the cost of a second extra tick.
The feel review's fixes: a contact freezes only the punch it belongs to,
a keyboard guard cancels a waiting press, a slip that never starts
leaves the punch's recovery alone, the POWER ARMED tag reads Option as
the punch does, the knockout replay lands a power punch as it was live,
no press waits at a round's start, and tests for the gaps the review's
breakages found.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

- Nothing runs after a stoppage on its tick: player one's upkeep and
  bleeding came before the check for a result, so a bleed could follow
  it.
- Only a parry protects the guard it wore thin: the held-guard break
  keyed on the counter window, which a slip, weave or pull opens too, so
  a worn guard raised after an evasion held for up to 15 ticks. A parry
  now records its own window.
- A clinch freezes only a guard held up into it; a lowered or broken one
  keeps coming back through the hold, as a breather.
- Tests for the guard start cancelling a press made while taunting,
  starting a clinch or clinched, and for a hold against a pinned
  referee keeping its distance.
…ing it

The engine now throws a press that reached it before a guard start
(83c877c), but every input device still dropped a press waiting in its
own buffer when a guard went up. The renderer had already started
predicting it, so the glove went out and the server never heard of it.
With F, J 3 ms later (held back by the 8 ms gap) and Q 12 ms after F,
the second press was lost in 9 rolls of 10, live. The same happened to
a press waiting on the edge budget, the 50 a second cap or a backed-up
socket, and a waiting slip kept its predicted pose and the recovery cut
it made, as it never got a sequence the refusal check could match.

A guard start now never cancels a press already made, on the keyboard,
a pad or the touch controls: it goes out with the guard frame and the
server decides, keeping a press that arrives in the same frame and
dropping stale ones itself. This reverses dda0dc4, which made the
keyboard clear first as the pad and touch controls did. The !repeat
condition on Q/E existed only for that clearing and goes with it; a
repeat still never makes a press. Live, all 20 presses of 10 rolls now
go out (11 before).
… stalled frame

- Once the referee is dead the commentary never has him step in, warn,
  take a point, wave them on or wave it off (each of those calls has a
  phrasing without him), and the ring announcer gives a stoppage as over
  with the referee dead on the canvas rather than stopped by him.
- His replay's closing hold may be cut short by the resume, so the start
  gate asks only for the rest of the replay to fit: a stalled frame or
  two around his death costs that hold, not the whole replay.
- In his replay he stands exactly where the recording has him from its
  first frame, rather than easing over from where he lay.
- Tests for his body lying still once the replayed punch lands, for a
  reconnect that missed his fall, and for half a second of the pause to
  spare around the replay.
…s a block

For a punch that met the guard the engine emits the block, then the hit
that leaked through it, then the knockdown when the poise is gone. Live
that showed as a block: the block's 45 ms freeze, no flash. The replay
of the knockout took the leaked hit for a landed one and, on a power
punch, gave it the 140 ms freeze, the impact flash and the camera kick.
In 140 simulated bouts 21 of 314 head knockdowns came through a block,
14 of them on a power punch.

The knockdown record now keeps whether its punch was blocked, from the
same wasBlocked that already denies such a knockdown its finisher, and
the replay's impact freezes the glove for a block and leaves out the
flash and kick for it.

The fallback added in 0dbd090 for a knockdown recorded without its hit
goes too. It rested on the belief that a punch the guard took leaves no
hit, but the engine reports the leaked hit of every guarded punch, so a
head knockdown always has its punch, and a body collapse whose punch
has left the history still names it.
A guard broken by being held up too long reaches the renderer as a
guard_break with the other man as its actor and no action id, and
fireContacts passes that id to his graph's landedHit. Letting a null id
through would freeze whatever punch he had at the target; the reviewer's
breakage of that check went unnoticed. Now pinned with a hook at the
target: no freeze for a contact naming no punch, the freeze for its own.
The test of the power modifier only turned input off while the touch
POWER pad was held, so dropping the keyboard's enabled check went
unnoticed. Alt held through a pause or the rest now reads as not armed
until input is back on, then armed again, as the key is still tracked.
The final review's input and replay fixes: a guard lets a press already
made go out on every device instead of dropping one the client had
already shown, and a knockdown through the guard is replayed as the
block it was. Tests for a null contact id never freezing a punch and
for the POWER ARMED tag while input is off.
@user1303836
user1303836 merged commit a5e19df into main Oct 17, 2026
6 checks passed
@user1303836
user1303836 deleted the feature/hands-arcade branch October 17, 2026 13:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant