Basket: la revisione di Codex, i punti gravi

Dalla mira iniziale (52°) da sotto non entrava con nessuna forza: ora 64°, forza minima più bassa e prima postazione
un passo indietro, così da ogni postazione entra in una fascia di forza contigua di almeno l'8% (test). Il palo
fermava anche sopra la sua cima e contava come tiro pulito; il tabellone lasciava passare da dietro. Il canestro
conta appena entra (anche il risultato all'ultimo pallone), la pausa fra un pallone e l'altro è più corta, la palla
entrata non rotola fino in fondo. Andrea resta a braccia alzate finché la palla vola (niente doppio pallone), il
rilascio parte sopra la sua testa. La guida si ferma al primo urto e ha un bordo scuro con la punta; il lampo del
canestro si spegne; l'obiettivo del livello è sempre in vista; i microurti sul ferro non tintinnano; dietro Andrea
non si carica; l'aiuto spiega il clic che fissa la mira e le soglie.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECdBYE8csKd4tcFvwntT
This commit is contained in:
2026-10-27 23:39:30 +01:00
co-authored by Claude Fable 5.1
parent f1de62f01e
commit 8b957c7cf7
6 changed files with 133 additions and 52 deletions
+27 -16
View File
@@ -1,12 +1,13 @@
import { FLOOR, RIM, BALL, BALLS, SPOTS, canThrow, left, spot, releasePoint, arc, status } from './basket.js';
import { FLOOR, RIM, BALL, BALLS, SPOTS, canThrow, left, spot, releasePoint, guide as guidePoints, status } from './basket.js';
import { text, panel } from './bocce-render.js';
const ANDREA = { scale: 0.66 };
// Quale posa: carica mentre tiene premuto, il salto appena lasciata la palla, l'esultanza quando entra.
// Quale posa: carica mentre tiene premuto; finché la palla vola Andrea resta a braccia alzate (l'unica posa senza
// pallone), esulta quando entra, e a palla ferma ce l'ha di nuovo in mano.
export function andreaPose(c) {
if (c.holding) return 'load';
if (c.step === 'flight' && c.ball.time < 0.45) return 'shoot';
if ((c.step === 'settle' || (c.step === 'flight' && c.ball.scored)) && c.ball?.scored) return 'cheer';
if (c.step === 'flight') return c.ball.scored ? 'cheer' : 'shoot';
if (c.step === 'settle' && c.ball?.scored) return 'cheer';
return 'hold';
}
function andrea(ctx, c, sheet) {
@@ -37,21 +38,31 @@ function ball(ctx, b, r = BALL) {
}
function guide(ctx, c) {
const r = releasePoint(c);
// La freccia della mira c'è sempre; la parabola per quanto concede il livello.
const a = c.angle * Math.PI / 180, len = 22 + c.power * 18;
ctx.strokeStyle = 'rgba(255,246,210,0.9)'; ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(r.x, r.y); ctx.lineTo(r.x + Math.cos(a) * len, r.y - Math.sin(a) * len); ctx.stroke();
// La freccia della mira c'è sempre, con la punta e un bordo scuro per staccarsi da rete e alberi; la parabola per
// quanto concede il livello, fino al primo urto.
const a = c.angle * Math.PI / 180, len = 22 + c.power * 18, tip = { x: r.x + Math.cos(a) * len, y: r.y - Math.sin(a) * len };
const arrow = () => {
ctx.beginPath(); ctx.moveTo(r.x, r.y); ctx.lineTo(tip.x, tip.y);
for (const s of [-1, 1]) { ctx.moveTo(tip.x, tip.y); ctx.lineTo(tip.x - Math.cos(a - s * 0.5) * 6, tip.y + Math.sin(a - s * 0.5) * 6); }
ctx.stroke();
};
ctx.lineWidth = 3; ctx.strokeStyle = 'rgba(20,16,10,0.8)'; arrow();
ctx.lineWidth = 1; ctx.strokeStyle = '#fff6d2'; arrow();
if (c.skill.preview > 0) {
ctx.setLineDash([2, 4]); ctx.strokeStyle = 'rgba(143,199,255,0.8)';
ctx.beginPath();
for (const [i, p] of arc(c, c.skill.preview).entries()) { if (p.y > FLOOR) break; if (i) ctx.lineTo(p.x, p.y); else ctx.moveTo(p.x, p.y); }
ctx.stroke(); ctx.setLineDash([]);
const pts = guidePoints(c, c.skill.preview);
for (const [width, color] of [[3, 'rgba(20,16,10,0.6)'], [1, 'rgba(143,199,255,0.95)']]) {
ctx.lineWidth = width; ctx.strokeStyle = color; ctx.setLineDash([2, 4]);
ctx.beginPath();
for (const [i, p] of pts.entries()) { if (i) ctx.lineTo(p.x, p.y); else ctx.moveTo(p.x, p.y); }
ctx.stroke(); ctx.setLineDash([]);
}
}
text(ctx, `${Math.round(c.angle)}°`, r.x - 6, r.y - 10, 6, '#fff4c9', 'right');
ctx.fillStyle = 'rgba(20,16,10,0.6)'; ctx.fillRect(r.x - 34, r.y - 16, 30, 11);
text(ctx, `${c.angle.toFixed(c.angle % 1 ? 1 : 0)}°`, r.x - 6, r.y - 10, 6, '#fff4c9', 'right');
}
function swish(ctx, c) {
// Un lampo di righe attorno al ferro quando la palla è appena entrata.
const b = c.ball; if (!b?.scored || b.time - (b.scoredAt ?? b.time) > 0.3) return;
const b = c.ball; if (c.step !== 'flight' || !b?.scored || b.time - b.scoredAt > 0.3) return;
const cx = (RIM.x1 + RIM.x2) / 2;
ctx.strokeStyle = 'rgba(255,240,170,0.8)';
for (let i = 0; i < 6; i++) { const t = i / 6 * Math.PI * 2; ctx.beginPath(); ctx.moveTo(cx + Math.cos(t) * 20, RIM.y + Math.sin(t) * 8); ctx.lineTo(cx + Math.cos(t) * 30, RIM.y + Math.sin(t) * 13); ctx.stroke(); }
@@ -77,14 +88,14 @@ export function drawBasket(ctx, c, def, bg, sheet) {
panel(ctx, 182, 50, 440, 24);
text(ctx, status(c, def.names), 402, 63, 6, '#fff0cb', 'center');
// I palloni rimasti e il conto.
panel(ctx, 18, 86, 156, 58);
panel(ctx, 18, 86, 196, 58);
text(ctx, 'PALLONI', 26, 98, 6, '#eed5a0');
for (let n = 0; n < BALLS; n++) {
const x = 30 + n * 14, y = 113;
if (n < left(c)) ball(ctx, { x, y, spin: 0 }, 4);
else { ctx.strokeStyle = '#766e5c'; ctx.strokeRect(x - 3, y - 3, 6, 6); }
}
text(ctx, `${c.baskets} canestri · ${c.points} punti`, 26, 131, 6, '#d7ccb4');
text(ctx, `${c.baskets} su ${c.skill.goal} per la gloria · ${c.points} punti`, 26, 131, 6, c.baskets >= c.skill.goal ? '#b9e5b3' : '#d7ccb4');
// La forza in basso a sinistra: a destra c'è il pulsante per uscire.
if (canThrow(c) || c.holding) {
panel(ctx, 18, 306, 176, 37);