Files
pinball-duel/index.html
aevgarik cff8656f0b Sprite sync hardening, fix double-centering, extract Bumper outward-force to testable helper
## Codex review on 6bb1c81

### #1 Critical (corrected): visible ball not syncing

Codex заявил что ball.syncSprite() нигде не вызывается. Это неверно —
syncSprite вызывается каждый update tick на MatchScene.ts:216 (grep
подтверждает). Но описанный симптом (ball визуально не двигается) —
реальная проблема и требует hardening.

Hardening:
- Ball.setPosition() теперь immediate-sync sprite вместе с physics body.
  Раньше при teleport (stuck nudge, hard respawn) sprite оставался на
  старой позиции до следующего update tick — теоретическая 1-frame
  рассинхронизация устранена.
- Ball sprite получил setDepth(30) — выше fixed/setup bumper'ов чтобы
  не быть случайно перекрытым в render order.

### #2 High: double-centering canvas

Корень: #game-container использовал display:flex justify-content:center,
И Phaser Scale.CENTER_BOTH добавлял margin-left на canvas. Оба
центрирования складывались → canvas сдвинут вправо.

Fix: убрал flex из #game-container, оставил только Phaser CENTER_BOTH
(он надёжно центрирует через computed margin'ы).

### #3 Medium: physics fix не тестируется

Extract Bumper outward-force logic в pure helper src/game/bumperPhysics.ts
(computeOutwardForce). Возвращает new velocity или null если apply не
нужен.

src/game/bumperPhysics.test.ts — 10 unit-тестов:
- happy paths: fast outward, fast diagonal outward → null
- force apply: slow outward (still apply min speed), fast inward,
  zero velocity, fast tangential (radial<threshold)
- direction correctness: ball over bumper → outward up; ball diagonal
  → outward по диагонали 45°
- degenerate: ball точно в центре bumper → fallback вверх
- REAL stuck regression: low speed + radial nearly zero → force outward

Bumper.handleBallHit рефактор: вызывает computeOutwardForce, applies
если non-null.

117/117 tests (было 107). typecheck/lint/build .

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 13:24:47 +03:00

52 lines
1.9 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover" />
<meta name="theme-color" content="#0a0a14" />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%230a0a14'/%3E%3Ccircle cx='16' cy='16' r='8' fill='%23ff006e'/%3E%3Ccircle cx='16' cy='16' r='3' fill='%2300f0ff'/%3E%3C/svg%3E" />
<meta name="description" content="Дуэльный пинбол с 4 AI-противниками — у каждого свой характер." />
<title>Пинбол-Дуэль</title>
<style>
html, body {
margin: 0;
padding: 0;
overflow: hidden;
background: #0a0a14;
width: 100%;
height: 100%;
-webkit-tap-highlight-color: transparent;
touch-action: none;
user-select: none;
-webkit-user-select: none;
}
/* НЕ используем flex centering — Phaser Scale.CENTER_BOTH центрирует canvas
через margin'ы; flex поверх этого создаёт double-centering и сдвиг вправо. */
#game-container {
width: 100vw;
height: 100vh;
}
#loading {
color: #ff006e;
font-family: 'Press Start 2P', monospace, sans-serif;
font-size: 14px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
animation: pulse 1.2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
</style>
</head>
<body>
<div id="game-container">
<div id="loading">Loading…</div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>