The cup climbs: easy to hard, deterministic — the shuffle retires with its screen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
dbfd085493
commit
a5a681f9f2
5 files changed
+47
-57
No files matched your search
+11
@@ -491,6 +491,17 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu
|
|||||||
`RETURNED`, `DOUBLE KILL`) is a footnote to something you already watched happen. Sharing the loud
|
`RETURNED`, `DOUBLE KILL`) is a footnote to something you already watched happen. Sharing the loud
|
||||||
FACE with the milestone cards is right; sharing their SIZE made every footnote read as a milestone.
|
FACE with the milestone cards is right; sharing their SIZE made every footnote read as a milestone.
|
||||||
|
|
||||||
|
## Session log: THE CUP CLIMBS — the shuffle retires too (2026-08-29, same day, STAGING ONLY)
|
||||||
|
|
||||||
|
Oliver, straight after the draw screen went: "warum sollten wir noch mischen? wäre es nicht gut
|
||||||
|
von leicht nach schwer zu spielen?" — right again. Without the screen the shuffle was invisible
|
||||||
|
novelty, and a bad roll paired the strongest challenger with the one-game round one, INVERTING
|
||||||
|
the curve the whole game is tuned around ("starts easy, gets harder" is literally test-pinned).
|
||||||
|
`cupRoundsFor` is deterministic now: one band position per round, ascending, the final the top —
|
||||||
|
and the setup's league panel is thereby an exact preview of the tournament order. `cupDraw`
|
||||||
|
collapsed to a read; `cup.draw`/`cup.played` are gone (startMatch keeps the dead `draw` parameter
|
||||||
|
for call-site arity only). The old draw test became the climb-order pin.
|
||||||
|
|
||||||
## Session log: THE DRAW SCREEN RETIRES (2026-08-29, same day, STAGING ONLY)
|
## Session log: THE DRAW SCREEN RETIRES (2026-08-29, same day, STAGING ONLY)
|
||||||
|
|
||||||
Oliver: "the draw ist doch jetzt eigentlich obsolet, oder?" — yes. The screen was built for the
|
Oliver: "the draw ist doch jetzt eigentlich obsolet, oder?" — yes. The screen was built for the
|
||||||
|
|||||||
+1
-1
@@ -854,6 +854,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="src/main.js?v=329"></script>
|
<script type="module" src="src/main.js?v=330"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
+8
-22
@@ -354,13 +354,12 @@ export function watchdog(dt) {
|
|||||||
// the match mode ends. Regicide is inherited for free: smashing the ball into the body still wounds
|
// the match mode ends. Regicide is inherited for free: smashing the ball into the body still wounds
|
||||||
// it, and killing it outright wins the game early (killZombie fires resolveInterlude('P'), which
|
// it, and killing it outright wins the game early (killZombie fires resolveInterlude('P'), which
|
||||||
// lands in matchGameWon via the empty-court check).
|
// lands in matchGameWon via the empty-court check).
|
||||||
// `draw` is an optional pre-settled list of rungs, one per cup round — null (the normal case
|
|
||||||
// since the bracket screen was deleted, 2026-08-29) means "roll each round as it comes". The
|
|
||||||
// parameter stays: the suite uses it to pin the validation below.
|
|
||||||
// `train` (2026-08-28) is a RUNG NUMBER and turns the run into the TRAINING COURT: that one
|
// `train` (2026-08-28) is a RUNG NUMBER and turns the run into the TRAINING COURT: that one
|
||||||
// opponent, one game at a time, nothing banked. The ladder is parked — d.opp never advances, a
|
// opponent, one game at a time, nothing banked. The ladder is parked — d.opp never advances, a
|
||||||
// felled body walks straight back on, and bleeding out costs a modal instead of the run. All of
|
// felled body walks straight back on, and bleeding out costs a modal instead of the run. All of
|
||||||
// it rides the ordinary match machinery behind the d.train flag; see matchGameWon / matchPoint.
|
// it rides the ordinary match machinery behind the d.train flag; see matchGameWon / matchPoint.
|
||||||
|
// `draw` is DEAD weight kept only for call-site arity — the cup runs easy to hard now
|
||||||
|
// (cupRoundsFor is deterministic, 2026-08-29) and nothing reads it.
|
||||||
export function startMatch(cup = false, draw = null, train = 0) {
|
export function startMatch(cup = false, draw = null, train = 0) {
|
||||||
if (G.interlude || G.horde || G.bossFight || G.drill || G.match || G.bench || G.net) return false;
|
if (G.interlude || G.horde || G.bossFight || G.drill || G.match || G.bench || G.net) return false;
|
||||||
// THE LEAGUES: the profile's league is the world (band, pace tier, court — see PROJECT.md).
|
// THE LEAGUES: the profile's league is the world (band, pace tier, court — see PROJECT.md).
|
||||||
@@ -376,7 +375,7 @@ export function startMatch(cup = false, draw = null, train = 0) {
|
|||||||
// on (a rung DRAWN from the round's band — cupDraw sets d.opp, so every ladder dial downstream
|
// on (a rung DRAWN from the round's band — cupDraw sets d.opp, so every ladder dial downstream
|
||||||
// scales off the drawn rung untouched), how deep the body's pool is (matchOpponent multiplies
|
// scales off the drawn rung untouched), how deep the body's pool is (matchOpponent multiplies
|
||||||
// by the round's `need`), and where a won game goes (cupGameWon). See config.cupRoundsFor.
|
// by the round's `need`), and where a won game goes (cupGameWon). See config.cupRoundsFor.
|
||||||
if (cup) { G.match.cup = { round: 0, g: 0, draw: draw || null, rounds: cupRoundsFor(G.league) }; cupDraw(); }
|
if (cup) { G.match.cup = { round: 0, g: 0, rounds: cupRoundsFor(G.league) }; cupDraw(); }
|
||||||
G.matchFall = null;
|
G.matchFall = null;
|
||||||
G.hp = G.hpMax = TUNE.matchPlayerHp; // a real match is longer than four mistakes — the gore
|
G.hp = G.hpMax = TUNE.matchPlayerHp; // a real match is longer than four mistakes — the gore
|
||||||
// mode's 4 was "too weak" here, felt not calculated
|
// mode's 4 was "too weak" here, felt not calculated
|
||||||
@@ -412,25 +411,12 @@ export function startMatch(cup = false, draw = null, train = 0) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The round's rung, drawn from its band. d.opp carries it so matchFoe, the ladder speed dials and
|
// The round's opponent. No draw and no roll any more: the rounds ARE the band, ascending, so
|
||||||
// the felled arithmetic all read the drawn rung with no cup-awareness of their own.
|
// this simply reads the round's one position off (cupRoundsFor carries the order). d.opp carries
|
||||||
// A PASSED DRAW IS THE DRAW THAT IS PLAYED (it once arrived from the bracket screen; the suite
|
// it so matchFoe, the ladder speed dials and the felled arithmetic stay cup-unaware.
|
||||||
// still passes one) — this reads it off rather than rolling again.
|
|
||||||
// Guarded on the band all the same: a saved or hand-passed draw that names a rung this round does
|
|
||||||
// not contain falls back to an honest roll rather than dealing the champion in round one.
|
|
||||||
function cupDraw() {
|
function cupDraw() {
|
||||||
const cup = G.match.cup, R = cup.rounds[cup.round];
|
const cup = G.match.cup;
|
||||||
const drawn = cup.draw && cup.draw[cup.round];
|
G.match.opp = cup.rung = cup.rounds[cup.round].band[0];
|
||||||
// WITHOUT REPLACEMENT: a league band is 3-4 rungs and every non-final round carries the whole
|
|
||||||
// sub-band, so the run remembers who has already fallen (cup.played) and never deals a body
|
|
||||||
// twice — a repeat would read as the bracket screen lying. The final's band is one rung and
|
|
||||||
// sails through untouched.
|
|
||||||
cup.played = cup.played || [];
|
|
||||||
const pool = R.band.filter(r => !cup.played.includes(r));
|
|
||||||
cup.rung = R.band.includes(drawn) && !cup.played.includes(drawn) ? drawn
|
|
||||||
: (pool.length ? pool : R.band)[Math.floor(Math.random() * (pool.length ? pool : R.band).length)];
|
|
||||||
cup.played.push(cup.rung);
|
|
||||||
G.match.opp = cup.rung;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Every point starts like a real one: the server behind his line (setupPoint's tennis branch) and
|
// Every point starts like a real one: the server behind his line (setupPoint's tennis branch) and
|
||||||
|
|||||||
+14
-14
@@ -917,22 +917,22 @@ export const leagueOf = lg => LEAGUES[Math.min(LEAGUES.length, Math.max(1, lg |
|
|||||||
export const leagueOfRung = n => LEAGUES.find(l => n <= l.band[1]) || LEAGUES[LEAGUES.length - 1];
|
export const leagueOfRung = n => LEAGUES.find(l => n <= l.band[1]) || LEAGUES[LEAGUES.length - 1];
|
||||||
|
|
||||||
// The tournament, PER LEAGUE. Same shape CUP_ROUNDS always had (name/band/need) — round one is a
|
// The tournament, PER LEAGUE. Same shape CUP_ROUNDS always had (name/band/need) — round one is a
|
||||||
// single game so a run opens fast, and the drawn body's pool still scales with `need`
|
// single game so a run opens fast, and the body's pool still scales with `need` (matchOpponent).
|
||||||
// (matchOpponent). The rounds ride ON the cup object (beats.startMatch stamps them), so every
|
// The rounds ride ON the cup object (beats.startMatch stamps them).
|
||||||
// consumer reads cup.rounds and the full-ladder constant is gone.
|
// EASY TO HARD, DETERMINISTIC (2026-08-29, Oliver: "warum sollten wir noch mischen?"): each round
|
||||||
// A league band is 3-4 rungs, so the draw is WITHOUT REPLACEMENT: every non-final round carries
|
// is exactly one position of the band, ascending, the final the band's top. The shuffle was the
|
||||||
// the whole sub-band and the run deals each body at most once (cup.played / buildBracket's used
|
// deleted draw screen's ceremony — without the screen it only ever risked pairing the strongest
|
||||||
// set), the final is always the band's top rung — "only the finale is settled", still. A 4-rung
|
// challenger with the shortest round, inverting the curve the whole game is tuned around. The
|
||||||
// league is four rounds (a shuffle of three challengers), a 3-rung league is three.
|
// setup's league panel is now, incidentally, an exact preview of the tournament order.
|
||||||
export const cupRoundsFor = lg => {
|
export const cupRoundsFor = lg => {
|
||||||
const [lo, hi] = leagueOf(lg).band;
|
const [lo, hi] = leagueOf(lg).band;
|
||||||
const open = [];
|
const names = hi - lo >= 3 ? ['ROUND ONE', 'QUARTER-FINAL', 'SEMI-FINAL']
|
||||||
for (let r = lo; r < hi; r++) open.push(r);
|
: ['ROUND ONE', 'SEMI-FINAL'];
|
||||||
const names = open.length >= 3 ? ['ROUND ONE', 'QUARTER-FINAL', 'SEMI-FINAL']
|
const rounds = [];
|
||||||
: ['ROUND ONE', 'SEMI-FINAL'];
|
for (let r = lo; r < hi; r++)
|
||||||
return open.map((_, i) => ({ name: names[i] || 'ROUND ' + (i + 1), band: open.slice(),
|
rounds.push({ name: names[rounds.length] || 'ROUND ' + (rounds.length + 1),
|
||||||
need: i ? 2 : 1 }))
|
band: [r], need: rounds.length ? 2 : 1 });
|
||||||
.concat([{ name: 'THE FINAL', band: [hi], need: 2 }]);
|
return rounds.concat([{ name: 'THE FINAL', band: [hi], need: 2 }]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TOURS = [
|
export const TOURS = [
|
||||||
|
|||||||
@@ -1233,29 +1233,22 @@ test('THE TOURNAMENT: bands draw, bodies deepen, rounds hold their body, the cha
|
|||||||
assert.ok(pr && pr.to === 2, 'the ceremony is parked for the menu to play');
|
assert.ok(pr && pr.to === 2, 'the ceremony is parked for the menu to play');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('THE DRAW: a passed draw is validated per round, honoured, or honestly re-rolled', () => {
|
test('THE CUP CLIMBS: rounds run easy to hard, deterministic, the final the top of the band', () => {
|
||||||
// The screen would be worse than nothing if it dealt a different tournament from the one it just
|
// the shuffle retired with the draw screen (Oliver: "warum sollten wir noch mischen?") — a bad
|
||||||
// showed you settling (main.buildBracket hands its rungs straight to startMatch). The guard is
|
// roll paired the strongest challenger with the shortest round, inverting the tuned curve
|
||||||
// the other half: a rung that is not in the round's band falls back to an honest roll rather
|
for (const l of cfg.LEAGUES) {
|
||||||
// than dealing THE CHAMPION in round one.
|
const R = cfg.cupRoundsFor(l.id);
|
||||||
const { LADDER } = cfg;
|
const order = R.map(r => (assert.equal(r.band.length, 1, 'one settled body per round'), r.band[0]));
|
||||||
const R = cfg.cupRoundsFor(1); // a fresh profile's cup is league one's
|
for (let i = 1; i < order.length; i++)
|
||||||
const drawn = [3, 1, 2, 4]; // a legal shuffle of the sub-band, the final settled
|
assert.ok(order[i] > order[i - 1], `${l.name}: round ${i + 1} is the harder body`);
|
||||||
_startMatch(true, drawn); G.modal = null; G.modalQ.length = 0;
|
assert.equal(order[0], l.band[0], `${l.name} opens on its easiest`);
|
||||||
assert.equal(G.match.opp, drawn[0], 'round one walks on the body the screen landed on');
|
assert.equal(order[order.length - 1], l.band[1], `${l.name} ends on its final`);
|
||||||
assert.deepEqual(G.match.cup.draw, drawn, 'and the rest of the draw rides with the run');
|
}
|
||||||
endMatch();
|
// ...and the run reads that order off exactly
|
||||||
// a nonsense draw is refused per round, not trusted
|
|
||||||
_startMatch(true, [LADDER, LADDER, LADDER, LADDER]); G.modal = null; G.modalQ.length = 0;
|
|
||||||
assert.ok(R[0].band.includes(G.match.opp),
|
|
||||||
'a rung round one cannot contain is rolled instead of dealt');
|
|
||||||
endMatch();
|
|
||||||
// ...and no draw at all is the old behaviour, which the #cup back door still uses
|
|
||||||
_startMatch(true); G.modal = null; G.modalQ.length = 0;
|
_startMatch(true); G.modal = null; G.modalQ.length = 0;
|
||||||
assert.ok(R[0].band.includes(G.match.opp), 'no draw: the round rolls its own');
|
assert.equal(G.match.opp, 1, 'round one walks on the easiest body');
|
||||||
endMatch();
|
endMatch();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cup rallies never strand the latch: no-crossing points, the fall, and the mid-round game', () => {
|
test('cup rallies never strand the latch: no-crossing points, the fall, and the mid-round game', () => {
|
||||||
// Oliver's second lock: in the quarter-final, matchFallWillPlay still divided by 4 while
|
// Oliver's second lock: in the quarter-final, matchFallWillPlay still divided by 4 while
|
||||||
// matchPoint divided by 4*need — it promised a fall on a point that crossed nothing, the
|
// matchPoint divided by 4*need — it promised a fall on a point that crossed nothing, the
|
||||||
|
|||||||
Reference in new issue
Block a user