golfgame/docs/v3/V3_12_DRAW_SOURCE_DISTINCTION.md
adlee-was-taken 9fc6b83bba v3.0.0: V3 features, server refactoring, and documentation overhaul
- Extract WebSocket handlers from main.py into handlers.py
- Add V3 feature docs (dealer rotation, dealing animation, round end reveal,
  column pair celebration, final turn urgency, opponent thinking, score tallying,
  card hover/selection, knock early drama, column pair indicator, swap animation
  improvements, draw source distinction, card value tooltips, active rules context,
  discard pile history, realistic card sounds)
- Add V3 refactoring docs (ai.py, main.py/game.py, misc improvements)
- Add installation guide with Docker, systemd, and nginx setup
- Add helper scripts (install.sh, dev-server.sh, docker-build.sh)
- Add animation flow diagrams documentation
- Add test files for handlers, rooms, and V3 features
- Add e2e test specs for V3 features
- Update README with complete project structure and current tech stack
- Update CLAUDE.md with full architecture tree and server layer descriptions
- Update .env.example to reflect PostgreSQL (remove SQLite references)
- Update .gitignore to exclude virtualenv files, .claude/, and .db files
- Remove tracked virtualenv files (bin/, lib64, pyvenv.cfg)
- Remove obsolete game_log.py (SQLite) and games.db

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 10:03:45 -05:00

7.7 KiB

V3-12: Draw Source Distinction

Overview

Drawing from the deck (face-down, unknown) vs discard (face-up, known) should feel different. Currently both animations are similar. This feature enhances the visual distinction.

Dependencies: None Dependents: None


Goals

  1. Deck draw: Card emerges face-down, then flips
  2. Discard draw: Card lifts straight up (already visible)
  3. Different sound for each source
  4. Visual hint about the strategic difference
  5. Help new players understand the two options

Current State

From card-animations.js (CardAnimations class):

// Deck draw: suspenseful pause + flip reveal
animateDrawDeck(cardData, onComplete) {
    // Pulse deck, lift card face-down, move to holding, suspense pause, flip
    timeline.add({ targets: inner, rotateY: 0, duration: 245 });
}

// Discard draw: quick decisive grab
animateDrawDiscard(cardData, onComplete) {
    // Pulse discard, quick lift, direct move to holding (no flip needed)
    timeline.add({ targets: animCard, translateY: -12, scale: 1.05, duration: 42 });
}

The distinction exists and is already fairly pronounced. This feature enhances it further with:

  • More distinct sounds for each source
  • Visual "shuffleDeckVisual" effect when drawing from deck
  • Better timing contrast

Design

Deck Draw (Unknown)

1. Deck "shuffles" slightly (optional)
2. Top card lifts off deck
3. Card floats to holding position (face-down)
4. Brief suspense pause
5. Card flips to reveal
6. Sound: "mysterious" flip sound

Discard Draw (Known)

1. Card lifts directly (quick)
2. No flip needed - already visible
3. Moves to holding position
4. "Picked up" visual on discard pile
5. Sound: quick "pick" sound

Visual Distinction

Aspect Deck Draw Discard Draw
Card state Face-down → Face-up Face-up entire time
Motion Float + flip Direct lift
Sound Suspenseful flip Quick pick
Duration Longer (suspense) Shorter (decisive)
Deck visual Cards shuffle N/A
Discard visual N/A "Picked up" state

Implementation

Enhanced Deck Draw

The existing animateDrawDeck() in card-animations.js already has most of this functionality. Enhancements to add:

// In card-animations.js - enhance existing animateDrawDeck

// The current implementation already:
// - Pulses deck before drawing (startDrawPulse)
// - Lifts card with wobble
// - Adds suspense pause before flip
// - Flips to reveal with sound

// Add distinct sound for deck draws:
animateDrawDeck(cardData, onComplete) {
    // ... existing code ...

    // Change sound from 'card' to 'draw-deck' for more mysterious feel
    this.playSound('draw-deck');  // Instead of 'card'

    // ... rest of existing code ...
}

// The shuffleDeckVisual already exists as startDrawPulse:
startDrawPulse(element) {
    if (!element) return;
    element.classList.add('draw-pulse');
    setTimeout(() => {
        element.classList.remove('draw-pulse');
    }, 450);
}

Key existing features:

  • startDrawPulse() - gold ring pulse effect
  • Suspense pause of 200ms before flip
  • Flip duration 245ms with easeInOutQuad easing

Enhanced Discard Draw

The existing animateDrawDiscard() in card-animations.js already has quick, decisive animation:

