Version 2.0.0: Animation fixes, timing improvements, and E2E test suite
Animation fixes: - Fix held card positioning bug (was appearing at bottom of page) - Fix discard pile blank/white flash on turn transitions - Fix blank card at round end by skipping animations during round_over/game_over - Set card content before triggering flip animation to prevent flash - Center suit symbol on 10 cards Timing improvements: - Reduce post-discard delay from 700ms to 500ms - Reduce post-swap delay from 1800ms to 1000ms - Speed up swap flip animation from 1150ms to 550ms - Reduce CPU initial thinking delay from 150-250ms to 80-150ms - Pause now happens after swap completes (showing result) instead of before E2E test suite: - Add Playwright-based test bot that plays full games - State parser extracts game state from DOM for validation - AI brain ports decision logic for automated play - Freeze detector monitors for UI hangs - Visual validator checks CSS states - Full game, stress, and visual test specs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,12 +12,15 @@ class AnimationQueue {
|
||||
this.animationInProgress = false;
|
||||
|
||||
// Timing configuration (ms)
|
||||
// Rhythm: action → settle → action → breathe
|
||||
this.timing = {
|
||||
flipDuration: 400,
|
||||
moveDuration: 300,
|
||||
pauseAfterMove: 200,
|
||||
pauseAfterFlip: 100,
|
||||
pauseBetweenAnimations: 100
|
||||
flipDuration: 540, // Must match CSS .card-inner transition (0.54s)
|
||||
moveDuration: 270,
|
||||
pauseAfterFlip: 144, // Brief settle after flip before move
|
||||
pauseAfterDiscard: 550, // Let discard land + pulse (400ms) + settle
|
||||
pauseBeforeNewCard: 150, // Anticipation before new card moves in
|
||||
pauseAfterSwapComplete: 400, // Breathing room after swap completes
|
||||
pauseBetweenAnimations: 90
|
||||
};
|
||||
}
|
||||
|
||||
@@ -159,21 +162,17 @@ class AnimationQueue {
|
||||
inner.classList.add('flipped');
|
||||
|
||||
// Step 1: If card was face down, flip to reveal it
|
||||
this.setCardFront(front, oldCard);
|
||||
if (!oldCard.face_up) {
|
||||
// Set up the front with the old card content (what we're discarding)
|
||||
this.setCardFront(front, oldCard);
|
||||
|
||||
this.playSound('flip');
|
||||
inner.classList.remove('flipped');
|
||||
await this.delay(this.timing.flipDuration);
|
||||
await this.delay(this.timing.pauseAfterFlip);
|
||||
} else {
|
||||
// Already face up, just show it
|
||||
this.setCardFront(front, oldCard);
|
||||
// Already face up, just show it immediately
|
||||
inner.classList.remove('flipped');
|
||||
}
|
||||
|
||||
await this.delay(100);
|
||||
|
||||
// Step 2: Move card to discard pile
|
||||
this.playSound('card');
|
||||
animCard.classList.add('moving');
|
||||
@@ -181,8 +180,8 @@ class AnimationQueue {
|
||||
await this.delay(this.timing.moveDuration);
|
||||
animCard.classList.remove('moving');
|
||||
|
||||
// Pause to show the card landing on discard
|
||||
await this.delay(this.timing.pauseAfterMove + 200);
|
||||
// Let discard land and pulse settle
|
||||
await this.delay(this.timing.pauseAfterDiscard);
|
||||
|
||||
// Step 3: Create second card for the new card coming into hand
|
||||
const newAnimCard = this.createAnimCard();
|
||||
@@ -197,6 +196,9 @@ class AnimationQueue {
|
||||
this.setCardFront(newFront, newCard);
|
||||
newInner.classList.remove('flipped');
|
||||
|
||||
// Brief anticipation before new card moves
|
||||
await this.delay(this.timing.pauseBeforeNewCard);
|
||||
|
||||
// Step 4: Move new card to the hand slot
|
||||
this.playSound('card');
|
||||
newAnimCard.classList.add('moving');
|
||||
@@ -204,8 +206,8 @@ class AnimationQueue {
|
||||
await this.delay(this.timing.moveDuration);
|
||||
newAnimCard.classList.remove('moving');
|
||||
|
||||
// Clean up animation cards
|
||||
await this.delay(this.timing.pauseAfterMove);
|
||||
// Breathing room after swap completes
|
||||
await this.delay(this.timing.pauseAfterSwapComplete);
|
||||
animCard.remove();
|
||||
newAnimCard.remove();
|
||||
}
|
||||
@@ -297,7 +299,8 @@ class AnimationQueue {
|
||||
await this.delay(this.timing.moveDuration);
|
||||
animCard.classList.remove('moving');
|
||||
|
||||
await this.delay(this.timing.pauseAfterMove);
|
||||
// Same timing as player swap - let discard land and pulse settle
|
||||
await this.delay(this.timing.pauseAfterDiscard);
|
||||
|
||||
// Clean up
|
||||
animCard.remove();
|
||||
@@ -322,17 +325,13 @@ class AnimationQueue {
|
||||
|
||||
// Move to holding position
|
||||
this.playSound('card');
|
||||
await this.delay(50);
|
||||
|
||||
animCard.classList.add('moving');
|
||||
this.setCardPosition(animCard, holdingRect);
|
||||
await this.delay(this.timing.moveDuration);
|
||||
animCard.classList.remove('moving');
|
||||
|
||||
// The card stays face down until the player decides what to do
|
||||
// (the actual card reveal happens when server sends card_drawn)
|
||||
|
||||
await this.delay(this.timing.pauseAfterMove);
|
||||
// Brief settle before state updates
|
||||
await this.delay(this.timing.pauseBeforeNewCard);
|
||||
|
||||
// Clean up - renderGame will show the holding card state
|
||||
animCard.remove();
|
||||
|
||||
Reference in New Issue
Block a user