Bring the swing log to every rally mode, and time the gold ring
Oliver wants to hand over a log of why he missed balls. It existed behind a scoreboard tap but was gated on G.mode === 'tennis', so the Climb, the cup and training (which run as 'splatter') logged nothing. It now writes in every rally mode: hits with shot, tap age and any quality cap, misses with the new let-it-bounce reason, and a WON/LOST line naming why each point went. Tapping the open log copies all 200 entries with a header. Its first catch: the gold ring went gold at a distance, which is too early for the slow balls steps 1-3 brought in. It is now timed off the ball's real path to his reach (against where his run is taking him): gold means touch now. ringTime 0 restores the old ring. v365. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
180288ef8f
commit
d110e12321
8 files changed
+225
-41
No files matched your search
+25
-11
@@ -4,7 +4,7 @@ import { cv, ctx } from './dom.js';
|
||||
import { BENCH, BENCH_STAGES, ZFAMS, famFrames, racketStage, TUNE, UI, EATEN_BURST, EATEN_SUMMARY, EATEN_RESTART, DONE_RESTART, COURT, P_BOUNDS, PLAYER_H, BOY_H, UMP_H, UMP_X, MACH_H, BOSS_ART, ZWOUND, woundStage, BOUNCE_IDLE, SPRITE_H, zombieH , WEAPONS, RACK_ROW, TOSS, HEAD_CROP, TINT, SHOT_DEPTH, matchFoe, cupFixture, cupRoundName } from './config.js';
|
||||
import { P, O, ball, score, crowd, G, court, tour, kit, skin } from './state.js';
|
||||
import { RAISERS, FRAMES } from './crowd.js';
|
||||
import { predictLanding, previewServe, slotId, slotStock, serveApexY, rallyFeel, swayOf } from './gameplay.js';
|
||||
import { predictLanding, previewServe, slotId, slotStock, serveApexY, rallyFeel, swayOf, ringCue } from './gameplay.js';
|
||||
import { drillQueue, drillLegal, drillMult } from './drill.js';
|
||||
import { zombies, gore, piles, parts, pellets, extras, shots, pops } from './zombies.js';
|
||||
import { runDelta, progress, prof, cupBest, hasPromo } from './profile.js';
|
||||
@@ -1070,13 +1070,21 @@ function drawApproachRing() {
|
||||
// core tennis legible. Reported three times before it was believed. The gore mode is left out on
|
||||
// purpose: there is no rally there, the ball comes back off bodies.
|
||||
if (!(G.match || G.mode === 'tennis') || G.phase !== 'rally' || !ball.active || ball.lastHit !== 'O') return;
|
||||
const d = Math.hypot(ball.x - P.x, (ball.z - P.z) * 0.8); // the CONTACT test's own metric
|
||||
if (d > TUNE.matchRingFar || ball.vz < 0) return; // far away, or already going back
|
||||
const p = proj(ball.x, ball.y, ball.z);
|
||||
if (p.s <= 0) return;
|
||||
const inBand = Math.abs(d - TUNE.matchRingSweet) < TUNE.matchRingBand;
|
||||
const rSweet = Math.max(6, p.s * 0.055); // the fixed target ring
|
||||
const r = rSweet * Math.max(1, d / TUNE.matchRingSweet); // the closing ring — they kiss at sweet
|
||||
let inBand, r;
|
||||
if (TUNE.ringTime) { // TIMED (gameplay.ringCue): kiss = touch now
|
||||
const c = ringCue();
|
||||
if (!c || c.eta > TUNE.ringLead || c.eta < c.sweet - c.band) return; // not yet, or the moment has passed
|
||||
inBand = c.gold;
|
||||
r = rSweet * (1 + Math.max(0, c.eta - c.sweet) * 4); // closes onto the target as the moment nears
|
||||
} else { // the old DISTANCE ring, for A/B
|
||||
const d = Math.hypot(ball.x - P.x, (ball.z - P.z) * 0.8);
|
||||
if (d > TUNE.matchRingFar || ball.vz < 0) return;
|
||||
inBand = Math.abs(d - TUNE.matchRingSweet) < TUNE.matchRingBand;
|
||||
r = rSweet * Math.max(1, d / TUNE.matchRingSweet);
|
||||
}
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.strokeStyle = inBand ? UI.warm : 'rgba(255,255,255,0.55)';
|
||||
ctx.beginPath(); ctx.arc(p.x, p.y, r, 0, 7); ctx.stroke();
|
||||
@@ -1117,6 +1125,12 @@ function drawApexBand() {
|
||||
// walking looks wrong on some of them" has to be checked. Geometry lives here, next to the drawing,
|
||||
// and input.js hit-tests the same function: one source, so a moved button cannot stop responding
|
||||
// (the rack learned this the hard way — see rackGeom).
|
||||
// THE SWING LOG's panel — drawn here, hit-tested by main.js's copy tap (one source, the rackGeom rule)
|
||||
export function logGeom() {
|
||||
const rows = Math.min(9, G.missLog.length);
|
||||
return { x: 6, y: 40, w: W - 12, h: rows * 10 + 16, rows };
|
||||
}
|
||||
|
||||
export function benchButtons() {
|
||||
// FOUR across now: FALL plays the real collapse-and-rise beat (beats.benchFall), which is a SEQUENCE
|
||||
// and not a pose, so it could not be one of the stepped stops.
|
||||
@@ -3296,15 +3310,15 @@ export function render() {
|
||||
ctx.font = F(8); ctx.fillStyle = '#ff9060';
|
||||
ctx.fillText(G.lastMiss.text.toUpperCase(), W / 2, H - 28);
|
||||
}
|
||||
if (G.logShow) { // scoreboard tap: recent swing diagnostics, screenshot-friendly
|
||||
const list = G.missLog.slice(-9), lh = 10, top = 40;
|
||||
ctx.fillStyle = 'rgba(8,10,16,0.88)'; ctx.fillRect(6, top, W - 12, list.length * lh + 16);
|
||||
if (G.logShow) { // scoreboard tap: recent swing diagnostics; tap the panel to COPY the whole log
|
||||
const L = logGeom(), list = G.missLog.slice(-L.rows), lh = 10;
|
||||
ctx.fillStyle = 'rgba(8,10,16,0.88)'; ctx.fillRect(L.x, L.y, L.w, L.h);
|
||||
ctx.font = F(7, 0); ctx.textAlign = 'left';
|
||||
ctx.fillStyle = UI.dim; ctx.fillText('SWING LOG — tap scoreboard to hide', 10, top + 5);
|
||||
ctx.fillStyle = UI.dim; ctx.fillText('SWING LOG — TAP HERE TO COPY · SCOREBOARD HIDES', 10, L.y + 5);
|
||||
const maxCh = Math.floor((W - 20) / 4.3);
|
||||
list.forEach((e, i) => {
|
||||
ctx.fillStyle = e.result === 'HIT' ? '#7fd07f' : '#ff9060';
|
||||
ctx.fillText((e.at + 's ' + e.result + ' ' + e.reason).slice(0, maxCh), 10, top + 16 + i * lh);
|
||||
ctx.fillStyle = e.result === 'HIT' || e.result === 'WON' ? '#7fd07f' : '#ff9060';
|
||||
ctx.fillText((e.at + 's ' + e.result + ' ' + e.reason).slice(0, maxCh), 10, L.y + 16 + i * lh);
|
||||
});
|
||||
}
|
||||
if (G.modal) drawModal(G.modal); // last: it is a wall, nothing draws over it
|
||||
|
||||
Reference in new issue
Block a user