The tour rides the wire: your friend plays in the body they picked
A tour is which body you play in, so it belongs in the look message next to the kit, the skin and the name. Without it both phones drew the friend in whatever tour THIS phone had picked, and a men's/women's pairing showed each player someone their friend was not using. `oTour()` is the one accessor — yours everywhere, theirs in netplay — and it feeds the three things that must agree about a body: the sprite suffix, how the art takes a colour (her whites wash, his blue dyes) and the palm the idle ball bounces under. An older build sends no tour and falls back to ours, which is what it did before. The palm knobs were re-checked against the new front sheets rather than assumed — the poses were copied, so the ball still lands under the drawn hand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5f9170b7d7
commit
c5eb6846e3
5 files changed
+44
-11
No files matched your search
+13
@@ -2577,6 +2577,19 @@ of the `o_*` originals they were composed from.
|
||||
The one thing the first roll got wrong: it drew a TENNIS BALL into both bouncing frames. The game
|
||||
draws the ball; a drawn one is a blob stuck to his hand. The prompt says so now.
|
||||
|
||||
**THE TOUR RIDES THE WIRE TOO** (Oliver, same session: *"ja, das geschlecht sollte auch über die
|
||||
leitung gehen"*). A tour is which BODY you play in, so it is part of the look and it goes in the same
|
||||
message (`u`, alongside `k`/`s`/`n`). Before that, both phones drew the friend in whatever tour THIS
|
||||
phone picked, so a men's/women's pairing showed each player a body their friend was not using.
|
||||
`oTour()` in render.js is the one accessor — yours everywhere, theirs in netplay — and it feeds the
|
||||
three places that must agree: the sprite suffix (`SK`), how the art takes a colour (`PS`: her whites
|
||||
wash, his blue dyes) and the palm the idle ball bounces under (`BOUNCE_IDLE.oDx/oApex`, measured per
|
||||
body). An older build sends no tour and falls back to ours, which is exactly what it used to do.
|
||||
|
||||
Those palm knobs were re-checked against the new art rather than assumed: the ball marker still
|
||||
lands under the drawn palm on `n_bounce` and `n_bounce_f` at the values measured off `o_bounce`,
|
||||
because the poses were copied. No retune.
|
||||
|
||||
**And the SETUP SCREEN uses the front too** (Oliver, same session: *"das heisst aber auch, dass wir
|
||||
in der farb- und charakterauswahl den frontfacing nehmen können"*). Picking a face off the back of a
|
||||
head was the best the art allowed; `drawLook` draws `n_idle1` now — same kit, same palette, same
|
||||
|
||||
+1
-1
@@ -659,6 +659,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="src/main.js?v=294"></script>
|
||||
<script type="module" src="src/main.js?v=295"></script>
|
||||
</body>
|
||||
</html>
|
||||
+10
-3
@@ -17,8 +17,8 @@
|
||||
//
|
||||
// ponytail: no reconnect-and-resume. A dropped radio ends the match with a card; the recorded
|
||||
// plan calls reconnection "the bulk of the real work" and a prototype must not start there.
|
||||
import { COURT, UI, KITS, SKINS } from './config.js';
|
||||
import { G, P, O, ball, score, setupPoint, selectCourt, court, kit, skin } from './state.js';
|
||||
import { COURT, UI, KITS, SKINS, TOURS } from './config.js';
|
||||
import { G, P, O, ball, score, setupPoint, selectCourt, court, kit, skin, tour } from './state.js';
|
||||
import { pointTo, fault, tossBall } from './gameplay.js';
|
||||
import { startMatch, matchReady, resolveInterlude, netMatchWon } from './beats.js'; // runtime-only cycle: beats sends verdicts back through this module
|
||||
import { zombies } from './zombies.js';
|
||||
@@ -129,7 +129,11 @@ function netStart(side, death, courtId) {
|
||||
// tell them how we look AND WHO WE ARE, the moment there is somebody to tell. Sent once, on
|
||||
// pairing: neither phone can change its pick mid-match, because the setup screen is only
|
||||
// reachable between runs.
|
||||
send({ t: 'look', k: kit.id, s: skin.id, n: savedName() });
|
||||
// `u` is the TOUR — which body they play in, not just what colour it wears. It rides the same
|
||||
// message because it is the same fact: what the friend across the net LOOKS like. Without it both
|
||||
// phones drew the opponent in whatever tour THIS phone picked, so a men's/women's pairing showed
|
||||
// each player a body their friend was not using.
|
||||
send({ t: 'look', k: kit.id, s: skin.id, n: savedName(), u: tour.id });
|
||||
G.modal = null; G.modalQ.length = 0; // the waiting card has done its job
|
||||
// THE HOST'S COURT RIDES THE ROOM, exactly as the death-match pick does — two phones playing the
|
||||
// same rally on two different surfaces disagree about the bounce as well as the picture, and the
|
||||
@@ -202,6 +206,9 @@ export function netMsg(m) {
|
||||
else if (m.t === 'look') G.net.look = {
|
||||
kit: KITS.find(k => k.id === m.k) || KITS[0],
|
||||
skin: SKINS.find(s => s.id === m.s) || SKINS[0],
|
||||
// a build that predates the tour field sends none, and the fallback is what it used to do:
|
||||
// draw them in OUR tour. Same rule as the kit — an unknown id resolves to the drawn default.
|
||||
tour: TOURS.find(t => t.id === m.u) || tour,
|
||||
name: tidyName(m.n || '').slice(0, 8) || 'FRIEND',
|
||||
};
|
||||
}
|
||||
|
||||
+14
-5
@@ -234,7 +234,7 @@ function farB(z) {
|
||||
// Asset cache-buster — bump with main.js?v=N (see the img.src note below). EXPORTED because the
|
||||
// menu screens hang <img> tags on the same files, and two copies of this number is one copy too
|
||||
// many: the one that does not get bumped is the one that serves a cached 404.
|
||||
export const SPRV = 141;
|
||||
export const SPRV = 142;
|
||||
// The boot bar in main.js watches this. Nothing here CHANGES when things load — every sprite was
|
||||
// already requested at import — it just makes the wait visible and holds the menu back until the
|
||||
// last sheet has landed. See the onerror note below for the other half of "sometimes missing".
|
||||
@@ -1526,10 +1526,19 @@ function drawPiles(near) { // split at the net like zombies so height occludes c
|
||||
// one whose PNG has not loaded yet — falls straight through to the men's frame. That fallback is
|
||||
// deliberate: a cached 404 on one female frame degrades to a man for a frame, it does not blink the
|
||||
// body out of existence (the same rule drawZombie's missing-frame guard is built on).
|
||||
// THE TOUR THE BODY ACROSS THE NET IS PLAYING. Yours everywhere except netplay, where it is the
|
||||
// one their phone sent — a tour is which BODY you play in, and the friend picked that on their own
|
||||
// setup screen. It reaches three things and they must not disagree: the sprite suffix (SK), how the
|
||||
// art takes a colour (PS: her whites wash, his blue dyes) and the palm the idle ball bounces under
|
||||
// (BOUNCE_IDLE.oDx/oApex, which are measured per body).
|
||||
const oTour = () => (G.net && G.net.look && G.net.look.tour) || tour;
|
||||
|
||||
const SK = n => {
|
||||
// the bench's living slots pick the suffix directly (G.bench.char 'o'/'of'/'p'/'pf'), so all four
|
||||
// tennis bodies can be reviewed without touching the picked tour
|
||||
const sfx = G.bench && G.bench.char != null ? (G.bench.char.endsWith('f') ? '_f' : '') : tour.sfx;
|
||||
const sfx = G.bench && G.bench.char != null ? (G.bench.char.endsWith('f') ? '_f' : '')
|
||||
: n[0] === 'n' && n[1] === '_' ? (oTour().sfx || '') // the friend, in THEIR tour
|
||||
: tour.sfx;
|
||||
return (sfx && SPR[n + sfx]) ? n + sfx : n;
|
||||
};
|
||||
|
||||
@@ -1636,7 +1645,7 @@ function PS(name) {
|
||||
// women's it sits at the same hue as her skin and her hair, so a dark pick painted her arms, her
|
||||
// legs and her ponytail. Blue-over-white is hue-isolated by construction — see TINT in config.
|
||||
if (n[0] === 'n' && n[1] === '_' && G.net && G.net.look)
|
||||
return tinted(n, img, G.net.look.kit, G.net.look.skin, tour.kit, TINT.kit);
|
||||
return tinted(n, img, G.net.look.kit, G.net.look.skin, oTour().kit, TINT.kit);
|
||||
return img;
|
||||
}
|
||||
|
||||
@@ -2445,8 +2454,8 @@ export function render() {
|
||||
// the racket side. One `* sv.facing` fixes it for both players and both tours; it was wrong in
|
||||
// the men's world too, it just took a second body to notice.
|
||||
const mine = sv.side === 'P';
|
||||
const bx = sv.x + sv.facing * (mine ? -bounceDx() : (tour.oDx ?? BOUNCE_IDLE.oDx));
|
||||
const by = Math.abs(Math.sin(G.idleT * BOUNCE_IDLE.tempo)) * (mine ? bounceApex() : (tour.oApex ?? BOUNCE_IDLE.oApex));
|
||||
const bx = sv.x + sv.facing * (mine ? -bounceDx() : (oTour().oDx ?? BOUNCE_IDLE.oDx));
|
||||
const by = Math.abs(Math.sin(G.idleT * BOUNCE_IDLE.tempo)) * (mine ? bounceApex() : (oTour().oApex ?? BOUNCE_IDLE.oApex));
|
||||
drawBall(bx, by, sv.z);
|
||||
}
|
||||
// hot-ball ghost streak: a fast rally ball drags three fading ghosts behind it. It gated on
|
||||
|
||||
@@ -1840,13 +1840,17 @@ test('netplay: your look goes over the wire on pairing, and theirs comes back',
|
||||
assert.ok(look, 'pairing announces this phone\'s look');
|
||||
assert.equal(look.k, 'green'); assert.equal(look.s, 'deep');
|
||||
|
||||
netMsg({ t: 'look', k: 'red', s: 'pale' });
|
||||
assert.equal(look.u, state.tour.id, "...and WHICH BODY it plays in — the tour is part of the look");
|
||||
|
||||
netMsg({ t: 'look', k: 'red', s: 'pale', u: 'women' });
|
||||
assert.equal(G.net.look.kit.id, 'red', 'their kit is what the opponent is drawn in');
|
||||
assert.equal(G.net.look.skin.id, 'pale');
|
||||
assert.equal(G.net.look.tour.id, 'women', 'and their tour, whatever THIS phone picked');
|
||||
// an older build, or a colour this one has never heard of, must not blank the opponent out
|
||||
netMsg({ t: 'look', k: 'chartreuse', s: undefined });
|
||||
netMsg({ t: 'look', k: 'chartreuse', s: undefined, u: 'mixed doubles' });
|
||||
assert.equal(G.net.look.kit.id, cfg.KITS[0].id, 'an unknown colour falls back to as-drawn');
|
||||
assert.equal(G.net.look.skin.id, cfg.SKINS[0].id);
|
||||
assert.equal(G.net.look.tour.id, state.tour.id, 'and an unknown tour falls back to OUR tour');
|
||||
state.selectKit(cfg.KITS[0].id); state.selectSkin(cfg.SKINS[0].id); // leave the table as found
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user