Make the bosses move, and put six of them and three new bodies on court
A chapter boss had no AI at all: it matched no branch in the variant chain and fell through to the crowd's aimless wobble, so "the boss AI owns its x" was a comment describing code that did not exist. Movement is now data on the BOSS_ART row -- pace, shadow, surge, charge, bounce -- and bossAI is the one place it happens. Every behaviour field is optional, so a row carrying only art behaves exactly as a boss did before. Three earlier drafts of those styles failed the same way: written as "walk toward the player's x", which in splatter means arrive in four seconds and stand still, because the player is rooted in pretoss for nearly all of a boss fight. Every style now moves under its own power and only biases toward him. Measured over a simulated minute each: 50-136 m travelled, none static. Three new designs -- THE HEAD COACH (a 5-shot drill 0.14s apart), THE PHYSIO (charges, and withdraws less than it charges, so it reaches you at 38s and that is the fight's clock), THE SPONSOR MASCOT (gap 0, so the whole wall of five leaves in ONE frame and is dodged rather than parried). Harder to kill: 16 -> 20 hp, 9 -> 10 per chapter, plus per-design multipliers. Three new bodies: the ball boy (3 hp, 2 skins, from game 3), the bouncer (6/3, game 8) and the roller man (9/4, game 15), each with a 4-frame walk and a drawn two-frame lurch for every wound stage. Three wound rules became one. The brute had a hardcoded stage array, everything else a single skin behind 'abd'.includes(...), and the boss a fraction of hpMax. The boss's rule reproduces the other two exactly -- verified: the brute still draws z_gw/gw2/gw3/gw4 at hp 4/3/2/1 -- and keeps working when hp is not one-per-skin, which all three new bodies need. So a multi-hit body is now a row in config.ZWOUND and no code. bleed() reads the same call, which also gave the steward the trickle his wound skin had always implied. Fixes a real crash on the way past: killPlayer empties shots[] while the shots loop is mid-iteration, so every remaining index read undefined and the frame threw. The mascot's five-at-once volley makes it easy to hit; the shipped 3-shot volley could always have hit it. _regen.py gains two style blocks and a check tier. One constant used to say "keep every colour exactly as the reference" -- right for a remaster, and it fights every word of the subject otherwise. newart is for a new character, state for the same character wounded or mid-throw; using newart for the wound sheets licensed a repaint and got one, five came back with living tan skin. And the detail gate ratchets against the file it replaces, so check: any exists for re-rolling already-fine art to fix something the gate cannot see, such as colour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0774a2a31d
commit
4cda6bde76
64 files changed
+694
-78
No files matched your search
+88
-4
@@ -79,6 +79,29 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu
|
||||
and only then extend, or you have silently nerfed the part of the game that was already fine.
|
||||
The raw dials do run out around game 25 by design; bosses and ranged attacks (steps 4-5) are what
|
||||
carry escalation past that, not more bodies.
|
||||
- **`woundStage(zb, n)` in config.js is the ONE rule for how damage READS.** 0 = unhurt, 1..n = the
|
||||
skins, spread evenly over `hpMax`. There used to be three rules: the brute's hardcoded stage array
|
||||
indexed by absolute hp, a single skin behind `'abd'.includes(...)`, and the boss's fraction-of-hpMax.
|
||||
The boss's rule reproduces the other two EXACTLY (5 hp over 4 skins is one skin per hit; 2 hp over
|
||||
1 skin is the shambler) and keeps working when hp is not one-per-skin — which the ball boy (3 hp,
|
||||
2 skins), bouncer (6/3) and roller man (9/4) all need. So a new multi-hit body is now one row in
|
||||
`config.ZWOUND` (healthiest first, `…b` stride twin looked up automatically) and NO code.
|
||||
`bleed()`'s anchors are read through the same call and are deliberately the same length as that
|
||||
variant's ZWOUND row, so the blood and the drawing it comes out of cannot drift apart — they used
|
||||
to be written by hand against different constants. **'r' and 'B' keep their own skins** (the boss's
|
||||
on its design row) but go through the same `woundStage`.
|
||||
- **A chapter boss's MOVEMENT is data on its `BOSS_ART` row** (`move`, plus optional `hpMul`,
|
||||
`speedMul`, `volley`, `gap`, `spread`, `cycle`, `wind`, `shot`), and `bossAI` in zombies.js is the
|
||||
only place it moves. Every field is optional, so a row carrying nothing but art behaves exactly as a
|
||||
boss did before there were any. Three things that are load-bearing and were each a bug first:
|
||||
**(a)** the generic forward step at the top of the body loop SKIPS `'B'` — bossAI owns both axes,
|
||||
and leaving it in advanced the boss twice per frame; **(b)** the stride steps off `zb.dist`
|
||||
(ground covered on both axes), not `zb.z`, or a boss strafing sideways slides with its feet frozen;
|
||||
**(c)** a boss holds a z BAND (`bossZMin..bossZMax`) except while charging — uncapped, a fight ended
|
||||
by the boss simply arriving after about a minute, and every extra hp made that MORE likely, not less.
|
||||
**No style may rely on the player moving to create its own motion**: in splatter he is rooted in
|
||||
`pretoss` for nearly all of a boss fight, so "close on the player's x" arrives, stops, and is static
|
||||
for the rest of the fight. Every style moves under its own power and only BIASES toward him.
|
||||
- **`wound(zb, amount, fx)` in zombies.js is the ONE place damage is applied to a multi-hit body.**
|
||||
Fractional amounts are legal (a pellet chips `TUNE.dmgPellet` = 0.34 of a point); they bank on
|
||||
`zb.chip` and only WHOLE points come off `zb.hp`, because the wound skins and `bleed()` anchors
|
||||
@@ -384,6 +407,24 @@ Clear the intro first: `document.getElementById('intro')?.remove()`.
|
||||
survives as a rim on a green court.
|
||||
12. **Two-row "style reference / redraw this" input does NOT work.** The model returns both rows and
|
||||
invents a background. Describe the target detail level in words instead.
|
||||
12b. **`_regen.py` has THREE style blocks now, and picking the wrong one costs rolls.** The shared
|
||||
block used to be one constant that always said *"keep every pose, every colour EXACTLY as in the
|
||||
reference"* — correct for a remaster, and it fights every word of the subject for anything else.
|
||||
- default (`STYLE`): remastering a sprite that already exists.
|
||||
- `"newart": true` (`NEW_STYLE`): a NEW character drawn in the reference's style. Drops the
|
||||
fidelity clause and says explicitly not to copy the reference's body or outfit.
|
||||
- `"state": true` (`STATE_STYLE`): the SAME character in a new state — wounded, mid-throw. This is
|
||||
the one that is easy to get wrong, because `newart` looks right and isn't: it licenses a repaint,
|
||||
and it got one. Five wound sheets came back with living TAN skin and a redesigned shirt while the
|
||||
healthy frame stayed a grey-green corpse, and the coach's navy tracksuit came back mid-blue
|
||||
(torso RGB blue channel 111 -> 138). `STATE_STYLE` pins colours, clothing and build to the
|
||||
reference and states outright that he is a corpse, never pink or tan.
|
||||
**A brand-new sprite name must exist on disk before its batch runs** — `run()` measures `density()`
|
||||
of the file it is about to replace, and `colour_match` opens it. Seed each new name by copying a
|
||||
body of the right size and facing, and use `check: same`/`loose` so `colour_match` is skipped and
|
||||
the seed's palette is NOT inherited. The trap in that: a batch that silently fails leaves a perfect
|
||||
copy of another character behind, which renders as working art. Assert every new PNG actually
|
||||
differs from its seed before shipping (the scratchpad `check_seeds.py` does exactly this).
|
||||
13. **`assets/_derive.py` is where a frame that must MATCH another frame is made.** Three of them
|
||||
live there now: `p_bounce2` (a per-column shear of `p_bounce` — the free arm bends, nothing else
|
||||
moves), `p_toss` (the drawn-in ball cut out, canvas height KEPT so drawChar keeps his scale) and
|
||||
@@ -409,6 +450,47 @@ Known generator quirks, all still true:
|
||||
- **Colour-match any regenerated frame** of an existing pair before shipping: fit a per-channel
|
||||
linear map onto the original's mean and sd, then verify with a mean-difference number.
|
||||
|
||||
## Session log: the roster grows — moving bosses, six of them, three new bodies (2026-08-03)
|
||||
|
||||
Oliver asked for five things. This is the first half: **enemies first, weapons second**, his call, so
|
||||
the arsenal could be tuned against the harder game rather than the other way round.
|
||||
|
||||
**Bosses move now, and the style is data.** A boss previously matched NO branch in the variant chain
|
||||
and fell through to the crowd's aimless ±0.15 m/s wobble — "the boss AI owns its x" was a comment
|
||||
describing code that did not exist. Six styles, one per design: `pace` (groundskeeper, and the coach
|
||||
at 1.9x), `shadow` (line judge — paces a band centred on YOU), `surge` (chair umpire — rolls up and
|
||||
down the whole depth band), `charge` (physio), `bounce` (mascot). Measured over a simulated minute
|
||||
each: 50–136 m travelled, none static. **Three earlier drafts of these styles failed the same way and
|
||||
it is worth knowing why: they were written as "walk toward the player's x", which in splatter means
|
||||
arrive in four seconds and stand still for the rest of the fight, because the player is rooted in
|
||||
`pretoss` and never moves.** Measure `xSpan` over 3600 steps before believing any movement change.
|
||||
|
||||
**Three new bosses**: THE HEAD COACH (rapid 5-shot drill, 0.14 s apart), THE PHYSIO (charges — it
|
||||
withdraws LESS than it charges, so it is a couple of metres nearer after each rush and reaches you at
|
||||
about 38 s, which is the fight's clock), THE SPONSOR MASCOT (a `gap: 0` volley — the whole wall of
|
||||
five leaves in ONE frame, so it is dodged, not parried; the salvo loop is a `while` for exactly this).
|
||||
Harder to kill: `bossHp` 16 -> 20, `bossHpPerChapter` 9 -> 10, plus per-design `hpMul`.
|
||||
**Chapter 6 is now 91 hp — the number most likely to want tuning after a real playtest.**
|
||||
|
||||
**Three new bodies**: ball boy `'e'` (3 hp, 2 skins, from game 3), bouncer `'h'` (6/3, game 8), roller
|
||||
man `'k'` (9/4, game 15). Named "roller man" and not "groundsman" so he cannot be confused with the
|
||||
GROUNDSKEEPER boss.
|
||||
|
||||
**One real bug found, and it was pre-existing.** `killPlayer()` empties `shots[]` while the shots loop
|
||||
is mid-iteration, so every remaining index reads `undefined` and the frame throws. The mascot's
|
||||
five-at-once volley makes it easy to hit; the shipped 3-shot volley could always have hit it. Fixed
|
||||
with the same `if (!s) continue` guard the body loop above it already carries, and for the same reason.
|
||||
|
||||
**Verification that actually caught things** (all via `window.T`, RAF halted):
|
||||
- `drawImage` instrumentation per design: all six bosses and all three new bodies draw their OWN
|
||||
frames, both `…w`/`…wb` stride twins included. Nothing silently falls back to design 1.
|
||||
- Differential check on the refactor: the brute still draws `z_gw/gw2/gw3/gw4` at hp 4/3/2/1 — the
|
||||
new shared `woundStage` is byte-identical to the array it replaced.
|
||||
- Foot-gap spread: ball boy 76% (the shipped shambler is 36%, the brute 39%). Boss stride-pair
|
||||
difference 34–54%, inside the shipped range of 30–83%.
|
||||
- 26-game soak: no exceptions, no stall, `mgT` back to 0, gore capped 40, piles capped 30, and
|
||||
`e`/`h`/`k`/`B` all appeared in it.
|
||||
|
||||
## Session log: court types (2026-08-01)
|
||||
|
||||
Three courts, picked on a second start-screen stage. A court is pure DATA — `COURTS` in config.js
|
||||
@@ -746,11 +828,13 @@ Nothing here is broken; these are known gaps and unverified risks.
|
||||
scale but gained no detail in the remaster, and they are the two most-seen poses in the game.
|
||||
Fix by hand-slicing `_sheet_player2/3` and `_sheet_opp2/3` at `IDLE_TARGET_H` 224 rather than going
|
||||
through the signature matcher, which assigned those frames elsewhere.
|
||||
4. **Boss designs are asymmetric.** Design 1 (GROUNDSKEEPER) has 3 wound stages and 4 throw frames;
|
||||
designs 2 (LINE JUDGE) and 3 (CHAIR UMPIRE) have 1 wound skin and 2 throw frames each. Legal — the
|
||||
4. **Boss designs are asymmetric — still, for two of the six.** Designs 1 (GROUNDSKEEPER), 4 (HEAD
|
||||
COACH), 5 (PHYSIO) and 6 (SPONSOR MASCOT) have 3 wound stages and 4 throw frames each; designs 2
|
||||
(LINE JUDGE) and 3 (CHAIR UMPIRE) still have 1 wound skin and 2 throw frames. Legal — the
|
||||
`BOSS_ART` indices are clamped — but a chapter-2 boss degrades in one visible step instead of three.
|
||||
5. **Only 3 boss designs, cycling.** Chapter 4 repeats the groundskeeper at 43 hp and 3.67 m. A fourth
|
||||
design is a sheet plus one row in `BOSS_ART`.
|
||||
Fixing them is now just two batches in the shape of `boss_coach_wounds` / `boss_coach_throw`.
|
||||
5. ~~**Only 3 boss designs, cycling.**~~ CLOSED 2026-08-03 — six now, so a chapter does not repeat a
|
||||
design until chapter 7. A seventh is still a sheet plus one row in `BOSS_ART`.
|
||||
6. **Landscape is under-maintained.** The readability pass (camZ/camY/camF/horizon), the adaptive
|
||||
height and the safe-area work are all portrait-only; landscape still runs the old camera and a fixed
|
||||
480x270. Portrait is the stated primary target, but landscape has drifted and is untested.
|
||||
|
||||
Reference in new issue
Block a user