aevgarik 4548ae7544 CRITICAL fix: Matter velocity units — launch ball moved 60x too fast
## Корневая причина статичного поля (codex #1)

launchBall и Booster.shoot имели мёртвый код:
   speed * 0.01 * 100  ==  speed  (no-op)
который должен был быть преобразованием px/sec → Matter units (/60),
но эффективно НЕ конвертировал.

setVelocity(400, 0) в Matter = 400 units/timestep = ~24000 px/sec.
Это вызывало:
- Ball tunnel'ил через стенки/bumpers (Matter без CCD не handle'ит
  velocity >> body_size/timestep)
- Watchdog видел chaotic position и fire'ил постоянно (3-7 ball_unstuck
  в одном матче по словам codex)
- В browser screenshot ball мог оказаться где угодно или вне viewport,
  визуально создавая впечатление "статичного поля"

Goals регистрировались потому что ball все равно проходил через
goal zone при tunneling, telemetry работала, но визуальный feedback
был сломан.

## Fix

### MatchScene.launchBall
```
const speedMatter = BALL_SPEED_START / 60; // 400/60 = 6.67 Matter units
this.ball.setVelocity(cos(angle) * speedMatter, sin(angle) * speedMatter);
```
Результат: ~6.67 Matter units = ~400 px/sec (intended pinball speed).

### Booster.shoot
Аналогично: shootSpeed/60 = ~18.3 Matter units = ~1100 px/sec.

## Дополнительные fixes

### bumperPhysics.computeOutwardForce — preserve speed
Раньше force outward всегда сбрасывал velocity на minSpeed=2.5
(replace, not min). Fast ball heading into bumper резко тормозил.

Теперь: targetSpeed = Math.max(speed, minSpeed). Direction меняется
наружу, но магнитуда preserved (минимум minSpeed как floor).

minSpeed bumped 2.5 → 5 (~300 px/sec @60fps) — для надёжного escape
из contact manifold при низкоскоростных hits.

### Tests

- Обновил «fast + inward» test — теперь expect preserved speed (5)
  вместо клипа до 2.5
- Обновил «tangential» аналогично
- Добавил «REAL fix» test: very fast inward (speed=10) → preserved
  to 10, не клипнуто к minSpeed

118/118 tests, typecheck/lint/build .

## Ожидаемый эффект

Ball теперь должен:
1. Двигаться с осмысленной скоростью (~400 px/sec на launch,
   ~600-1000 px/sec после флипперов)
2. НЕ tunnel'ить через объекты (velocity << body_size/timestep)
3. Visible в каждом frame screenshot
4. Watchdog почти не должен срабатывать (только на genuine edge cases)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 13:37:32 +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%