// Current implementation already does:
// - Pulses discard before picking up (startDrawPulse)
// - Quick lift (42ms) with scale
// - Direct move (126ms) - much faster than deck draw
// - No flip needed (card already face-up)

// Enhancement: Add distinct sound for discard draws
_animateDrawDiscardCard(cardData, discardRect, holdingRect, onComplete) {
    // ... existing code ...

    // Change sound from 'card' to 'draw-discard' for decisive feel
    this.playSound('draw-discard');  // Instead of 'card'

    // ... rest of existing code ...
}

Current timing comparison (already implemented):

Phase Deck Draw Discard Draw
Pulse delay 250ms 200ms
Lift 105ms 42ms
Travel 175ms 126ms
Suspense 200ms 0ms
Flip 245ms 0ms
Settle 150ms 80ms
Total ~1125ms ~448ms

The distinction is already pronounced - discard draw is ~2.5x faster.

Deck Visual Effects

The draw-pulse class already exists with a CSS animation (gold ring expanding). For additional deck depth effect, use CSS only:

/* Deck "depth" visual - multiple card shadows */
#deck {
    box-shadow:
        1px 1px 0 0 rgba(0, 0, 0, 0.1),
        2px 2px 0 0 rgba(0, 0, 0, 0.1),
        3px 3px 0 0 rgba(0, 0, 0, 0.1),
        4px 4px 8px rgba(0, 0, 0, 0.3);
}

/* Existing draw-pulse animation handles the visual feedback */
.draw-pulse {
    /* Already defined in style.css */
}

Distinct Sounds

// In playSound() method

} else if (type === 'draw-deck') {
    // Mysterious "what's this?" sound
    const osc = ctx.createOscillator();
    const gain = ctx.createGain();
    osc.connect(gain);
    gain.connect(ctx.destination);

    osc.type = 'triangle';
    osc.frequency.setValueAtTime(300, ctx.currentTime);
    osc.frequency.exponentialRampToValueAtTime(500, ctx.currentTime + 0.1);
    osc.frequency.exponentialRampToValueAtTime(350, ctx.currentTime + 0.15);

    gain.gain.setValueAtTime(0.08, ctx.currentTime);
    gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.2);

    osc.start(ctx.currentTime);
    osc.stop(ctx.currentTime + 0.2);

} else if (type === 'draw-discard') {
    // Quick decisive "grab" sound
    const osc = ctx.createOscillator();
    const gain = ctx.createGain();
    osc.connect(gain);
    gain.connect(ctx.destination);

    osc.type = 'square';
    osc.frequency.setValueAtTime(600, ctx.currentTime);
    osc.frequency.exponentialRampToValueAtTime(300, ctx.currentTime + 0.05);

    gain.gain.setValueAtTime(0.08, ctx.currentTime);
    gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.06);

    osc.start(ctx.currentTime);
    osc.stop(ctx.currentTime + 0.06);
}

Timing Comparison

Phase Deck Draw Discard Draw
Lift 150ms 80ms
Travel 250ms 200ms
Suspense 200ms 0ms
Flip 350ms 0ms
Settle 150ms 80ms
Total ~1100ms ~360ms

Deck draw is intentionally longer to build suspense.


Test Scenarios

  1. Draw from deck - Longer animation with flip
  2. Draw from discard - Quick decisive grab
  3. Rapid alternating draws - Animations don't conflict
  4. CPU draws - Same visual distinction

Acceptance Criteria

  • Deck draw has suspenseful pause before flip
  • Discard draw is quick and direct
  • Different sounds for each source
  • Deck shows visual "dealing" effect
  • Timing difference is noticeable but not tedious
  • Both animations complete cleanly
  • Works for both local player and opponents

Implementation Order

  1. Add distinct sounds to playSound()
  2. Enhance animateDrawDeck() with suspense
  3. Enhance animateDrawDiscard() for quick grab
  4. Add deck visual effects (CSS)
  5. Add shuffleDeckVisual() method
  6. Test both draw types
  7. Tune timing for feel

Notes for Agent

  • Most of this is already implemented in card-animations.js
  • Main enhancement is adding distinct sounds (draw-deck vs draw-discard)
  • The existing timing difference (1125ms vs 448ms) is already significant
  • Deck draw suspense shouldn't be annoying, just noticeable
  • Discard draw being faster reflects the strategic advantage (you know what you're getting)
  • Consider: Show deck count visual changing? (Nice to have)
  • Sound design matters here - different tones communicate different meanings
  • Mobile performance should still be smooth