Doc fix + stuck telemetry position для hot-spot analysis
Codex review on c9826ce — 3 findings, 2 actionable, 1 backlog.
## #2 Low: comment в flipperState расходится с реализацией
Старый комментарий писал что press во время releasing rejected, но
тесты и код специально разрешают re-press (responsive design choice).
Fixed: переписан state diagram с explicit «releasing» state как
intermediate (cooldown ещё НЕ активен). Добавлено явное упоминание
что re-press allowed во время releasing — это design choice, не bug.
## #1 High backlog: bonus telemetry для hot-spot analysis
Codex отметил residual ball_unstuck/hard_respawn без конкретных hot-spot
данных. Чтобы убрать blind tuning, добавил позицию + bumper type в
telemetry events:
- ball_unstuck: + ballPositionX, ballPositionY, overlappingBumperType
- ball_hard_respawn: + ballPositionX, ballPositionY
findBumperTypeAtPosition(pos): O(N) lookup среди fixed+setup bumpers
по координатам (с 1px tolerance). Возвращает «<source>:<type>» строку
для аналитики (e.g., «player_setup:curve» — самый частый stuck-spot
будет видно сразу при анализе аналитики).
Этого достаточно для будущего targeted fix без необходимости guess-tuning.
## #3 Known backlog
npm audit Vite/esbuild moderate — отложен до Phase 5 Vite 8 upgrade.
186/186 tests, typecheck/lint/build ✅.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -2,16 +2,25 @@
|
||||
// Extracted из Flipper.ts чтобы isPressed/cooldown логика была unit-тестируемой
|
||||
// без mocking Phaser tweens.
|
||||
//
|
||||
// State transitions:
|
||||
// idle (isPressed=false, cooldown expired)
|
||||
// → press(now) → pressed (isPressed=true)
|
||||
// → press during cooldown / already pressed → rejected
|
||||
// pressed
|
||||
// → release() → releasing (isPressed=false, tween в процессе)
|
||||
// → press → rejected (already pressed)
|
||||
// releasing
|
||||
// → onReleaseComplete(now) → cooldown started (cooldownUntil = now + cooldownMs)
|
||||
// → press(now) во время releasing → rejected (если в cooldown)
|
||||
// States and transitions:
|
||||
// idle (isPressed=false, cooldownUntil expired)
|
||||
// press(now) → pressed
|
||||
// press during cooldown → rejected
|
||||
//
|
||||
// pressed (isPressed=true)
|
||||
// release() → releasing
|
||||
// press → rejected (already pressed)
|
||||
//
|
||||
// releasing (isPressed=false, release-tween в процессе, cooldown ЕЩЁ НЕ активен)
|
||||
// press(now) → pressed (ALLOWED — responsive re-press до завершения tween;
|
||||
// cooldownUntil ещё старое значение, canPress=true)
|
||||
// onReleaseComplete(now) → cooldown starts (cooldownUntil = now + cooldownMs)
|
||||
//
|
||||
// cooldown (после onReleaseComplete до истечения cooldownMs)
|
||||
// press(now < cooldownUntil) → rejected
|
||||
// time passes → idle
|
||||
//
|
||||
// Re-press allowed во время releasing — design choice для responsive control.
|
||||
//
|
||||
// Flipper.ts wrapper'ит это с Phaser tweens + Matter body updates.
|
||||
|
||||
|
||||
@@ -584,17 +584,37 @@ export class MatchScene extends Phaser.Scene {
|
||||
matchId: this.matchId,
|
||||
attempt: this.stuckWatchdog.getNudgeAttempts(),
|
||||
hadOverlap: overlap !== null,
|
||||
// Position для hot-spot analysis — где конкретно ball застревает (codex backlog #1)
|
||||
ballPositionX: Math.round(bp.x),
|
||||
ballPositionY: Math.round(bp.y),
|
||||
overlappingBumperType: overlap?.body
|
||||
? this.findBumperTypeAtPosition(overlap.body.position)
|
||||
: null,
|
||||
matchTimeSeconds: (this.time.now - this.matchStartTime) / 1000,
|
||||
});
|
||||
}
|
||||
|
||||
/** Найти bumper type на позиции (для telemetry hot-spot analysis). */
|
||||
private findBumperTypeAtPosition(pos: { x: number; y: number }): string | null {
|
||||
for (const b of [...this.fixedBumpers, ...this.setupBumpers]) {
|
||||
const body = b.getBody();
|
||||
if (Math.abs(body.position.x - pos.x) < 1 && Math.abs(body.position.y - pos.y) < 1) {
|
||||
return `${b.source}:${b.type}`;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hard respawn — мяч действительно не разлипается после 3 nudge.
|
||||
* Уничтожаем тело, спавним из бокового порта в нейтральном цвете.
|
||||
*/
|
||||
private applyStuckHardRespawn(): void {
|
||||
const bp = this.ball.position;
|
||||
this.trackEvent('ball_hard_respawn', {
|
||||
matchId: this.matchId,
|
||||
ballPositionX: Math.round(bp.x),
|
||||
ballPositionY: Math.round(bp.y),
|
||||
matchTimeSeconds: (this.time.now - this.matchStartTime) / 1000,
|
||||
});
|
||||
this.ball.destroy();
|
||||
|
||||
Reference in New Issue
Block a user