The stand stops wobbling: a real clap frame, a wave, and three spectators who have already turned
The wobble was never the animation. x_crowd2 was a RE-ROLL of x_crowd, not a pose of it — 13-16% of its pixels differed, over faces, torsos, seat backs and the railing — so swapping 16 columns at 4.5 Hz re-hashed a strip of crowd twice a second. assets/_crowdpass.py rebuilds the pair: the clap frame is now the seated frame plus 36 genuine raises, each contained in ONE of render's columns, so any column swap is seamless by construction and an empty column is invisible instead of being a wobble. The three courts are one composition in three palettes, so a raise found in one court's roll is transplanted into the other two through a colour map learnt from the aligned seated pair. The same tool paints the crowd, because at ~8 logical px a spectator detail can only mean chroma and silhouette: 55 shirts and 44 heads of hair in colours the court does not own, 28 caps, 4 flags above the heads, 8 emptied seats, 8 people permanently standing — and 3 who are already turned, grey-green, mid-row, who are also the only three who never applaud. Movement, one draw path and three behaviours: applause scatters per column, a WAVE crosses the stand for a moment big enough to deserve one (crowd.pop >= 0.75), and with nobody cheering ONE column stands for a third of every TUNE.fidgetEvery, so the stand between points is no longer a photograph. Paparazzi now scale with the moment: cheer() records the size of it (vol was already exactly that at every call site) and queues shutters proportional to it, drained at a fifth of the normal gap. pop is held at the loudest while an applause is live, so a won rally landing inside a match-point ovation cannot demote the stand to a polite clap — that rule has a test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
76ea1d0d79
commit
74afd5988c
14 files changed
+585
-42
No files matched your search
+101
-18
@@ -20,9 +20,11 @@ anything, so if you add a `return` for an awkward deal, check the body still run
|
||||
the state-machine contracts that have historically produced the show-stoppers. The Dockerfile runs
|
||||
it as a build stage, so a red suite cannot deploy — but finding out from Coolify is the slow way.
|
||||
|
||||
Last updated 2026-08-20 (THE SHELL: a Capacitor iOS project, haptics on the existing sfx call
|
||||
Last updated 2026-08-21 (THE STAND: the clap frame rebuilt as a real pose of the seated one, which
|
||||
is what the "wobble" was; an art pass for chroma and silhouette; the wave, the idle fidget, and
|
||||
paparazzi that scale with the moment. See "THE STAND STOPS WOBBLING". Before that 2026-08-20 (THE SHELL: a Capacitor iOS project, haptics on the existing sfx call
|
||||
sites, portrait enforced in Info.plist, and swipe-left-to-go-back in the menus — the web build
|
||||
unchanged and still primary. See "THE SHELL". Before that, same day: the friend across the net gets his OWN sheets — the player's kit, drawn
|
||||
unchanged and still primary. See "THE SHELL"). Before that, same day: the friend across the net gets his OWN sheets — the player's kit, drawn
|
||||
from the front, so his pick dyes the same garment yours does; the setup screen shows that front too.
|
||||
See "THE FRIEND ACROSS THE NET WEARS YOUR KIT". Before that 2026-08-18: the store model in LATER revised: free download + €2.99 unlock-everything
|
||||
IAP with the free-tier gates, superseding paid-upfront; before that 2026-08-15, two sessions: the
|
||||
@@ -345,12 +347,19 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu
|
||||
`mopRound` walks him to a stain, holds him there for `TUNE.boyMopDwell` while the push cycle
|
||||
animates in place, then moves him on. Three sheets: `x_boymop` (stopped, working),
|
||||
`x_boycarry` (walking with the mop shouldered), `x_boyrun` (tennis-mode fetch).
|
||||
- **Crowd applause is a real two-frame swap** (`x_crowd` seated / `x_crowd2` arms up), per column on
|
||||
its own phase, with **no vertical offset**. A fractional `dy` on a scaled image re-snaps its pixel
|
||||
rows every frame under nearest-neighbour — that boiling beside the HUD was the "scoreboard
|
||||
flicker" (the board art is 99.9% opaque; nothing was ever showing through it).
|
||||
**If you touch one crowd frame you must apply the identical curve to the other**, or the swap
|
||||
starts flashing brightness. `assets/_sheet_crowd1.png` is the pre-brightening original.
|
||||
- **THE CLAP FRAME IS THE SEATED FRAME PLUS FORTY PAIRS OF ARMS — nothing else may differ.**
|
||||
`x_crowd2` is built FROM `x_crowd` by `assets/_crowdpass.py`: identical pixel for pixel except
|
||||
inside blobs that are genuinely arms-up and that each sit **inside one of render's 16 columns**
|
||||
(`COLS_CROWD`, and `COLS` in that script — change one and you must recut the sheets). That is
|
||||
what makes a column swap seamless, and what lets the same machinery drive three behaviours
|
||||
(applause / wave / idle fidget) off one sheet. It was NOT true before 2026-08-21: the clap sheet
|
||||
was a re-roll that differed from the seated one in 13-16% of its pixels, everywhere, so a swapped
|
||||
column re-hashed the crowd rather than raising arms — Oliver, "the crowd wobbles with a client
|
||||
side animation right now.. that feels odd." **Never hand-edit one crowd frame.** Edit the seated
|
||||
sheet and re-run the tool, which rebuilds its partner. Draws stay integer-aligned with **no
|
||||
vertical offset**: a fractional `dy` on a scaled image re-snaps its pixel rows every frame under
|
||||
nearest-neighbour — that boiling beside the HUD was the "scoreboard flicker".
|
||||
`assets/_sheet_crowd1.png` is the pre-brightening original.
|
||||
|
||||
## Settled feel decisions — do not regress silently
|
||||
- **Far bodies draw off a flattened scale curve; the camera holds still.** `TUNE.farK` 0.25
|
||||
@@ -2298,6 +2307,7 @@ it is the WRONG tool, superseded by the reference-sheet flow. Route by job:
|
||||
| THE SAME CHARACTER IN SOMEONE ELSE'S CLOTHES, every pose at once | `_frontroll.py` — famroll's trick pointed at the tennis poses: image 1 is the ten `o_*` frames composed from disk, image 2 is the body to dress them in. Two calls made the whole netplay family. Ten `_regen` batches would have been ten chances for the man to drift. |
|
||||
| Single-sheet remaster, boss roster, any non-family sprite | `_regen.py` + `_regen_batches.json` — the only jobs it is still for. |
|
||||
| Standalone art (logo, panels) | `_key.py`; coarse legacy sheets, `_slice.py`. |
|
||||
| The CROWD sheets — either frame, any court | `_crowdpass.py`. It owns the seated/clap pair as ONE artefact; there is no such thing as editing half of it. Not idempotent: `git checkout assets/x_crowd*.png` before every run. |
|
||||
|
||||
**And when installed sprites look WORSE than the `_refcheck/fam_*.png` sheet they came from, the
|
||||
suspect is the CUT, not the roll** — the sheets on disk are the evidence, and a re-cut is free (see
|
||||
@@ -2538,6 +2548,69 @@ The gates, and the numbers they were calibrated on:
|
||||
**SUPERSEDED 2026-08-12 — see the next section.** Those 31 frames were not art: `--verify` found
|
||||
27 of them dead. The brute is now live (`ZFAMS` has `'br'`, the KEEPER wears it) off ONE sheet.
|
||||
|
||||
## THE STAND STOPS WOBBLING, AND STARTS BEING A CROWD (2026-08-21)
|
||||
|
||||
*"the crowd spritesheet could use some love (resolution and movement wise)... also the crowd wobbles
|
||||
with a client side animation right now.. that feels odd. also add with a little bit of humor"* — and,
|
||||
mid-build: *"the crowd could be more detailed (and I don't [mean] the technical resolution of the
|
||||
image)"*.
|
||||
|
||||
**The wobble was never the animation.** `x_crowd2` was a RE-ROLL of `x_crowd`, not a pose of it:
|
||||
13-16% of its pixels differed, spread over faces, torsos, seat backs and the railing. Swapping 16
|
||||
columns between them at 4.5 Hz therefore re-hashed a strip of crowd twice a second. Every knob you
|
||||
could turn on the animation was the wrong knob.
|
||||
|
||||
`assets/_crowdpass.py` rebuilds the pair instead, and establishes the invariant in "Architecture &
|
||||
invariants". Three things fall out of the fact that **the three courts are one composition in three
|
||||
palettes, pixel-aligned**:
|
||||
|
||||
- the art pass finds its people ONCE (on the clay sheet, the cleanest — grass carries the brightening
|
||||
curve's noise) and applies the same masks to all three, so the three crowds stay the same people;
|
||||
- a raise that exists in only ONE court's roll is **transplanted** into the other two through a colour
|
||||
map learnt from the aligned seated pair (`colour_map`). Per court the honest raises number about
|
||||
fifteen; pooled, 44 people can get to their feet. Eight of them are baked STANDING into both frames;
|
||||
- everything the art pass paints is excluded from the clap set, so no painted pixel can ever flicker.
|
||||
That ordering is load-bearing — the clap set is chosen FIRST and reserved, because a crowd this
|
||||
heavily painted leaves nothing that is allowed to move if you paint first.
|
||||
|
||||
**RESOLUTION IS NOT THE PROBLEM AND ADDING PIXELS WOULD NOT HELP.** Measured: the sheet is 1408x768
|
||||
and the stand band is ~1080x784 device px on a 3x phone, i.e. already ~1:1 vertically and 0.77x
|
||||
horizontally (in landscape it is 1.36x wide by 0.54x tall — the sheet is stretched to whatever band
|
||||
exists, so spectators are anamorphic, differently per orientation; see OPEN). At that size a
|
||||
spectator is ~8 logical px, so "more detailed" can only mean **chroma and silhouette**, which is what
|
||||
the pass adds: 55 shirts and 44 heads of hair in colours the court does not own, 28 of those as caps,
|
||||
4 flags on poles above the heads, 8 emptied seats so no row ends in a straight line of identical
|
||||
heads, and 8 people permanently standing.
|
||||
|
||||
**HUMOUR, and it costs nothing:** three spectators are already TURNED (grey-green skin, mid-row,
|
||||
nobody around them reacts) — and because a painted pixel is never in the clap set, they are also the
|
||||
only three in the stand who never applaud. It is a hint at the parked stands-turn-into-zombies step,
|
||||
not that step.
|
||||
|
||||
**MOVEMENT — one draw path, three behaviours** (`drawClapColumns`):
|
||||
|
||||
- **applause**: every column on its own phase, 3.5 Hz — a scatter of people rising and sitting;
|
||||
- **THE WAVE**: a band ~2.5 columns wide crossing the stand every 1.4 s, and only when `crowd.pop >=
|
||||
0.75` — the set, the match, and the ones the umpire calls unbelievable. A game won is `cheer(2.4,
|
||||
0.6)` and a wave on every held serve is a wave nobody looks up for;
|
||||
- **the FIDGET**: with nobody cheering, ONE column stands for a third of every `TUNE.fidgetEvery`
|
||||
(4.5 s). Between points the stand used to be a photograph. The column walks `slot * 5 + 3` — five
|
||||
is coprime with 16 so it visits every column, where the first cut (`* 7919`, i.e. `* 15`) moved one
|
||||
seat sideways each time and read as a march.
|
||||
|
||||
**PAPARAZZI SCALE WITH THE MOMENT** (Oliver: "increase paparazzi burst during bigger events").
|
||||
`cheer(seconds, vol)` now records `crowd.pop` (the size of the moment, 0..1 — vol was already exactly
|
||||
that at every call site) and queues `crowd.burst` shutters proportional to it; `tickFlashes` drains
|
||||
that queue at a fifth of the normal gap, and outside a burst the rate rides `pop` too. **`pop` is
|
||||
held at the loudest while an applause is live** — a won rally landing inside a match-point ovation
|
||||
must not demote the stand to a polite clap, and `cheer()` is called from a dozen places that overlap.
|
||||
That is the one new state-machine rule here, and it has a test.
|
||||
|
||||
**What this did NOT do:** the crowd is still the same drawing. A genuinely richer stand — more
|
||||
distinct silhouettes, people doing different things — needs a re-roll of the sheet, which this
|
||||
session could not do (no API key in the environment) and which would cost the clap animation
|
||||
outright: the raises are lifted from the OLD roll, and a new composition has nothing to lift from.
|
||||
|
||||
## THE SHELL — CAPACITOR, HAPTICS, PORTRAIT, SWIPE-BACK (2026-08-20)
|
||||
|
||||
The first three bullets of LATER item 2 ("What the shell buys GAMEPLAY-wise") are built. **The web
|
||||
@@ -3632,42 +3705,52 @@ Nothing here is broken; these are known gaps and unverified risks.
|
||||
**If the game ever stutters, `RES` back to 3 is the first move** — not reducing zombie counts.
|
||||
2. **The game-20 horde fix is unplayed.** The crowd-scaled swish (1.00 s at one body -> 0.22 s at
|
||||
fourteen) is measured but not yet felt. If it now trivialises hordes, `meleeCoolMin` is the knob.
|
||||
3. **`p_idle1`/`p_idle2` and most `o_*` frames are 2x-upscaled, not re-sliced.** They are on the right
|
||||
3. **The crowd sheet is stretched, not fitted.** It is drawn to fill whatever stand band the
|
||||
viewport leaves, so on portrait a spectator is squeezed to 0.77x his width and in landscape he is
|
||||
1.36x wide by 0.54x tall — anamorphic, differently per orientation, and nearest-neighbour on top
|
||||
(one source column in four is simply dropped on a 3x phone, which is the hash on the faces). Fixes
|
||||
are (a) author the sheet at the aspect portrait actually uses, or (b) tile it at its own aspect the
|
||||
way `render()` already tiles the ad strip. **Deliberately not done 2026-08-21**: at ~8 logical px
|
||||
per person nobody sees a 25% narrow spectator, and the same effort spent on chroma and silhouette
|
||||
was worth more. Revisit only if the crowd is ever re-rolled at a chosen size.
|
||||
4. **`p_idle1`/`p_idle2` and most `o_*` frames are 2x-upscaled, not re-sliced.** They are on the right
|
||||
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 — still, for two of the six.** Designs 1 (GROUNDSKEEPER), 4 (HEAD
|
||||
5. **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.
|
||||
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
|
||||
6. ~~**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
|
||||
7. **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.
|
||||
**RETIRED IN THE APP, 2026-08-20** — `Info.plist` lists one orientation, so the shell never rotates
|
||||
and landscape is officially not-a-surface there. The WEB build can still be turned sideways (iOS
|
||||
Safari has no orientation lock), so this stays open, at a lower stake: it is now a browser-only
|
||||
cosmetic gap rather than something a store reviewer will see.
|
||||
7. **`drawCard` (the chapter/set cards) still cuts in** with no entrance, while modals swoosh
|
||||
8. **`drawCard` (the chapter/set cards) still cuts in** with no entrance, while modals swoosh
|
||||
(`TUNE.modalIn`). Offered and not yet requested.
|
||||
8. **A chapter can be skipped.** `G.bossDue` takes the max, so going a long stretch without ever
|
||||
9. **A chapter can be skipped.** `G.bossDue` takes the max, so going a long stretch without ever
|
||||
clearing the court means you face the current chapter, not a backlog. Deliberate; noted in case it
|
||||
ever reads as a bug.
|
||||
9. **The runner's four frames were fixed BY HAND and must be regenerated.** `z_f1..4` are currently
|
||||
10. **The runner's four frames were fixed BY HAND and must be regenerated.** `z_f1..4` are currently
|
||||
hip-registered (padded to one width with the hip on the centre line) and frames 3 and 4 are
|
||||
mirrored copies, because all four were drawn leaning the same way and lifting the same leg. That
|
||||
is exactly the post-processing Oliver ruled out — it shipped before he stated the rule. The batch
|
||||
to write asks for a four-frame front-on sprint whose stride and lean ALTERNATE, and the numbers to
|
||||
check afterwards are the per-frame hip centroid (it was 13 px of lateral jump at 12 frames a
|
||||
second) and which leg leads.
|
||||
10. **Step 6 stays PARKED** by Oliver's decision: the crowd turning into zombies (spawning from the
|
||||
11. **Step 6 stays PARKED** by Oliver's decision, and the 2026-08-21 art pass did NOT spend it — the
|
||||
three already-turned spectators in the seated sheet are a hint, not the mechanic. Step 6 is: the
|
||||
crowd turning into zombies (spawning from the
|
||||
stands — attacks from behind, high spectacle, `x_crowd` sheets already exist) and the ball-boy zombie
|
||||
that catches your shots. Recorded so the ideas are not lost, not as a to-do.
|
||||
11. **`ump_no_deal` does not exist.** Oliver asked for its removal; `ump_no_appeal` was removed as the
|
||||
12. **`ump_no_deal` does not exist.** Oliver asked for its removal; `ump_no_appeal` was removed as the
|
||||
closest match. Worth one confirmation that no other line is still unwanted.
|
||||
12. **Four sounds are missing and need RESEARCH first** (find/choose the source before implementing —
|
||||
13. **Four sounds are missing and need RESEARCH first** (find/choose the source before implementing —
|
||||
every existing sfx is either synthesised in `audio.js` or a wav in `assets/audio/`):
|
||||
- the **ball machine** while it moves — a squeaking wheel/castor, so the furniture announces
|
||||
itself. It already telegraphs its shot (`machWind` 0.9 s); this is the *approach* cue.
|
||||
|
||||
Reference in new issue
Block a user