Commit Graph

15 Commits

Author SHA1 Message Date
aevgarik
6bb1c81c83 Doc drift fix + physics root-cause: force outward velocity post-bumper
Codex review on ff7a787:
- #1 Medium: stuck больше не блокирует навсегда (watchdog escalates),
  но физика всё ещё regularly загоняет мяч в тот же AI bumper
  до hard_respawn (91.7s wait в worst case). Не Critical, но root
  cause не устранён.
- #2 Low: комментарии в watchdog и MatchScene drift'нули после v3→v4
  refactor.

## Fix #1 — root-cause физики

Bumper.handleBallHit раньше гарантировал только MIN_POST_HIT_SPEED,
но НЕ направление. Если post-impulse velocity направлена обратно
в bumper (radial component < 0), Matter resolved bы contact pushing
ball back, и мяч застревал в loop.

Теперь после impulse:
- Вычисляем "outward" вектор = от центра bumper'а к центру мяча
- Радиальная компонента velocity = dot(v, outward)
- Если speed < MIN_POST_HIT_SPEED (2.5) ИЛИ radial < 0.5 (мяч
  движется внутрь или почти не движется):
    setVelocity({outward * MIN_POST_HIT_SPEED})

Гарантирует что после КАЖДОГО bumper hit мяч движется наружу
с минимум 250 px/sec. Должно резко снизить частоту stuck-events
и watchdog escalation'ов в реальном gameplay.

## Fix #2 — комментарии под актуальную v4 модель

- src/game/BallStuckWatchdog.ts header: «sliding-window escalation»
  → «cumulative counter (no auto-reset)» + объяснение почему v3
  bug требовал именно cumulative подход
- src/scenes/MatchScene.ts:226: «v3 — position-based» → «v4 —
  position-based + cumulative counter»

## Trade-off (документировано в коде)

Curve bumper's ±35° angle rotation применяется ДО outward-force.
Если итоговая скорость низкая ИЛИ направлена внутрь — outward
форсируется (curve effect частично перебивается). Slingshot/turbo
speedBoost ×1.2/×1.5 обычно достаточен чтобы пройти MIN threshold,
их эффект сохраняется.

107/107 tests, typecheck/lint/build .

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 13:13:17 +03:00
aevgarik
ff7a787474 Anti-stuck v4: cumulative counter + overlap fallback teleport + setup font
Codex post-fix-v3 review #1: 90 секунд зависший матч с 7 ball_unstuck
attempt:1 каждый раз. Причина: v3 healthyReset сбрасывал nudgeAttempts
КАЖДЫЙ раз когда мяч на секунду улетал после nudge → бесконечный цикл
stuck → nudge → отскок → re-stuck → attempt=1 опять.

## Решение (v4)

### CUMULATIVE counter (без auto-reset)
- nudgeAttempts накапливается ПЕРСИСТЕНТНО
- Сбрасывается ТОЛЬКО через: reset() (goal/match-start/cradle release)
  или hard_respawn внутри watchdog
- 4-й fire → hard_respawn независимо от тайминга
- Если мяч re-stuck'ается N раз внутри одного match-phase → escalate

Это устраняет cycle: stuck → recover → re-stuck → ещё раз recover → ...

