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

280 lines
7.7 KiB
Markdown

# 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):
```javascript
// 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:
```javascript
// 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:
```javascript
// 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:
```css
/* 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
```javascript
// 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