Commit Graph
477 Commits
Author SHA1 Message Date
wuwiandClaude Fable 5 8fd5055e5c Half-volleys: a rising ball is always scoopable; scoreboard tap shows the swing log
Field data (miss log) showed the dominant unfair miss: the assist parks
you on the landing spot, the ball spends the whole swing window below
minY rising off the bounce at your feet, and becomes hittable one frame
after the window closes. Root fix: the below-ankles rejection now only
applies to a DROPPING ball — rising post-bounce balls can be scooped at
any height (a half-volley). Feet-bounce taps go from ~0 to 19/30 winning
frames; 30 simulated real points now log 50 hits, 0 whiffs.

Also: swing-log snapshots record the same swing-timer value trySwing
tests (removes the one-frame misclassification behind 'unclassified'),
and tapping the scoreboard toggles an on-screen dump of the last 9 log
entries so live situations can be screenshotted from the phone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 21:14:34 +02:00
wuwiandClaude Fable 5 d6d00929bb Miss log: every tennis swing logs why it failed, last reason shown on HUD
Whiffed swings sample the ball's approach each frame and classify the
miss at swing expiry: tap too early (with ms), tap too late, ball line
wide, too high/low, out of reach. Balls that die on your side without
a swing log as PASSED with closest-approach data. Ring buffer of 50 at
T.G.missLog; the latest miss reason flashes on the HUD for 3.5s
(TUNE.missHud to disable). CPU serve-return benchmark still 11/12.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 21:02:11 +02:00
wuwiandClaude Fable 5 943b7da9fd Zombies get faster over time; when one reaches you, you're eaten
splatT now ticks while in splatter mode and scales spawn speed by
1 + min(1.5, splatT/70). A zombie within 0.7m of the player ends the
run: gore burst, 'game, set, massacre', dark EATEN. overlay with final
games and a tap-to-restart (gated 0.7s so death-spam doesn't skip it).
Zombies keep shambling on the end screen but no longer score. Also
verified the two prior unverified items: quick-serve cadence (0.25s
between balls, flick = instant aimed serve) and flick aiming across
the net (far/near lane selection works as designed). HANDOFF updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:54:09 +02:00
wuwiandClaude Fable 5 a5a6f550a3 Volleys get wider reach and swing window; serve volleys no longer fault
A ball passing pre-bounce moves at full pace with no bounce cue to time
against, so marker-behind volleys needed frame-tight taps. Pre-bounce
player contact now widens reach x1.4 and the swing window x1.8 (VOLLEY
in config.js). Also clear G.wasServe in doHit so a serve returned out
of the air doesn't fault-check the return, and launch.json uses
autoPort.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:46:36 +02:00
wuwiandClaude Fable 5 e7aab295d4 WIP splatter feedback round + HANDOFF.md for session continuation
Done (unverified): faster serve cadence in splatter (overT 0.25, serve from
where you stand, flick = instant aimed quick serve), aim-anywhere targeting
relative to the player. Not done: zombie speed ramp (G.splatT exists but is
never incremented), eaten-on-touch death + restart screen. Full state and
plans in HANDOFF.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:28:00 +02:00
wuwiandClaude Fable 5 60c5f72a0a Splatter aiming targets zombies anywhere; triple-tap the umpire to skip ahead
- doHit/hitServe in splatter mode lock onto zombies instead of tennis court
  depths: your flick picks the lane and depth band, the shot targets the best
  match anywhere on the court (including your own side) with a walking lead;
  serves auto-track the most advanced shambler
- hidden shortcut: tap/click the umpire three times within 1.6s during tennis
  mode to summon the transition immediately

Verified: near-side and far zombies killable, cheat triggers transition,
tennis mode targeting untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 16:38:27 +02:00
wuwiandClaude Fable 5 69e3a0c310 Zombie town: the match continues
Win your first game and the applause cuts off mid-clap. The palette drains,
the umpire lowers his voice ('Quiet... please'), your opponent turns — torn
red polo, dragging his racket — and shamblers come over the billboard wall.
The scoreboard rewrites CPU to THEM. 'Play.'

- zombies.js: shamblers walk your way; quality shots splatter them (gore
  decal + particle burst + wet synth splat), weak sitters only stagger them
  back; one crossing your baseline is a point for THEM
- tennis scoring machinery keeps running untouched: fifteen, thirty, deuce,
  and 'Game, set... massacre' on a game win, all in recorded umpire English
- ball boys mop the gore they pass (their destiny fulfilled)
- transition: palette lerp, scripted spawns incl. the turned opponent,
  new recorded lines (quiet_dark, remain_seated, play, new_balls)
- moan ambience, drained splatter palette, all zombie params in TUNE

Verified: transition triggers on first game win, splat/stagger/baseline
scoring all correct, dead-ball flow feeds fresh serves, tennis mode
regression clean (6/6 returns, 6/6 points resolved).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 16:26:49 +02:00
wuwiandClaude Fable 5 f221ff84df Taller umpire chair; umpire proportionally smaller
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 15:58:18 +02:00
wuwiandClaude Fable 5 9d52a43d27 Umpire faces the court (profile sprite instead of front view)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 15:54:24 +02:00
wuwiandClaude Fable 5 c506a631c2 Split the monolith into ES modules; hoist scattered magic numbers
game.js (830 lines) -> 9 modules, native ES modules so still zero build step:
  dom      canvas handles (imports nothing; keeps cycles harmless)
  config   TUNE (live-tunable) + structural constants
  state    all mutable game state (G) + setupPoint/sendBoy
  audio    sfx synthesis, recorded umpire voice, reverb, speech fallback
  input    keyboard + touch, exports axes and takeFlickAim()
  gameplay ballistics, swings, serve, rules, AI, update loop
  render   camera, viewport, sprites, crowd, all drawing
  panel    T tuning UI
  main     boot, loop, intro, window.T test hooks

Deduped into named constants: bounce tempo (was in 3 places), contact
height band, intercept zone depth, home positions, player bounds, toss
params, shot depths, pose hold time, sprite scale, actor heights. Also
extracted drawShadow/drawBall/expirePose from repeated code.

One intentional import cycle (input <-> gameplay), safe: both sides only
call functions at runtime, never during module evaluation.

Regression-verified in browser sims: point cycle, CPU returns 11/12,
assist contract 8/8, corner-winner gradient intact (0/12 perfect corners
returned, 10/12 center balls returned).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 15:38:15 +02:00
wuwiandClaude Fable 5 f2bf21f252 White landing ring: closed-form prediction kills the jitter
predictLanding stepped the trajectory in 10ms chunks and returned the first
point past ground level — an overshoot of up to ~25cm that varied per frame,
which is what actually jittered (the earlier pixel-snap only addressed
sub-pixel shimmer). The landing point of a parabola has an exact solution;
computing it directly makes the prediction identical every frame.
Measured: max frame-to-frame movement 5.5mm, was ~25cm.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 15:25:43 +02:00
wuwiandClaude Fable 5 0bf6937121 Serve bounce sprite switches exactly at the ball's apex
Hand-raised frame while the ball rises, pat-down frame from the apex on —
synced to the ball's direction instead of a height threshold. Includes
Oliver's hand-tuned ball offset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 15:18:32 +02:00
wuwiandClaude Fable 5 7069723042 Re-slice corrected bounce sheet; ball under the hand; stable landing rings
- p_bounce/p_bounce2 re-extracted from Oliver's corrected _sheet_pbounce.png
  (racket properly in the right hand)
- bounce ball offset 0.4 -> 0.32 so it sits under the reaching hand
- landing rings draw with integer-snapped center/radii and a solid 2px
  stroke — the sub-pixel anti-aliasing shimmer read as flickering

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 15:11:30 +02:00
wuwiandClaude Fable 5 91da24f1a7 Serve stance can no longer render mirrored
The sprite file was fine — drawChar mirrors sprites by P.facing, which
persisted from the last run direction, so the pre-serve bounce stance
randomly flipped (racket in the wrong hand, ball beside the wrong hand).
setupPoint now resets both players to the canonical righty stance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 15:01:59 +02:00
wuwiandClaude Fable 5 34f31a65a2 4-frame ball boy run cycle; steady two-frame serve bounce stance
- boy runs on a proper 4-phase cycle (stride, pass, mirrored stride, pass)
- server holds the crouched bounce stance through pretoss instead of
  flickering to standing idle; hand-down/hand-up frames sync with the ball,
  which now bounces floor-to-hand height

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 14:55:21 +02:00
wuwiandClaude Fable 5 456e168c27 Topspin pace model, beatable CPU, boy run cycle, umpire reverb, z-order fix
- fake topspin: quality shots dive under extra effective gravity, so real pace
  (up to 28 m/s) can clear the net AND land in — without it the net-clearance
  loop silently slowed every shot to a coverable lob, which is why the CPU
  returned everything regardless of aim
- no grass-scraping: balls below ankle height (0.24m) are past you, both sides
- CPU: 0.45s shot-read delay, 4.3 m/s legs, half-speed recovery between shots.
  Verified gradient: perfect/good corner shots are winners, center balls and
  weak shots still come back
- player aimed shots go wider (3.3m vs 2.7m)
- ball boy: nano-banana mirrored stride frames (was stride-vs-hop), height
  1.2m, z-ordered against umpire and CPU (no more running over the chair)
- umpire voice through a synthesized convolver reverb — center-court PA echo

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 14:41:58 +02:00
wuwiandClaude Fable 5 f3c2af4ef5 Catchability contract: every ball reachable via auto-run, timing is the skill
- intercept() now picks a point the runner can actually reach in time
  (travel-time feasibility), falling back to the deepest hittable point
- tapping no longer slows the auto-run: anticipating a shot doesn't lose it
- assist legs slightly superhuman (1.15x) so positioning is guaranteed
- ball boy run frames regenerated with contrasting stride/gather poses
  (previous pair was near-identical, which read as no animation)

Verified: 10/10 CPU shots end inside the honest 1.3m contact radius with
zero manual movement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 14:21:52 +02:00
wuwiandClaude Fable 5 769dea8755 Ball boys sprint across the field; honest hit registration
- ball boy redesign: spawns at the sideline on point end, sprints across at
  the ball's depth with a proper side-view run animation, scoops the ball
  mid-run, despawns off the other side (no more net-post idling/sliding)
- hit feel: ball snaps to the racket at the contact frame so hits visually
  connect; contact radius tightened 1.7 -> 1.3 (assist does the positioning,
  the tap must be honest); missed swings now play an audible swish

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 14:12:11 +02:00
wuwiandClaude Fable 5 9e6e686cff Landing rings, ball-boy fetching, 2x sprite density, assist flicker fix
- white ring shows where the incoming ball will land; yellow/red ring shows
  where your shot will land (red = going out) — Mario-Tennis-style feedback
- ball boys wait at the net posts, fetch every dead ball, and trot home
- canvas renders at 2x density, all sprites re-sliced at 112px; run/swing
  sheets regenerated in the finer style
- assist: 0.35m deadzone + facing hysteresis kills the left/right flicker;
  player reach 1.5 -> 1.7

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 13:56:18 +02:00
wuwiandClaude Fable 5 dc1e12b9b6 Recorded umpire voice, court staff, out-zone borders, intro overlay, no-cache deploys
- 23 pre-recorded umpire lines (macOS say, en-GB Daniel) played via WebAudio,
  works on mobile; speechSynthesis stays as fallback only
- umpire chair + two ball boys (generated sprites) dress the court
- court redrawn: doubles alleys, white perimeter border, clearly darker out area
- intro overlay with control symbols; first tap dismisses it and unlocks audio
- nginx now serves everything Cache-Control: no-cache so a reload always
  revalidates and picks up new deploys

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 13:38:28 +02:00
wuwiandClaude Fable 5 d69896ddb8 Assist predicts the interception point instead of chasing the ball
Same predictive brain as the CPU: simulate the trajectory, run to where the
ball will be hittable (x and z), and drift back to home position after your
own shot. Full speed while chasing, 55% while recovering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:45:36 +02:00
wuwiandClaude Fable 5 9268529c8f Generated idle/serve animation sprites; assist runs at full speed with animation
- new 5-pose sheets per character: idle weight-shift x2, ball-bounce crouch,
  serve trophy pose (toss), overhead smash
- serve shows toss stance through the ball toss, smash frame on contact
- auto-assist: full player speed, adds depth tracking after the bounce,
  and drives the run animation + facing like manual movement

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:22:10 +02:00
wuwiandClaude Fable 5 65bd187633 CPU returns clear the net; serve/idle/run animations; iOS speech unlock
- doHit now trades pace for loft when contact is low, margin scales with
  shot quality; AI prefers waist-high interception instead of ankle-height
- serve stance held through toss, run cycle with bob, receiver split-step,
  server bounces the ball while waiting
- speechSynthesis unlocked inside first touch gesture for iOS

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:12:48 +02:00
wuwiandClaude Fable 5 c176ff6950 Fix CPU never returning: gate/contact radius mismatch caused guaranteed early whiff
The AI armed its swing at aiReach+0.4 but contact required aiReach, so it
always swung one guaranteed-whiff too early and the 0.4s cooldown consumed
the whole approach window. Also: predictive interception instead of ball-chasing,
shorter whiff recovery, slightly faster legs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:02:47 +02:00
wuwiandClaude Fable 5 05c7d636d8 Portrait mode and one-handed touch controls (drag/flick/tap)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:51:21 +02:00
wuwiandClaude Fable 5 c2ba256b3b Add touch controls and nginx Dockerfile for Coolify deploy
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:30:26 +02:00
wuwi 7bedb2d215 initial commit 2026-07-11 09:23:01 +02:00