aevgarik 29f809fd84 Anti-stuck v2: physical displacement + hard respawn
Codex post-Phase-3-review #1: v1 watchdog (только setVelocity) не разлеплял
contact manifold — мяч продолжал застревать после 20+ nudge'ов.

## Решение

### src/game/BallStuckWatchdog.ts (pure logic)
- StuckAction = 'none' | 'nudge' | 'hard_respawn'
- BallStuckWatchdog class — FSM-tracker:
  - slow >= 1500ms → nudge (max 3 раза)
  - 4-й fire → hard_respawn
  - 2s стабильного движения → reset attempts
  - fireCooldownMs=500 защищает от per-tick спама
- findNearestOverlap() pure — находит overlapping static body
- computeDisplacementTarget() pure — безопасная position от body

### src/scenes/MatchScene.ts integration
- applyStuckNudge: вместо просто setVelocity →
  (1) физически телепортирует ball.setPosition() прочь от nearest bumper
      на (bodyR + ballR + margin)
  (2) velocity в направлении прочь от bumper + ±30° random
- applyStuckHardRespawn: destroy + spawn from side port + reset watchdog
- watchdog.reset() при goal respawn и cradle (booster.aiming)
- Skip когда booster cradle (мяч намеренно sticky)

### Telemetry
- ball_unstuck event теперь содержит { attempt, hadOverlap } для дебага
- ball_hard_respawn — отдельный event при escalation
- Rate-limit через max attempts (3) + cooldown — не >3 unstuck per match
  плюс 1 respawn событие; в v1 было >20 unstuck в одном матче

### Tests
- src/game/BallStuckWatchdog.test.ts: 14 unit-тестов
  - basic states, fire cooldown, escalation to hard_respawn,
    attempts reset через healthy move, reset(), findNearestOverlap
    с overlap detection, computeDisplacementTarget math
- Fix sentinel-zero bug: использовал | null вместо 0
  (now=0 коллизировал с initial state)
- 102/102 tests (было 88)

## Не сделано в этом коммите (codex #2 backlog)

E2E integration test «после launchBall за 10s position изменилась
и ball_unstuck < N». Требует vitest + happy-dom + Phaser headless —
большой setup. Текущие unit-тесты покрывают pure logic; visual smoke
в браузере остаётся ручной для финальной верификации.

typecheck/lint/build .

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

Pinball-Duel

Single-player пинбол vs 4 AI-личности (head-to-head формат, Williams Joust 1983).

Stack: Phaser 4 (beta) + TypeScript + Vite + Matter.js (bundled). Capacitor добавляется в Phase 1.5.

Platforms:

  • MVP: Yandex Games (web only)
  • Phase 1.5 (post-MVP-launch): Google Play + RuStore (Android, через Capacitor)
  • Post-Phase 2: iOS (если PMF подтвердится)

Status: Phase 0 (pre-production) в процессе. Концепт-пакет — ~/Knowledge/Projects/pinball-duel/ (19 файлов, 10 audit-раундов).

Development

Requirements

  • Node.js 20+
  • npm

Setup

npm install
npm run dev          # localhost:5173

Build & checks

npm run build        # type-check + production build → dist/
npm run preview      # serve build locally
npm run typecheck    # tsc --noEmit
npm run lint         # ESLint
npm run test         # Vitest

Architecture

Game stack

  • Phaser 4 — game engine + Matter.js physics (bundled)
  • TypeScript strict — все типы из src/types/index.ts (схемы из data-contracts.md)
  • Vite — bundler + dev server (с base: './' для YG)

Platform Adapter

Game-код не знает о платформе через PlatformAdapter interface (src/platform/). Реализации:

  • MockPlatformAdapter — local dev и unit tests
  • YgPlatformAdapter — Yandex Games SDK (MVP)
  • CapacitorAdmobAdapter — Phase 1.5 (Play)
  • RuStorePlatformAdapter — Phase 1.5 (RuStore)

Detection и factory в PlatformAdapter.ts::createPlatformAdapter().

Структура папок

src/
  config/      Remote Config defaults, палитра, константы
  types/       SaveState, MatchResult, RemoteConfig, etc.
  platform/    PlatformAdapter interface + 4 реализации
  scenes/      Phaser scenes (Boot, Preload, MainMenu, Setup, Match, Result)
  game/        Game objects (Ball, Flipper, Bumper, Table, Match orchestration)
  ai/          AI personalities (Defensive, Aggressive, Trickster, Ghost) + heuristics
  scoring/     calculateScores + MatchTracker (defensive setup scoring + bumper points)

Фазовый план

Phase Содержание
0 () Vite + Phaser 4 + TS boilerplate; Platform Adapter
1 () Core mechanics: матч, флипперы, мяч, last-touch, fixed bumpers, Defensive Easy AI
2 () Setup-фаза, 4 BumperType, 4 AI-личности × 3 сложности
3 ( partial, sprint-mode) Бустер «Захват» player-side, Hard difficulty (config-driven), cloud-save, 15 achievements. AI cradle + Ghost adaptation вынесены в backlog — см. KNOWN_ISSUES.md
4 Campaign + Tournament + визуальный polish (шейдеры), ассеты
5 YG SDK ads + payments + Remote Config
6 YG polish + submission + soft launch
1.5 (post-MVP launch) Capacitor: Google Play + RuStore mobile native

Operational docs (в репо)

  • KNOWN_ISSUES.md — toolchain vulnerabilities, плановые апгрейды, текущие game-side caveats
  • playtest-protocol.md — спека blind-attribution тестирования AI-личностей для закрытия Phase 2 DoD

Документация (концепт-пакет)

~/Knowledge/Projects/pinball-duel/ — 19 файлов:

  • index.md — overview + decision-history (10 audit-раундов)
  • concepts/scope-lock.md — что в каждой фазе (single source of truth)
  • concepts/data-contracts.md — все schema'ы (Remote Config, MatchResult, SaveState, leaderboard formula)
  • concepts/definition-of-done.md — DoD per фаза
  • concepts/telemetry-spec.md — event map
  • concepts/development-roadmap.md — план разработки
  • concepts/monetization.md — IAP + Anti-A2W правило
  • concepts/bot-ai-design.md — 4 AI-личности
  • player-guide.md — narrative-руководство игрока

License

Proprietary, all rights reserved.

Description
Single-player пинбол vs 4 AI-личности (head-to-head, Williams Joust 1983 format). Phaser 4 + TS + Vite + Matter.js. MVP YG-web only.
Readme 747 KiB
Languages
TypeScript 99.4%
HTML 0.6%