Files
pinball-duel/playtest-protocol.md
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

7.8 KiB
Raw Permalink Blame History

Playtest Protocol — Phase 2 AI Distinguishability

Цель. Закрыть Phase 2 DoD «тестер различает 4 AI-личности после 5 матчей» через blind attribution. Требуется внешний тестер (НЕ разработчик и НЕ автор AI кода).

Pass-bar: ≥ 75 % правильных attribution-ответов (≥ 15 / 20) ИЛИ ≥ 1 distinct tell per личность, названный тестером без подсказки.


Подготовка

Шаг Команда / действие
Build npm run build && npm run preview (production build на localhost:4173)
Difficulty Medium — Easy скрывает поведение за reaction-задержкой; Hard не реализован (Phase 3)
Mode Quick Match → выбор личности → Medium
Device Минимум 1 desktop + 1 mobile session (mobile важно — touch zones там единственный input)
Telemetry Mock adapter логирует trackEvent в консоль — оставить открытой DevTools для пост-анализа

Per-match observation rubric

После каждого матча тестер заполняет строку:

Поле Что фиксировать Источник
match_index 1..20 manual
setup_pattern Какие bumper-типы AI поставил, где (углы / центр / далеко) визуально, AI половина
reaction_distance «early / normal / late» — насколько близко мяч долетает до AI-флипперов субъективно
miss_rate «низкий / средний / высокий» — как часто AI пропускает свой удар визуально
special_behaviour Любой характерный приём (двойной свинг, странный угол отскока, агрессивная атака центром) free-text
final_score Player : AI HUD post-match
duration_sec Длина матча HUD timer / match_end telemetry
personality_guess Бэтти / Турбо / Хитрый Хо / Эхо blind по завершению ВСЕХ 20 матчей

Expected tells (для оценщика; тестеру НЕ показывать до конца сессии)

Личность Tells
Defensive (Бэтти) Setup: 2 slingshot в углах ворот + 1 турбо центр + 1 curve + 1 пусто. Поведение: реакция надёжная (reliabilityMul: 1.1, capped 1.0 = почти 100 %), чуть быстрее (reactionMul: 0.9). AI пропускает редко; матчи длиннее обычного.
Aggressive (Турбо) Setup: 2 турбо в своей зоне (defensive trap против мяча игрока) + 1 slingshot угол + 1 curve + 1 standard. Поведение: реагирует с большего расстояния (triggerDistBonus: +80px), чаще промахивается (reliabilityMul: 0.85). Быстрые матчи; игрок, застрявший в setup Турбо, быстро отдаёт ему очки.
Trickster (Хитрый Хо) Setup: 4 curve bumper'а (лабиринт) + 1 турбо центр. Поведение: 15 % случайный спайк jitter (×1.3) — реакция «дёргается». Непредсказуемый счёт; необычные траектории мяча.
Ghost (Эхо) Setup: balanced (2 slingshot + 1 турбо + 1 curve + 1 standard). Поведение: 15 % «echo double-swing» — обе стороны активируются одновременно, даже если мяч идёт только в одну. Медианный счёт; характерный двойной flip.

Процедура

  1. Randomize order. Использовать random.org pick из [defensive, aggressive, trickster, ghost], 5 раз для каждой → 20 матчей. Не группировать по личности — рассыпать. Пример последовательности: [T, D, G, A, T, A, D, G, T, A, D, G, A, T, D, G, A, D, G, T].

  2. Играть в выбранном порядке. После каждого матча — заполнить строку рубрики. НЕ смотреть expected tells между матчами.

  3. Blind attribution. По завершении всех 20 матчей — для каждой строки заполнить personality_guess без обращения к памяти о выборе перед матчем.

  4. Score. Сравнить personality_guess с фактическим выбором. Подсчитать accuracy.

  5. Subjective rating. Тестер ставит общую оценку «AI feels different» по 5-балльной шкале (≥ 4 = pass).

  6. Report. Заполнить шаблон ниже, приложить к Phase 2 sign-off.


Failure modes & follow-ups

Результат Что делать
Accuracy < 50 % Модификаторы слишком слабые. Удвоить triggerDistBonus, опустить reliabilityMul ниже 0.7.
Accuracy 50-74 % Две личности путаются между собой. Развести их setup'ом сильнее (например, поменять Defensive curve → second slingshot для большего «защитного» паттерна).
Accuracy ≥ 75 %, subjective < 4 Поведение различимо, но не интересно. Это не блокер Phase 2, но завести задачу на «character polish» в Phase 4.
Тестер жалуется «AI слишком сложный/лёгкий на Medium» Тюнить reactionMs в src/config/defaults.ts — не personality-specific.

Codex automation (опционально)

codex exec потенциально может прогнать матчи через headless Phaser и replay-log; но для Phase 2 sign-off достаточно ручного playtest'а. Маппинг рубрики в telemetry events:

Поле рубрики Telemetry source
setup_pattern (AI) MatchResult.aiSetup.slots
reaction_distance derivable: ball trajectory + goal_scored.ballOwnershipBeforeGoal
miss_rate 1 (AI flipper hit count / ball-passes-through-AI-zone count) (нужен новый event flipper_hit — не в Phase 2 scope)
final_score match_end.goalsScored / goalsAgainst
duration_sec match_end.durationSeconds

Автоматизация — backlog Phase 5+.


Report template

# Playtest Report — Phase 2 AI Distinguishability

**Tester:** <имя / handle>
**Date:** <YYYY-MM-DD>
**Build:** HEAD <git-sha>
**Devices:** desktop=<browser/os>, mobile=<browser/os>

## Match log
| # | Personality (actual) | Setup observed | Reaction | Miss rate | Special | Score | Duration | Guess | Match? |
|---|---|---|---|---|---|---|---|---|---|
| 1 | trickster | 4 curve + turbo | normal | medium | hectic angles | 5:3 | 4m12s | trickster | ✅ |
| 2 | ... | | | | | | | | |

## Results
- Accuracy: __ / 20 = __ %
- Subjective rating: __ / 5
- Per-personality breakdown:
  - Defensive: __ / 5
  - Aggressive: __ / 5
  - Trickster: __ / 5
  - Ghost: __ / 5

## Free-text observations
<распознанные tells, неожиданные моменты, баги>

## Verdict
- [ ] Phase 2 AI DoD PASS (≥75% accuracy + ≥4/5 subjective)
- [ ] Phase 2 AI DoD FAIL — see follow-ups