Training pays a quarter, and the level bar wears its endpoints
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7d39032907
commit
a38c804069
6 files changed
+37
-10
No files matched your search
+12
@@ -491,6 +491,18 @@ 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
|
||||
FACE with the milestone cards is right; sharing their SIZE made every footnote read as a milestone.
|
||||
|
||||
## Session log: training pays a quarter, the bar wears its endpoints (2026-08-29, the batch's two discussions, both go)
|
||||
|
||||
- **THE TRAINING COURT PAYS A QUARTER.** Zero pay made it feel pointless in a meta game (his
|
||||
words) — but full pay would make the no-permadeath, pick-your-body grind the optimal one.
|
||||
award() now quarters (rounds) training amounts instead of muting them; drill, bench and test
|
||||
mode stay muted. The footer says "NO RUN, NO DEATH — REPS PAY ¼ XP", the mode's one-time modal
|
||||
agrees, and training gained real practice value anyway since bodies train at their band's true
|
||||
tempo. The "NOTHING IS BANKED" half is still true: no run, no board, no ladder.
|
||||
- **THE BAR WEARS ITS ENDPOINTS**: "20 ▓▓░░ 21" on the plaque and the card (MAX at the cap) —
|
||||
he read the bare bar as total progress; the two numbers say "fill toward the next level"
|
||||
without a word of explanation. The card keeps its words and the CAREER bar as the long form.
|
||||
|
||||
## Session log: the licence, the peer line, the steward's vest (2026-08-29, Oliver's small-finds batch)
|
||||
|
||||
Three of five items from his post-merge test built (training xp and the level/xp display are
|
||||
|
||||
+11
-4
@@ -364,7 +364,12 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
white-space: nowrap; overflow: hidden; }
|
||||
#plLv { grid-area: lv; align-self: start; font: 400 26px Bangers, cursive; color: var(--acid);
|
||||
letter-spacing: 1.5px; text-decoration: none; text-shadow: 2px 2px 0 rgba(0,0,0,0.5); }
|
||||
#plRow { grid-area: bar; display: block; width: auto; margin-top: 15px; }
|
||||
/* the bar wears its ENDPOINTS — "20 ▓▓░░ 21" says "fill toward the next level" without a
|
||||
word of explanation (Oliver read the bare bar as total progress) */
|
||||
#plRow { grid-area: bar; display: flex; align-items: center; gap: 7px; width: auto;
|
||||
margin-top: 15px; }
|
||||
#plRow u, #cdBar u { font: 700 9px Silkscreen, monospace; color: var(--cream);
|
||||
text-decoration: none; text-shadow: 1px 1px 0 rgba(0,0,0,0.5); }
|
||||
/* NO nowrap here: the longest goal line is ~380px and a nowrap child sets the CARD's
|
||||
min-content past the viewport — that was the phantom horizontal scrollbar Oliver spotted */
|
||||
/* LITERAL gold — there is no --warm in the page palette (UI.warm is a CANVAS colour), and an
|
||||
@@ -377,6 +382,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
#plRow s, #cdBar s, #cdCareer s { flex: 1; height: 6px; background: rgba(255,255,255,0.10);
|
||||
border: 1px solid var(--line); text-decoration: none; display: block; }
|
||||
#cdBar s { height: 8px; }
|
||||
#cdBar { display: flex; align-items: center; gap: 8px; }
|
||||
#cdCareer { display: flex; align-items: center; gap: 8px; }
|
||||
#cdCareer u { font: 700 8px Silkscreen, monospace; color: var(--dim); text-decoration: none;
|
||||
letter-spacing: 1px; }
|
||||
@@ -615,7 +621,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
<img id="plBadge" alt="">
|
||||
<b id="plName"></b>
|
||||
<u id="plLv">LEVEL 1</u>
|
||||
<span id="plRow"><s><i id="plFill"></i></s></span>
|
||||
<span id="plRow"><u id="plFrom">1</u><s><i id="plFill"></i></s><u id="plTo">2</u></span>
|
||||
<i id="plGoal"></i>
|
||||
<em>></em>
|
||||
</button>
|
||||
@@ -740,7 +746,8 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
<img id="cdBadge" alt="">
|
||||
<u id="cdLeague"></u>
|
||||
<b id="cdLevel">LEVEL 1</b>
|
||||
<div id="cdBar"><s><i id="cdFill"></i></s><u id="cdXp"></u></div>
|
||||
<div id="cdBar"><u id="cdFrom">1</u><s><i id="cdFill"></i></s><u id="cdTo">2</u></div>
|
||||
<u id="cdXp"></u>
|
||||
<div id="cdCareer"><u>CAREER</u><s><i id="cdCareerFill"></i></s></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -864,6 +871,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="src/main.js?v=334"></script>
|
||||
<script type="module" src="src/main.js?v=335"></script>
|
||||
</body>
|
||||
</html>
|
||||
+1
-1
@@ -390,7 +390,7 @@ export function startMatch(cup = false, draw = null, train = 0) {
|
||||
G.seen.train = true;
|
||||
showModal({ title: 'THE TRAINING COURT', color: UI.hot,
|
||||
body: ['ONE BODY, ONE GAME AT A TIME',
|
||||
'FELL IT — OR BLEED OUT — AND IT COMES BACK', 'NOTHING IS BANKED HERE'] });
|
||||
'FELL IT — OR BLEED OUT — AND IT COMES BACK', 'NO RUN IS BANKED — REPS PAY QUARTER XP'] });
|
||||
} else if (cup && !G.seen.cup) {
|
||||
G.seen.cup = true;
|
||||
showModal({ title: 'THE TOURNAMENT', color: UI.hot,
|
||||
|
||||
+5
-1
@@ -526,6 +526,8 @@ function buildCard() {
|
||||
el.style.color = lg.color;
|
||||
document.getElementById('cdLevel').textContent =
|
||||
'LEVEL ' + pr.level + ' OF ' + profile.LEVEL_CAP;
|
||||
document.getElementById('cdFrom').textContent = pr.level;
|
||||
document.getElementById('cdTo').textContent = pr.need ? pr.level + 1 : 'MAX';
|
||||
// both bars sweep from empty to their mark (the 0.7s eased transition in the CSS)
|
||||
for (const [id, w] of [['cdFill', Math.round(pr.frac * 100)],
|
||||
['cdCareerFill', Math.min(100, Math.round(profile.prof.xp / profile.xpFor(profile.LEVEL_CAP) * 100))]]) {
|
||||
@@ -635,7 +637,7 @@ function summary() {
|
||||
`<span>${name} · ${t.name.replace(' SINGLES', '')} · ${c.name}${cupMode
|
||||
? ' · LEAGUE ' + lg.id : ''}</span>`
|
||||
// training banks nothing, so there is no mark to beat and the footer must not invent one
|
||||
+ `<span>${m.id === 'train' ? 'NOTHING AT STAKE — JUST REPS'
|
||||
+ `<span>${m.id === 'train' ? 'NO RUN, NO DEATH — REPS PAY ¼ XP'
|
||||
: best ? best + unit + ' TO BEAT' : 'FIRST RUN'}</span>`;
|
||||
}
|
||||
|
||||
@@ -652,6 +654,8 @@ function refreshSplash() {
|
||||
document.getElementById('plBadge').src = `assets/badge_l${lg.id}.png?v=${SPRV}`;
|
||||
document.getElementById('plName').textContent = stats.savedName() || 'YOUR PLAYER';
|
||||
document.getElementById('plLv').textContent = 'LEVEL ' + pr.level + ' · LEAGUE ' + lg.id;
|
||||
document.getElementById('plFrom').textContent = pr.level;
|
||||
document.getElementById('plTo').textContent = pr.need ? pr.level + 1 : 'MAX';
|
||||
document.getElementById('plFill').style.width = Math.round(pr.frac * 100) + '%';
|
||||
document.getElementById('plGoal').textContent = profile.prof.ch
|
||||
? 'CHAMPION OF THE DEAD CLUB — DEFEND THE NAME'
|
||||
|
||||
+6
-2
@@ -72,11 +72,15 @@ export function beginRunXp() { runXp = 0; runFrom = levelAt(prof.xp); }
|
||||
export const runDelta = () => ({ xp: runXp, from: runFrom, to: levelAt(prof.xp) });
|
||||
|
||||
// ONE guard for every no-stakes mode, in the shared function — a call site cannot forget it.
|
||||
const muted = () => G.drill || G.bench || G.testMode || (G.match && G.match.train);
|
||||
// The training court left this list 2026-08-29 (Oliver: zero pay made it feel pointless in a
|
||||
// meta game): it pays A QUARTER now — reps count, the real court stays strictly 4x better, and
|
||||
// the no-permadeath grind can never be the optimal one.
|
||||
const muted = () => G.drill || G.bench || G.testMode;
|
||||
|
||||
export function award(kind, n = 1) {
|
||||
if (muted()) return 0;
|
||||
const amt = (XP[kind] || 0) * n;
|
||||
let amt = (XP[kind] || 0) * n;
|
||||
if (G.match && G.match.train) amt = Math.round(amt / 4);
|
||||
if (!amt) return 0;
|
||||
const before = levelAt(prof.xp);
|
||||
prof.xp += amt; runXp += amt;
|
||||
|
||||
@@ -2181,9 +2181,9 @@ test('PROFILE: award pays through the one point funnel, and mutes every no-stake
|
||||
G[flag] = false;
|
||||
}
|
||||
G.match = { train: true };
|
||||
assert.equal(prof.award('kill'), 0, 'the training court pays nothing');
|
||||
assert.equal(prof.award('point'), prof.XP.point / 4, 'the training court pays a QUARTER — reps count');
|
||||
G.match = null;
|
||||
assert.ok(prof.award('kill') > 0, '...and a real kill pays again');
|
||||
assert.equal(prof.award('point'), prof.XP.point, '...and the real court pays full');
|
||||
});
|
||||
|
||||
test('PROFILE: the run delta survives the death screen and resets with the next run', () => {
|
||||
|
||||
Reference in new issue
Block a user