The crowd is drawn again, in detail — and the stand now moves one spectator at a time
Oliver on the first pass: "the new lighter areas read as rather sloppy artwork. please regenerate the crowd art and make it more detailed. also the animation also looks pretty artificial. the flashes from the fotographs are too rare and the whole thing somehow does not translate well to the other courts. also when the opponent makes a point there is an aww-sample (which has an applaus trail) so the crowd should still clap but more subtle." The paint pass is deleted — recolouring blobs of an old drawing is not drawing, and the reason it looked necessary was a wrong conclusion: the roll scripts read OPENROUTER_API_KEY from the repo's .env, so the art could be rolled all along. _crowdroll.py rolls the crowd itself: individually drawn spectators with hats, sunglasses, drinks, cameras, flags and banners, empty seats, stairways, and three zombies sitting in the middle of it behaving like furniture. ONE roll now serves all three courts through a per-channel histogram match onto each court's old palette, so anything the art gains shows up on clay and at night too. _crowdpass.py keeps only what was empty stand in the seated roll and became a person in the clap roll — the raised arms, 1.2% of the sheet — so the clap frame cannot drift by construction, and writes a box around each pair of arms into src/crowd.js. render.js raises ONE SPECTATOR AT A TIME out of that table, each on their own period: the 16-column strip swap moved a seventeenth of the crowd as a block, which is what read as machinery. crowd.pop is now a head count, so the same code covers a dozen people and the whole stand, and past 0.75 the wave travels person by person. aww() sets a small crowd.pop of its own — the sample is a groan that settles into applause, and the stand has to match what you can hear. flashEvery 5.0 -> 1.9, because at a five-second mean over a stand this wide nobody ever saw one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
74afd5988c
commit
5f53a686c8
14 files changed
+499
-398
No files matched your search
+73
-22
@@ -20,9 +20,10 @@ 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-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
|
||||
Last updated 2026-08-21 (THE CROWD: rolled again, in far more detail, once for all three courts;
|
||||
the clap frame is now the seated sheet plus the raised arms and nothing else, and the stand animates
|
||||
ONE SPECTATOR AT A TIME off `src/crowd.js`. The wave, the idle fidget, paparazzi that scale with the
|
||||
moment, and an aww that still claps. See "THE CROWD, ROLLED AGAIN". 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
|
||||
from the front, so his pick dyes the same garment yours does; the setup screen shows that front too.
|
||||
@@ -347,19 +348,25 @@ 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).
|
||||
- **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.
|
||||
- **THE CLAP FRAME IS THE SEATED FRAME PLUS THE ARMS, and that is meant literally.** `x_crowd2` is
|
||||
built FROM `x_crowd` by `assets/_crowdpass.py`, which copies only the pixels that were STAND in
|
||||
the seated roll and became PERSON in the clap roll — 93 pairs of raised arms and hands, 1.2% of
|
||||
the sheet. Not the raiser's face, not his shirt, not his seat: those are the seated sheet's own
|
||||
pixels and cannot drift. Each pair of arms has a box in **`src/crowd.js`** (generated), and
|
||||
render.js raises **one spectator at a time** out of that table. It was NOT true before 2026-08-21:
|
||||
the clap sheet was a separate roll that differed from the seated one in 13-16% of its pixels,
|
||||
everywhere, so the animation 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, and never edit `src/crowd.js`.** Re-roll with `_crowdroll.py`, then re-run `_crowdpass.py`,
|
||||
which rebuilds the pair, the three court grades and the table together. 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".
|
||||
- **The three courts are ONE crowd, graded.** `x_crowd`, `x_crowd_c` and `x_crowd_h` are the same
|
||||
roll under three per-channel histogram matches onto the courts' old palettes, so anything the
|
||||
crowd art gains shows up on all three. They were three separate rolls until 2026-08-21, and the
|
||||
cost of that was exactly what Oliver reported of the first pass: "the whole thing somehow does not
|
||||
translate well to the other courts, there the difference is barely visible."
|
||||
`assets/_sheet_crowd1.png` is the pre-brightening original of the FIRST crowd, kept for history.
|
||||
|
||||
## Settled feel decisions — do not regress silently
|
||||
- **Far bodies draw off a flattened scale curve; the camera holds still.** `TUNE.farK` 0.25
|
||||
@@ -2307,7 +2314,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. |
|
||||
| The CROWD — the art itself | `_crowdroll.py` (two rolls: the seated master, then the same picture with arms up) -> `_crowdpass.py` (throws the drift away, keeps the arms, grades the three courts, writes `src/crowd.js`). Both frames of all three courts come out of one run; there is no such thing as editing half of it. |
|
||||
|
||||
**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
|
||||
@@ -2548,6 +2555,46 @@ 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 CROWD, ROLLED AGAIN — AND ONE SPECTATOR AT A TIME (2026-08-21, same day)
|
||||
|
||||
Oliver on the pass below: *"the still look a little rough.. and now the new lighter areas read as
|
||||
rather sloppy artwork. please regenerate the crowd art and make it more detailed. also the animation
|
||||
also looks pretty artificial. the flashes from the fotographs are too rare and the the whole thing
|
||||
somehow does not translate well to the other courts. also when the opponent makes a point there is
|
||||
an aww-sample (which has an applaus trail) so the crowd should still clap but more subtle."*
|
||||
|
||||
All five, and the first one retracts a claim the section below made: **there IS a key** — the roll
|
||||
scripts read `OPENROUTER_API_KEY` from the repo's own `.env`, not from the environment, so "no API
|
||||
key in the environment" was a wrong conclusion, and painting detail onto an old sheet with numpy was
|
||||
never the right tool. It is deleted. The art is rolled.
|
||||
|
||||
- **THE ART IS A NEW ROLL** (`assets/_crowdroll.py`, two calls). Individually drawn spectators —
|
||||
hair, hats, sunglasses, drinks, cameras, small flags, plain banners, empty seats, stairways, a few
|
||||
already on their feet, and three zombies sitting in the middle of the crowd behaving like
|
||||
furniture. The prompt states the art resolution in FIGURE HEIGHTS ("about 90 pixels of height
|
||||
instead of the reference's 40"), which is the one lesson the sprite rolls paid for: the generator
|
||||
picks its own canvas, so a pixel count means nothing to it and a proportion does. The old sheet
|
||||
goes in as image 1 for LAYOUT ONLY — same camera, same tiers, same railing.
|
||||
- **ONE ROLL, THREE COURTS.** Each court is a per-channel histogram match onto its own old sheet, so
|
||||
clay is still terracotta and the night court is still night, but the crowd underneath is one
|
||||
drawing. The same LUT grades both frames of a pair, which is what keeps them a pair.
|
||||
- **THE ANIMATION IS PER SPECTATOR.** The clap roll drifts by 29% of its pixels; `_crowdpass.py`
|
||||
keeps only the blobs that were empty stand and became a person — the raised arms, 1.2% of the
|
||||
sheet — and writes a box around each into `src/crowd.js`. render.js draws ONE BOX AT A TIME, so a
|
||||
single spectator's arms go up. The 16-column strip swap it replaced moved a seventeenth of the
|
||||
crowd as a block, which is what read as machinery ("the animation also looks pretty artificial").
|
||||
Boxes that overlap are merged into one entry: two neighbours who always rise together are one
|
||||
person as far as the animation is concerned.
|
||||
- **THE SIZE OF THE MOMENT IS NOW A HEAD COUNT.** `crowd.pop` decides HOW MANY of the 93 join in
|
||||
(each on their own period, staggered by a stable per-person draw), so the same code does the whole
|
||||
range: a groan is a dozen people, a game is half the stand, and past 0.75 it becomes the wave —
|
||||
which is now a front travelling person by person rather than column by column.
|
||||
- **THE AWW CLAPS.** `tennis-aww` is a groan that settles into scattered applause, so a stand that
|
||||
sits through it contradicts the sound. `aww()` now sets a small `crowd.pop` itself — in audio.js,
|
||||
because it is the SAMPLE that makes this true and every current and future caller should get it.
|
||||
- **MORE PHOTOGRAPHERS.** `TUNE.flashEvery` 5.0 -> 1.9. At a five-second mean, jittered, spread over
|
||||
a stand this wide, Oliver never saw one.
|
||||
|
||||
## 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
|
||||
@@ -2606,10 +2653,13 @@ held at the loudest while an applause is live** — a won rally landing inside a
|
||||
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.
|
||||
**SUPERSEDED THE SAME DAY — see the section above.** Two things in this one were wrong. The paint
|
||||
pass ("55 shirts and 44 heads of hair...") is gone: Oliver, *"the new lighter areas read as rather
|
||||
sloppy artwork"*, and he is right — recolouring blobs of somebody else's drawing is not drawing. And
|
||||
"no API key in the environment" was a wrong conclusion that made the paint pass look necessary: the
|
||||
key is in the repo's `.env`, which is where the roll scripts have always read it from. What survives
|
||||
from this session is the diagnosis (the frame pair, not the animation), `crowd.pop`, the paparazzi
|
||||
scaling and the fidget.
|
||||
|
||||
## THE SHELL — CAPACITOR, HAPTICS, PORTRAIT, SWIPE-BACK (2026-08-20)
|
||||
|
||||
@@ -3712,7 +3762,8 @@ Nothing here is broken; these are known gaps and unverified risks.
|
||||
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.
|
||||
was worth more, and the 2026-08-21 re-roll went out at the same 1408x768 rather than spending
|
||||
the session on a fit nobody can see. Revisit only alongside a re-roll 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
|
||||
|
||||
Reference in new issue
Block a user