Serve reads, the mop beat, the head-icon card, and Daniel benched

Oliver's batch, the code half:
- The tennis serve lost its ghost fingers (idle tap demo + apex rider deleted
  with their counters; the rally-return hint survives).
- The toss rises over the tossing HAND, measured off the toss frames' raised
  fingertips (TOSS.dx/oDx, women's tour override) — not over the head.
- An uncaught toss returns to the bounce stance instead of flashing the smash
  frame the player never earned.
- Between match games the court belongs to the mop boy: 5 s (TUNE.matchMopT),
  full job allowance, a hurry speed between stains, and the far-court reach
  limit lifted for the match (his stains are exactly where opponents die).
  The next opponent walks on only after — d.ending guards the empty court.
- The walk-on card wears the opponent's own head: config.HEAD_CROP, straight
  crops measured off the family sheets, no new art.
- The match intro is DEATH MATCH, counting the real blood pool.
- Daniel is benched: every ump_* personality line routes through aside()'s
  early return; officiating calls keep speaking. Personality pass later.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Fable 5 committed 2026-08-13 17:10:45 +02:00
1 parent 0e01894f4d
commit c084d53497
8 files changed
+141 -70

No files matched your search

+29 -3
View File
@@ -222,7 +222,11 @@ export const TUNE = {
changeoverT: 5.0, // s the changeover breather lasts (after odd games, as the rules demand)
boyMopSpeed: 2.2, // m/s a MOPPING ball boy crosses at (a fetching one still sprints at 7.5)
boyMopDwell: 1.8, // s he stands on a stain pushing the mop before moving to the next one
boyHurry: 4.6, // m/s he covers ground during the MATCH mop beat — brisk, so the 5 s window
// is spent scrubbing rather than walking into shot
boyMaxJobs: 4, // stains one boy will clean in a round — a kill streak must not employ him forever
matchMopT: 5, // s the mop beat holds the court between match games — the boy visibly cleans
// before the next opponent walks on (skipped when there is nothing to mop)
boyStride: 0.28, // m of travel per animation frame. Frames are driven by DISTANCE, not time, so
// the feet cannot slide: change a boy's speed and his legs keep up on their own.
gameBeatT: 1.6, // s the game-end beat lasts on even (non-changeover) games
@@ -613,7 +617,7 @@ export const MODES = [
export const TOURS = [
{ id: 'men', name: "MEN'S SINGLES", tile: 'tour_m', sfx: null },
{ id: 'women', name: "WOMEN'S SINGLES", tile: 'tour_w', sfx: '_f', dx: 0.46, apex: 0.78 },
{ id: 'women', name: "WOMEN'S SINGLES", tile: 'tour_w', sfx: '_f', dx: 0.46, apex: 0.78, tossDx: -0.59 },
];
// netW was the singles-stick width (10.06) while the court is drawn to the DOUBLES lines, so the
@@ -806,6 +810,23 @@ export const BENCH_STAGES = ['HEALTHY', 'ARM OFF', 'HEADLESS'];
// the top of _master.py for the fix its next attempt needs.
export const ZFAMS = ['r', 'br', 'sh', 'bb', 'st', 'ru', 'gk', 'lj'];
// HEAD CROPS for the opponent intro card — a straight source-pixel crop out of each family's own
// healthy frame (z_<fam>1.png), so the card's icon is the body on court, never separate art.
// MEASURED offline (silhouette-width profile: the head band ends where the shoulders widen; br and
// gk hand-extended past the rule — the brute's shoulders start early, the keeper's hat brim wider
// than his face). Re-run the measurement when a family sheet is regenerated. A family missing here
// simply gets a text-only card, so a new roster row never breaks the walk-on.
export const HEAD_CROP = {
r: { x: 3, y: 0, w: 71, h: 73 },
br: { x: 0, y: 0, w: 87, h: 61 },
sh: { x: 8, y: 0, w: 62, h: 65 },
bb: { x: 7, y: 0, w: 62, h: 65 },
st: { x: 14, y: 0, w: 57, h: 55 },
ru: { x: 14, y: 0, w: 57, h: 55 },
gk: { x: 6, y: 0, w: 73, h: 65 },
lj: { x: 6, y: 0, w: 74, h: 56 },
};
// THE RACKET FAMILY'S THREE STAGES, as a FRACTION of the pool — never against literal hp, the rule
// this whole file already holds (see woundStage). It lived inline in drawZombie; the FALL BEAT needs
// the same answer to know which body collapses and which one gets back up, and two copies of a
@@ -1005,8 +1026,13 @@ export const HOME = { P: { x: 0, z: 22.8 }, O: { x: 0, z: 2.5 } };
// where the near player is allowed to move
export const P_BOUNDS = { x: 6.5, zMin: COURT.netZ + 1.2, zMax: 26 };
// serve toss: release height and upward speed (apex derives from these + gravity)
export const TOSS = { y0: 1.8, vy: 4.6 };
// serve toss: release height and upward speed (apex derives from these + gravity).
// dx/oDx: the tossing HAND's x offset from the frame centre, in metres, at facing +1 — the ball
// must rise over the raised palm, not the head (Oliver's report). MEASURED off the toss frames
// (topmost opaque row = the fingertips, × PLAYER_H/SPRITE_H): p_toss −52 px → −0.44 m,
// o_toss +50 px → +0.42 m (he is drawn from the front, so his left hand is the viewer's right).
// The women's frame reaches further (p_toss_f −70 px → −0.59 m) — TOURS overrides tossDx.
export const TOSS = { y0: 1.8, vy: 4.6, dx: -0.44, oDx: 0.42 };
// shot target depth from the receiving baseline, by aim (up = deep, none = mid, down = short)
export const SHOT_DEPTH = { deep: 3.0, mid: 5.4, short: 8.6 };