### Overlap-fallback teleport (codex hadOverlap=false case)
- В applyStuckNudge: если findNearestOverlap returns null (мяч не overlap'ит
  ни с одним bumper'ом — застрял у стенки/флиппера/в воздухе), вместо
  только setVelocity → random teleport ±40 px от current с clamp к границам
- Это форсирует физическое смещение даже без знания «с чем застрял»

### Setup font 13→20px (codex #3)
- 13px → 20px на canvas который масштабируется до 506px
- Цвет #aaaaaa → #cccccc (читабельнее на тёмном фоне)

## Tests

19 unit-тестов для watchdog (cumulative counter, regression, integration smoke):
- CRITICAL FIX test: counter НЕ сбрасывается при healthy движении
- CODEX REGRESSION smoke: stuck → nudge → recover → re-stuck → escalate.
  В v3 этот сценарий не эскалировал (loop'ил attempt=1); в v4 ≥3 nudges
  + ≥1 hard_respawn.
- Healthy gameplay (200 px/sec bouncing) → 0 false-fire'ов.

107/107 tests, typecheck/lint/build .

## codex #2 (Result screen unreachable)

Должен автоматически закрыться вместе с этим fix — gameplay теперь
проходим. Smoke в браузере остаётся за codex.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 12:58:15 +03:00
aevgarik
1f4b5b70a4 Anti-stuck v3: position-based detection + escalation smoke tests
Codex post-fix review: v2 watchdog (velocity-based) НЕ разлеплял реальный
матч — мяч застревал 90 секунд с ball_unstuck attempt:1 hadOverlap:false,
но эскалация до hard_respawn не происходила. Причина: Matter имеет micro-
velocity jitter при contact resolve, поэтому speed периодически превышал
threshold → slow timer сбрасывался → fire не успевал.

## Решение: position-based detection

Watchdog теперь принимает (position, now) вместо (speed, now):
- Если за stuckDurationMs мяч физически не сдвинулся >= 30px → stuck
- Healthy reset: 100 px за 2000ms → сбросить attempts
- Position history с pruning (4s окно)
- Эскалация: 3 nudge → 1 hard_respawn → clear history

Это устойчиво к micro-jitter contact manifold — physical displacement
не зависит от velocity samples.

## Integration smoke tests (codex #3)

Два smoke-теста симулируют реальный gameplay сценарий:
1. «мяч застрял на bumper'е с micro-jitter ±0.3px → 10s симуляция
   → должно быть ≥3 nudge + ≥1 hard_respawn, всего events < 15
   (rate-limit telemetry-noise)». Это ловит exactly баг с 20+ unstuck.
2. «launchBall с healthy движением (200 px/sec bouncing) → ноль
   false-fire'ов за 10s». Проверяет что watchdog не дёргает живой мяч.

Эти тесты НЕ зависят от Phaser/Matter и валидируют core invariant.

## MatchScene wiring

- watchdog.tick(this.ball.position, this.time.now) — position-only
- findNearestOverlap searchRadius 60→80, tolerance 2→14 px (захватывает
  «почти-overlap» edge cases когда мяч слегка вылетел из contact zone)

## Tests

107/107 passed (было 102). 19 unit-тестов для watchdog (включая 2 smoke).
Удалён CFG unused const. Поправлен non-null assertion warning.

## Trade-offs

- Position-based нечувствителен к velocity вообще — для booster.aiming
  watchdog.reset() предотвращает false-fire (sticky-ball).
- После hard_respawn watchdog clear полностью; первые 1.5s после respawn
  новый stuck не fire'тся (нет ещё истории).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 12:13:03 +03:00
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
aevgarik
c175f05748 Post-Phase-3-review: 6 fixes
- Critical #1: anti-stuck watchdog для ball. Если speed < 40 px/sec
  дольше 1.5s — random nudge (350 px/sec) в сторону одних из ворот.
  Skip активен когда booster cradle. + min-speed 2.0 (Matter units)
  после bumper-hit, чтобы curve не абсорбировал энергию.
  Telemetry: ball_unstuck event.
- High #2: touch L/R в aiming-фазе теперь вызывают booster.rotateAim()
  вместо flipper.activate() — mirror keyboard logic. Mobile aiming
  теперь работает.
- High #3: Hard difficulty label явно помечен «(partial) — AI cradle —
  позже». Согласно sprint-mode scope (см. KNOWN_ISSUES.md).
- Medium #4: MatchTracker.recordBoosterUsed('player') вызывается на
  successful attemptCradle → matchResult.boostersUsed теперь корректен.
  AchievementEvaluator unlock'ает cradle_first_use при boostersUsed > 0
  (с idempotency через already-unlocked check). +3 unit-теста.
- Medium UX #5: full-screen overlay'и в MainMenuScene (personality
  selector, difficulty selector, stub message) теперь alpha=1.0
  (раньше 0.92/0.85 — фон просвечивал, ощущение «наслоения»).
- Low #6: footer «Phase 0-2» → «Phase 0-3 dev build».

88/88 tests (было 85). typecheck/lint/build .

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 09:02:46 +03:00
aevgarik
39056fdc74 Phase 3: Booster «Захват», cloud-save, 15 achievements
Реализует Phase 3 в sprint-mode согласно scope-lock + DoD alternative
для Ghost (без adaptation, balanced setup). Полный AI booster откладывается
в Phase 3.5/4 — см. KNOWN_ISSUES.md.

## Booster «Захват» (player-side, полная реализация)

- src/game/Booster.ts — FSM idle→armed→aiming→cooldown
  + charge tracking (100 BP=1 заряд, max 2)
  + lockout 30s от match start
  + slow-mo trigger через event callbacks
  + aim rotation ±10° per tap
  + auto-shoot через cradleDurationMs=1500ms ИЛИ повторный tap
- src/scenes/MatchScene.ts wiring:
  - charge добавляется при credited player hit (result.creditedTo='player')
  - tap BOOST zone / keyboard S → handleBoosterTap
  - handleFlipperHit при armed → attemptCradle на nearest player flipper
  - tickBooster каждый update: sticky-ball anchor, aim indicator, auto-shoot
  - slow-mo через matter.engine.timing.timeScale + time.timeScale + tweens
  - HUD: «BOOST locked/armed/aim L/R · TAP/N/2» + полоска прогресса заряда
  - L/R input в aiming-фазе перенаправляются в booster.rotateAim (не flipper)
- 18 unit-тестов src/game/Booster.test.ts:
  initial state, lockout, charge accumulation/cap, FSM transitions,
  rotateAim/shoot from non-aiming = noop, autoShoot timing, callbacks.

## SaveState v1 + cloud-save

- src/services/SaveStateService.ts:
  - defaultSaveState() — полная схема SaveState (settings, statistics,
    cosmetics, campaign, tournament, achievements={}, ghostProfile=null)
  - SaveStateService.load() / save() через PlatformAdapter.cloudLoad/Save
  - applyMatchResult() — immutable update: statistics totals + achievements
- src/main.ts: cloud-load на boot, registry.set('saveState', state) +
  'saveService'; isReturning telemetry flag из totalMatches > 0
- src/scenes/MatchScene.ts:
  - persistMatchAndUnlocks(matchResult) после endMatch
  - errors swallow'аются + telemetry save_failed (не блокер UX)

## 15 Achievements

- src/types/index.ts: AchievementId union (15 IDs), AchievementsState
- src/config/achievements.ts: 15 определений с title/description
  (first_win, defensive/aggressive/trickster/ghost_*_win, flawless_5_0,
  bumper_master_50, defender_20, cradle_first_use, cradle_goal,
  comeback_3_4, golden_goal_win, marathon_10)
- src/scoring/AchievementEvaluator.ts:
  - evaluateAchievements(ctx) — pure function, проверяет условия
  - applyAchievementUnlocks() — immutable state update
- 17 unit-тестов: basic wins, personality×difficulty, flawless,
  bumper milestones, comeback, cradle/golden goal, marathon,
  applyUnlocks idempotency
- MatchScene: после endMatch evaluate + platform.unlockAchievement(id) +
  telemetry achievement_unlocked

## Hard difficulty (config-level)

- AIDifficulty='hard' уже работает через config (80ms reaction / 20ms jitter)
- UI label fixed: "Профи — 80мс реакция, jitter ±20мс" (раньше "(cradle — Phase 3)")
- AI cradle activation — backlog, см. KNOWN_ISSUES.md

## Sprint-mode Ghost

- GhostProfile остаётся null per scope-lock alternative DoD
- GhostAI без profile-tracking; balanced setup сохранён из Phase 2

## Telemetry

- bumper_hit (Phase 2 v3.10) — уже работает
- achievement_unlocked — emit per ID
- cradle_first_use — emit раз за match
- cradle_goal — emit при scoring через cradle anchor
- save_failed — emit при cloud-save error

## Docs

- KNOWN_ISSUES.md: Phase 3 partial DoD coverage (AI booster wishlist,
  Ghost sprint-mode rationale, defender_20 proxy heuristic)
- README.md: phase plan updated (0-3 )

Tests: 85/85 (5 файлов: calculateScores 16 + flipperGeometry 13 +
MatchTracker 21 + Booster 18 + AchievementEvaluator 17). Build 387KB gzip.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 08:36:43 +03:00
aevgarik
048f9ebdee Symmetric 5-bumper fixed center layout
3 → 5 fixed bumpers: 2 верхних (AI side) + 1 центр + 2 нижних (player
side). Раскладка зеркальна относительно centerY и vertical centerline.

- Все source = 'fixed_last_touch' (defensive scoring v3.10 без изменений)
- Type остаётся standard
- Зона y ∈ 560..720 при centerY=640 — не пересекается с AI setup (y≤300),
  player setup (y≥980), флипперами (y=100, 1180) или touch zones (y≥1060)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 08:17:36 +03:00
aevgarik
32b894ec08 Post-codex-review-5: align event-log с telemetry spec + UX polish
- MatchTracker (codex #1 Low): event-log поля переименованы под
  telemetry-spec.md v3.10 — ballOwner→ballOwnerBeforeHit,
  matchTimeSec→matchTimeSeconds (для bumper_hit и goal events).
  Future-proof для batched emit через .getEvents(). Тест assertion
  обновлён под новые поля.
- Touch zone labels (codex #2 Low): BOOST 14→18px, alpha все
  лейблы 0.5→0.7 для mobile readability.
- Test comment (codex #3 editorial): «player получает hits через
  ai_setup» → корректная формулировка про defensive trap
  (атакующий игрок кормит AI как защитника).

50/50 tests, typecheck/lint/build .

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 08:13:32 +03:00
aevgarik
bfdb24294a Implement defensive setup scoring v3.10
Реализует финальную модель scoring из data-contracts.md v3.10:
fixed center bumpers — по last-touch; setup-bumpers — defensive trap
(награждают владельца только когда мяч принадлежит сопернику).
Self-farm запрещён. Neutral ball не даёт очков нигде.

Code changes:
- types/index.ts: BumperScoreSource union ('fixed_last_touch' |
  'player_setup' | 'ai_setup'); MatchResult.aiBumperPointsEarned +
  aiBumperHitsCount.
- Bumper.ts: новый required field source: BumperScoreSource;
  передаётся в onHit callback через bumper.source.
- scoring/MatchTracker.ts: pure helper computeCreditedTo(source, ballOwner)
  → реализует 9-cell credit matrix; recordBumperHit принимает
  (rawPoints, source, ballOwner, type, ...) и возвращает {creditedTo,
  pointsEarned}; новые getter'ы getAIBumperHits + getUncreditedBumperHits.
- scenes/MatchScene.ts:
  * Fixed bumpers создаются с source='fixed_last_touch'
  * placeSetupBumpers передаёт source per side
  * handleBumperHit (новая сигнатура: bumper, points) рутит в tracker,
    emit'ит spawn-particles + telemetry bumper_hit event с полным
    набором полей per telemetry-spec.md v3.10
  * MatchResult заполняется с AI BP/hits
  * match_end telemetry также включает aiBumperPointsEarned/HitsCount
  * HUD: BP теперь сравнительный «P x / AI y»
- scenes/ResultScene.ts: breakdown показывает обе стороны BP и hits.

Particle effects (DoD v3.10):
- 12 псевдо-частиц радиально от bumper'а (cubic ease-out, 320ms)
- цвет: player=magenta, ai=cyan, null=grey
- screenshake 80ms/0.0025 ТОЛЬКО на credited hit
  (иначе экран дрожит на каждом нейтральном отскоке)

Tests: scoring/MatchTracker.test.ts — 21 теста:
- 9-cell credit matrix (computeCreditedTo, все source × ballOwner)
- recordBumperHit с counter assertions
- многократные hits (накопление BP per сторона)
- симметрия player/AI defensive trap scenarios
- fixed bumpers через смену владения
- events log для telemetry
- recordGoal с autogoal-flag

Итого 50/50 unit-тестов (16 calculateScores + 13 flipperGeometry +
21 MatchTracker). typecheck/lint/build все зелёные.

Несоответствия с docs остающиеся:
- DoD: «12 частиц per credited side» — done; SFX bumper hit (DoD line 247)
  — TODO Phase 4 (asset pack).
- Particle effects через psecdoupartials (Arc + tween), не через
  ParticleEmitter — упрощение, визуально эквивалентно.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 07:27:01 +03:00
aevgarik
a0ed17d814 Post-codex-review-4: flipper geometry fix + HUD repositioning
Critical bug (codex #1 High): концы нижних флипперов скрещивались В gap'е
(left.x=401, right.x=319 — оба в 306..414, причём X-shape) и торчали НИЖЕ
линии ворот (y=1275 vs playerGoalY=1260). Это блокировало корректный
gameplay — флипперы стояли крестом в створе.

Дополнительно: AI флипперы имели НЕВЕРНУЮ ориентацию (формула +180°
вместо y-flip) — concы торчали ВЛЕВО-ВНИЗ от pivot, наружу от AI ворот.
AI просто не отбивал мячи геометрически правильно.

Fixes:
- src/game/flipperGeometry.ts: pure utility — computeFlipperAngleDeg
  (sign × ownerSign × baseAngle = правильный y-flip между player/AI)
  + computeFlipperEnd (вычисление end position).
- src/game/Flipper.ts: использует утилиту; удалено +180 для AI.
- src/scenes/MatchScene.ts: pivot offset 100px по x (за gap edge) и
  80px по y (над goal line). Применено для всех 4 флипперов
  (player+AI × left+right).

HUD (codex #2 Medium): BP перенесён с y=GAME_HEIGHT-60 (= в зоне
флипперов/BOOST overlay) на y=145 (под таймером). Управление и HUD
теперь визуально разнесены.

Tests: добавлен src/game/flipperGeometry.test.ts — 13 проверок
(угол-симметрия player/AI, инвариант «end НЕ в gap И НЕ за линией»,
swing direction, regression-test для бывшего X-скрещивания). Итого
29/29 тестов зелёные.

codex #3 (npm audit) — без изменений, документирован в KNOWN_ISSUES.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 04:29:11 +03:00
aevgarik
a1b0ae9e96 Post-codex-review-3 docs: KNOWN_ISSUES + playtest protocol
- KNOWN_ISSUES.md: dev-only vite/esbuild moderate CVEs с impact analysis
  (production бандл не затронут), mitigation, плановый vite 5→8 + vitest 1→3
  апгрейд перед Phase 5; AI distinguishability caveat с ссылкой на playtest.
- playtest-protocol.md: 20-матчевая blind-attribution методика для закрытия
  Phase 2 DoD «тестер различает 4 личности»; рубрика per-match, expected
  tells (для оценщика, не тестера), randomization protocol, failure-mode
  follow-ups, report template, codex-automation roadmap.
- README.md: новый раздел «Operational docs» с cross-ref на оба файла.

Не код — операционная документация для closure Phase 2 и трекинга backlog.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 04:10:04 +03:00
aevgarik
bb9f7209ae Post-codex-review-2: mobile controls, turbo bug, AI diff, favicon
- Mobile match controls (codex #1 High): 3 touch zones внизу MatchScene
  (L / BOOST / R). Visible semi-transparent overlay'и; работает и на mouse,
  и на touch. Keyboard input теперь optional (this.keys?), не падает на
  headless mobile.
- Turbo first-hit bug (codex #2): guard на lastHitTime > 0 — иначе первый
  удар по turbo в первую секунду давал 0 очков (now < 0 + 1000).
- AI behavior diff (codex #3): добавлен PersonalityModifier (triggerDistBonus,
  reliabilityMul, reactionMul). Defensive: +reliability, -reaction.
  Aggressive: +trigger distance, -reliability. Ghost: 15% «эхо-double-swing»
  (обе стороны одновременно). Trickster уже имел jitter modifier.
  Поведенческие отличия теперь должны быть различимы за 5 матчей.
- Favicon 404 (codex #4 Low): inline SVG favicon в index.html, no more
  console noise.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 03:59:41 +03:00
aevgarik
4e3172de81 Phase 2 DoD closeout: curve physics, AI delta, share, setup UX
- Curve bumper: spin-modifier ±35° на отскоке (детерминированный знак на bumper)
  + визуальный вихрь-индикатор. Теперь curve механически отличается от standard.
- Easy/Medium AI delta: распознавательная дистанция (Easy 150px / Medium 280px),
  reliability (Easy 0.75 / Medium 0.95). difficulty прокидывается из каждой
  personality в shouldActivateFlipperBase. Hard остаётся placeholder для Phase 3.
- Share button на ResultScene: snapshot canvas → PNG blob → platform.shareImage().
  Mock логирует, YG использует SDK share. Telemetry: share_clicked / share_failed.
- Setup UX переписан: палитра типов сверху (4 типа + trash) + tap-палитра-tap-слот
  (unified mobile/desktop) + drag-from-palette ghost (desktop). Long-tap слота без
  выбранного типа удаляет содержимое (быстрый shortcut). Заменил cycle-on-click
  на normalуй setup, как в DoD.

Closes codex finding #2 (Phase 2 DoD gap).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 03:50:16 +03:00
aevgarik
8e49e088b0 Post-codex-review fixes: build, telemetry, last-touch, tests
- Fix #1 (Critical): rename private `data` → `sceneData` in Match/Setup/Result
  scenes (collided с Phaser.Scene.data DataManager, ломало TS build).
  Убран unused `game` var в main.ts. typecheck + build теперь зелёные.
- Fix #3: matchId генерируется один раз в MatchScene.create() и
  переиспользуется в match_start, match_end, MatchResult — telemetry funnel
  теперь связывается.
- Fix #4: AI setup сохраняется в `aiSetupConfig` и кладётся в MatchResult.aiSetup
  (раньше всегда пустой). Также убрана двойная инстанциация AI
  (createAIPlayer() вызывался дважды).
- Fix #5: last-touch меняется на ЛЮБОМ касании флиппера (активном или
  пассивном). Раньше owner не обновлялся при пассивном отскоке → ломалась
  autogoal-логика.
- Lint: убран unused `oldOwner` параметр в spawnBall callback.
- Tests: добавлены 16 unit-тестов для calculateScores
  (`src/scoring/calculateScores.test.ts`) — покрытие baseline, mode/difficulty
  multipliers, penalty стэкинг, double-points (local-only), Anti-A2W
  invariant (base ≤ local при penalty без double-points).
- package-lock.json закоммичен для reproducible build.

Codex findings #2 (Phase 2 DoD: drag-and-drop setup, curve bumper physics,
Share button, Easy/Medium delta) — отдельным заходом.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 03:43:59 +03:00
dc53623001 Phase 0-2: Vite/Phaser 4/TS boilerplate + Platform Adapter + core match (vs 4 AI personalities)
Phase 0 (foundation):
- package.json, tsconfig.json (strict), vite.config.ts (base: './'), .eslintrc.cjs
- index.html, src/main.ts (Phaser game init + platform detection)
- src/config/defaults.ts (Remote Config defaults, palette, constants)
- src/types/index.ts (все schemas из data-contracts: SaveState, MatchResult, RemoteConfig, etc.)
- src/platform/: PlatformAdapter interface + MockPlatformAdapter + YgPlatformAdapter

Phase 1 (core mechanics):
- src/scenes/: Boot, Preload, MainMenu (Quick Match selector), Setup, Match, Result
- src/game/: Table (геометрия + walls + ворота), Ball (last-touch + color tracking), Flipper (player + AI, swing + cooldown), Bumper (4 типа: standard/slingshot/curve/turbo)
- src/scoring/: MatchTracker (last-touch + bumper points), calculateScores (unified scoring pipeline per data-contracts v3.8 requirement)
- src/ai/: AIPlayer interface + DefensiveAI Easy/Medium

Phase 2 (setup + AI personalities):
- src/scenes/SetupScene (5 active slots, click-cycle bumper types, 30s timer)
- src/ai/personalities/: AggressiveAI, TricksterAI, GhostAI (Easy/Medium; Hard cradle-aim = Phase 3)
- src/ai/factory.ts (createAIPlayer)
- MainMenuScene: выбор AI-личности + сложности
- MatchScene использует factory вместо хардкода

Anti-A2W enforcement: calculateScores применяет penalty per v3.5 model
(Continue × 0.5, Campaign Skip × 0.7, +1 заряд × 0.9, реролл × 0.85,
Boost active × 0.7; ×2 sezon очки только в local).

Что НЕ в этом commit'е (следующие фазы):
- Phase 3: бустер «Захват» (cradle + slow-mo), Hard AI (cradle-aim solver), Ghost adaptation, YG Player API cloud-save, achievements
- Phase 4: Campaign (12 матчей), Tournament (8/16 bracket), шейдеры (CRT + bloom + chromatic aberration), 29 AI-арт ассетов, музыка
- Phase 5: реальная YG SDK интеграция (ads + payments), Remote Config Nakama RPC
- Phase 6: YG submission

Pre-greenlit concept-пакет — ~/Knowledge/Projects/pinball-duel/ (19 файлов, 10 audit-раундов).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 03:25:17 +03:00