diff --git a/assets/_regen.py b/assets/_regen.py index e5a39e9..c974201 100644 --- a/assets/_regen.py +++ b/assets/_regen.py @@ -82,7 +82,8 @@ def colour_match(src, dst_name): def run(name, spec, dry=False): os.makedirs(SCRATCH, exist_ok=True) sprites = spec['sprites'] - ref = ref_sheet(sprites, f'{SCRATCH}/{name}_ref.png', spec.get('scale', 2.0)) + lead = spec.get('ref') # generated for scale, thrown away after + ref = ref_sheet(([lead] if lead else []) + sprites, f'{SCRATCH}/{name}_ref.png', spec.get('scale', 2.0)) sheet = f'{SCRATCH}/{name}_hi.png' prompt = f"{spec['subject']} {STYLE}" if dry: @@ -94,7 +95,7 @@ def run(name, spec, dry=False): print(f'[{name}] FAILED: generator returned no image\n{r.stdout[-400:]}'); return False if touches_edge(sheet): print(f'[{name}] REJECTED: a figure runs off the canvas edge — re-run'); return False - outs = [f'{SCRATCH}/{name}_{i}.png' for i in range(len(sprites))] + outs = [f'{SCRATCH}/{name}_{i}.png' for i in range(len(sprites) + (1 if lead else 0))] cut = ['--cut', spec['cut']] if spec.get('cut') else [] r = subprocess.run([PY, os.path.join(HERE, '_fine.py'), sheet, *outs, '--anchor', str(spec.get('anchor', 0)), '--h', str(spec.get('h', 224)), *cut], @@ -102,6 +103,7 @@ def run(name, spec, dry=False): print(r.stdout.strip() or r.stderr.strip()) if r.returncode or not all(os.path.exists(o) for o in outs): print(f'[{name}] REJECTED: slicing failed (frames merged?) — re-run or add "cut"'); return False + if lead: outs = outs[1:] # the scale reference is not installed before = [density(os.path.join(HERE, s + '.png')) for s in sprites] after = [density(o) for o in outs] # 1.25x, not 1.5x: a sheet that already has blood on it starts with a high edge density, so a diff --git a/assets/_regen_batches.json b/assets/_regen_batches.json index cdbc423..fbf6788 100644 --- a/assets/_regen_batches.json +++ b/assets/_regen_batches.json @@ -104,8 +104,9 @@ "p_swing1", "p_swing2" ], - "subject": "Four frames of the same tennis player in a white shirt, blue shorts and a white headband, drawn as coarse low-resolution pixel art: tossing the ball up for a serve; the smash at full stretch with the racket overhead; and two frames of a forehand swing.", - "anchor": 0 + "ref": "p_idle1", + "anchor": 0, + "subject": "Five frames of the same tennis player in a white shirt, blue shorts and a white headband, seen from BEHIND and slightly to the side, drawn as coarse low-resolution pixel art. First a plain standing frame for scale, then: tossing the ball up for a serve with the racket arm dropped back; the smash at full stretch with the racket overhead and both feet off the ground; and two frames of a forehand swing. In every frame he faces AWAY from the camera, up the court." }, "player_run": { "sprites": [ @@ -114,8 +115,9 @@ "p_run3", "p_run4" ], - "subject": "Four frames of the same tennis player seen from BEHIND in a white shirt and blue shorts, running away from the camera, a four-frame run cycle with no racket, drawn as coarse low-resolution pixel art.", - "anchor": 0 + "ref": "p_idle1", + "anchor": 0, + "subject": "Five frames of the same tennis player in a white shirt and blue shorts, drawn as coarse low-resolution pixel art. Every figure is seen strictly FROM BEHIND \u2014 the back of his head and the back of his shirt face the viewer, no face, no eyes, no nose, he is running or standing AWAY from the camera. First a standing frame for scale, then a four-frame run cycle, running away from the camera with no racket." }, "player_death": { "sprites": [ @@ -231,5 +233,46 @@ ], "anchor": 0, "subject": "Two frames of the same young ball boy in a white polo shirt and shorts sprinting across a tennis court, two further stages of the same run cycle, drawn as coarse low-resolution pixel art." + }, + "player_back": { + "sprites": [ + "p_idle1", + "p_idle2", + "p_bounce", + "p_bounce2" + ], + "ref": "p_run1", + "anchor": 0, + "subject": "Five frames of the same tennis player in a white shirt, blue shorts and a white headband, drawn as coarse low-resolution pixel art. Every figure is seen strictly FROM BEHIND \u2014 the back of his head and the back of his shirt face the viewer, no face, no eyes, no nose, he is running or standing AWAY from the camera. First a running frame for scale, then: two waiting stances holding a racket down at his side, and two frames of him bouncing a ball on the ground beside his LEFT foot before serving." + }, + "runner_front": { + "sprites": [ + "z_f1", + "z_f2", + "z_f3", + "z_f4" + ], + "anchor": 0, + "subject": "Four frames of a lean SPRINTING zombie in a torn tracksuit, drawn as coarse low-resolution pixel art running side-on. Redraw the four frames as the same sprinting zombie CHARGING STRAIGHT AT THE VIEWER, down the court towards the camera \u2014 a front-facing run cycle: his face and chest towards you, one knee driving up towards the camera and the other leg trailing behind him, arms pumping across his body, the whole figure foreshortened as if he is bearing down on you. He must NOT be running sideways across the frame in any of them. Keep the torn tracksuit, the green skin, the same height." + }, + "brute_all_wounds": { + "sprites": [ + "z_gw", + "z_gwb", + "z_gw2", + "z_gw2b" + ], + "anchor": 0, + "subject": "Four frames of the same enormous fat zombie brute, drawn as coarse low-resolution pixel art. The FIRST TWO are the same damage stage \u2014 his LEFT arm torn off at the shoulder leaving a ragged bloody stump, the right arm dragging a rusty pole \u2014 shown as two stages of one stride. The LAST TWO are the next damage stage, BOTH arms gone at the shoulders, again two stages of one stride. Within each pair the injuries must be IDENTICAL and on the same side of the body: only the legs change." + }, + "brute_all_wounds2": { + "sprites": [ + "z_gw3", + "z_gw3b", + "z_gw4", + "z_gw4b" + ], + "anchor": 0, + "subject": "Four frames of the same enormous fat zombie brute, drawn as coarse low-resolution pixel art. The FIRST TWO are the same damage stage \u2014 chest and belly torn wide open with the ribs showing \u2014 as two stages of one stride. The LAST TWO are the final stage, HEADLESS with the ribs out, still standing, again two stages of one stride. Within each pair the injuries must be IDENTICAL and in the same place: only the legs change." } } \ No newline at end of file diff --git a/config.js b/config.js index 0856beb..2cfbd85 100644 --- a/config.js +++ b/config.js @@ -238,6 +238,8 @@ export const TUNE = { shieldDown: 1.5, // s a shield zombie's ('s') chair stays dropped after a block — the kill window meleeRange: 1.7, // m — a racket swish cleaves shamblers this close to you racketHp: 3, // splat-quality hits a racket zombie (mini-boss) takes before it dies + racketStride: 0.42, // m the racket zombie covers per walk frame — his cycle steps off GROUND + // COVERED, so he stops animating when he stops moving racketSpeed: 2.8, // m/s the racket zombie shambles to chase a ball racketQMax: 0.55, // its return quality is capped here (slower, sloppier than the tennis CPU) racketFlub: 0.2, // chance the racket zombie whiffs a return diff --git a/gameplay.js b/gameplay.js index 1ddfba8..b2f99f7 100644 --- a/gameplay.js +++ b/gameplay.js @@ -746,6 +746,7 @@ function racketAI(rz, dt) { const dx = ball.x - rz.x, dz = tz - rz.z, d = Math.hypot(dx, dz); if (d > 0.15) { const step = Math.min(TUNE.racketSpeed * dt, d); + rz.dist = (rz.dist || 0) + step; // ground covered — his walk cycle steps off this, not the clock rz.x += dx / d * step; rz.z += dz / d * step; rz.runT += dt; if (rz.pose !== 'swing') rz.pose = 'run'; if (Math.abs(dx) > 0.2) rz.facing = dx > 0 ? 1 : -1; diff --git a/index.html b/index.html index 225d8bc..14ed24b 100644 --- a/index.html +++ b/index.html @@ -97,6 +97,6 @@
- +