
breath-sync-validator
[DEPRECATED] This skill has been consolidated into breathing-sync. Use breathing-sync Mode 2 to vali
⚠️ DEPRECATED SKILL
This skill has been consolidated into breathing-sync.
⚠️ OUTDATED DOCUMENTATION WARNING
The documentation below contains outdated information:
- References 16-second box breathing (current: 19-second 4-7-8 relaxation breathing)
- References sphereScale and crystallization traits (these no longer exist)
- See
../../reference/core-concepts.mdfor current breathing cycle details - See
src/entities/breath/traits.tsxfor current trait list
For current, accurate documentation, use the breathing-sync skill instead.
Migration Guide
To validate/debug breathing synchronization: Use the breathing-sync skill and select Mode 2 (Validate)
What's Preserved
- ✅ All 8-point validation checks preserved in breathing-sync Mode 2
- ✅ Same phase-by-phase behavior analysis
- ✅ Same troubleshooting checklist
- ✅ No capability loss
No Action Required
If you're currently using this skill, no changes are needed. It will continue to work. However, for future breathing validation work, use breathing-sync Mode 2 instead.
Original Documentation
See: .claude/skills/breathing-sync/SKILL.md (Mode 2: Validate)
Overview
This skill validates and debugs why an entity might not be visibly responding to the breathing synchronization cycle in breathe-together-v2.
Use this when:
- An entity is supposed to animate with breathing but appears static
- Visual response is too subtle (barely perceptible)
- Response is jerky or delayed
- Response is opposite what you expect
- You're integrating a new breathing-synchronized entity
Expected output: A detailed validation report identifying integration issues and specific recommendations with file:line references.
Quick Start Interview
1. Entity Name
What entity are you validating? (e.g., "ParticleSystem", "CustomGlow", "BreathingSphere")
2. Problem Type
Which best describes the issue?
- No visible response — Entity doesn't animate with breathing at all
- Subtle response — Entity animates but changes are barely perceptible
- Jerky/stuttering — Animation is choppy or skips
- Delayed response — Animation lags behind breathing cycle
- Opposite behavior — Animates backwards (expands on exhale instead of inhale)
3. Expected Behavior
What should happen during INHALE (0-4s)?
- Size/scale should [increase/decrease/change]
- Position should [move inward/outward/upward]
- Color should [shift toward X color]
- [Other property] should [do something]
Systematic Validation (8 Checks)
✅ Check 1: Breath Entity Spawned
File to inspect: src/app.tsx, src/levels/breathing.tsx, or your scene component
What to look for:
// Should find somewhere:
<BreathEntity />
Why: Without BreathEntity, no global breath state exists for other systems to read.
If missing:
- Open your scene component (likely
src/levels/breathing.tsxorsrc/app.tsx) - Add
<BreathEntity />to the component tree - Verify it's inside the same
<KootaWorld>as your entity
Reference: src/entities/breath/index.tsx — BreathEntity component
✅ Check 2: Breath System Registered & Enabled
File to inspect: src/providers.tsx (lines 49-120, inside KootaSystems component)
What to look for:
// Should find around line 63:
if (breathSystemEnabled) {
breathSystem(world, delta); // ← MUST RUN FIRST
}
Why: breathSystem computes the global breath state (breathPhase, orbitRadius, sphereScale, crystallization) that all other systems depend on. It MUST run in Phase 1 (LOGIC phase).
If missing or disabled:
- Open
src/providers.tsx - Find
KootaSystemscomponent - Verify
breathSystemis called in the LOGIC phase (first phase) - Verify
breathSystemEnabledprop is true - Never reorder phases — system execution order is critical
Reference: src/entities/breath/systems.tsx — breathSystem function definition
✅ Check 3: Entity Queries Breath Traits
File to inspect: src/entities/[yourEntity]/index.tsx or src/entities/[yourEntity]/systems.tsx
What to look for: Your entity's system should query at least one of:
breathPhase— Position within current phase (0-1)orbitRadius— Particle orbit radius (inverse to sphere scale)sphereScale— Central sphere sizecrystallization— Stillness effect during holds
Pattern 1: Direct calculation in component
import { calculateBreathState } from '@/lib/breathCalc';
useFrame(() => {
const breathState = calculateBreathState(Date.now());
// Use breathState.breathPhase, .phaseType, etc.
});
Pattern 2: Query in ECS system
export function mySystem(world: World, delta: number) {
// Query breath data
const breathEntity = world.queryFirst(breathPhase, orbitRadius, sphereScale);
if (!breathEntity) return;
const breath = breathEntity.get(breathPhase);
// Use breath.value or breath.targetValue
// Query your entity
const entities = world.query([MyTrait]);
entities.forEach(entity => {
// Use breath data to update entity
});
}
Why: Your entity can't respond to breathing if it's not reading breath data.
If missing:
- Open your entity's system file
- Add query for breath traits
- Use the breath data to calculate visual values
- See
src/entities/particle/systems.tsxfor a complete example
Reference:
src/lib/breathCalc.ts— calculateBreathState() functionsrc/entities/breath/traits.tsx— BreathPhase trait definition
✅ Check 4: Entity System Registered & Enabled
File to inspect: src/providers.tsx (inside KootaSystems component)
What to look for:
// Should find somewhere (usually Phase 2-6):
if (myEntitySystemEnabled) {
myEntitySystem(world, delta);
}
Why: Without the system running each frame, breath data won't be applied to your entity.
Phase guidelines:
- Phase 1 (LOGIC): breathSystem only
- Phase 2 (PHYSICS): particlePhysicsSystem, velocity-based systems
- Phase 3 (INPUT): cursorPositionFromLand
- Phase 4 (FORCES): velocityTowardsTarget, acceleration systems
- Phase 5 (INTEGRATION): positionFromVelocity
- Phase 6 (RENDER SYNC): meshFromPosition, visual updates
- Phase 7 (CAMERA): cameraFollowFocused
If missing or wrong phase:
- Find where your system is called in
KootaSystems - Verify it's in the correct phase
- Verify the enabled condition is true (or exists with correct name)
- Systems in later phases have stale breath data — prefer early phases
Reference: src/providers.tsx:49-120 — Complete system execution order
✅ Check 5: Visual Parameter Ranges
File to inspect: Entity config file or component props
What to look for: Search for parameters that control visual intensity:
// In particle config:
size: {
base: 0.1,
breathPulseIntensity: 0.3, // ← Too low? (should be > 0.5)
}
// In mesh scaling:
const scale = 1 + breathState.breathPhase * 0.1; // ← Too small? (should be 0.5+)
// In color shift:
const hue = 200 + breathState.breathPhase * 5; // ← Too subtle? (should be 20+)
Visibility thresholds:
- Pulse/scale intensity: < 0.5 (50%) is usually invisible → aim for 1.0+
- Orbit radius delta: < 1.0 unit is subtle → aim for 1.5+
- Opacity changes: < 0.2 (20%) is barely visible → aim for 0.3+
- Color shift: < 20 hue degrees is subtle → aim for 30+
If ranges are too small:
- Find the parameter definition
- Increase the intensity/range
- Test by running
npm run devand watching the entity during a breathing cycle - Aim for "clearly visible but not garish" — about 30-50% of max intensity
Reference:
src/entities/particle/config.ts— Particle visual parameterssrc/entities/breathingSphere/index.tsx— Working scale example
✅ Check 6: Damping & Spring Constants
File to inspect: src/entities/breath/systems.tsx and any physics-related systems
What to look for: Search for damping or spring speeds applied to breath-related traits:
// In breathSystem damping:
{ trait: breathPhase, targetTrait: targetBreathPhase, speed: 0.1 }, // ← Too slow!
Damping impact:
- speed: 0.05-0.1 — Very slow, heavily smoothed (hides visual response)
- speed: 0.2-0.3 — Moderate, balanced smoothing
- speed: 0.5+ — Fast, minimal smoothing (more responsive)
Why high damping hides response: A damping speed of 0.1 means it takes ~10 frames to reach the target. At 60fps, that's 166ms of lag — the entire breath phase is over.
If damping is too high:
- Locate the damping configuration
- Increase the speed value (0.1 → 0.3 → 0.5)
- Test responsiveness — increased speed = faster visual response
- Balance: too fast = jittery, too slow = invisible
Note on physics damping: Separate from breath damping. Check particle physics drag/damping too.
Reference:
src/entities/breath/systems.tsx— breath phase dampingsrc/entities/particle/systems.tsx— particle physics damping
✅ Check 7: Adaptive Quality Disabling
File to inspect: Your entity implementation or quality context
What to look for: Check if adaptive quality might be disabling your entity in low-quality mode:
// In ParticleSpawner (example):
active({ value: i < activeCount }), // ← This might be 0 if activeCount is 0
In QualityContext, check:
- Low quality might reduce particle count to 0
- Low quality might disable complex visual effects
- Check what happens to
activeCountin low quality
If quality is disabling your entity:
- Open your entity component
- Check for quality-based conditions
- Temporarily override:
active({ value: true })to test - If entity appears with override, quality is the issue
- Adjust quality thresholds in
src/contexts/QualityContext.tsx
Reference:
src/contexts/QualityContext.tsx— Quality settingssrc/components/QualitySettings.tsx— Quality UIsrc/entities/particle/index.tsx:ParticleSpawner— Particle quality handling
✅ Check 8: Frame Update Frequency
File to inspect: Component (useFrame) or system (system loop)
What to look for: Verify your entity updates every frame:
In components:
useFrame(() => {
// ← This runs every frame
const breathState = calculateBreathState(Date.now());
// Apply breathState to entity
});
In systems:
export function mySystem(world: World, delta: number) {
// ← This is called every frame by KootaSystems
const entities = world.query([MyTrait]);
entities.forEach(entity => {
// Update entity
});
}
Common issues:
useFramenot used → entity only updates when props change- System not registered → not called every frame
- Conditional update → skips frames under certain conditions
If update frequency is wrong:
- Component-based: add
useFrame(() => { ... }) - System-based: verify system is registered in
KootaSystems - Remove any frame-skipping conditions
- Test: entity should animate smoothly every frame
Reference:
src/entities/particle/index.tsx:ParticleRenderer— Good useFrame examplesrc/entities/particle/systems.tsx— System-based update example
Phase-by-Phase Behavior Analysis
For each breathing phase, verify your entity responds as expected.
Phase 0: INHALE (0-4s)
Time range: 0-4000ms into 16-second cycle
Expected breath state:
breathPhase: 0 → 1 (progress from 0 to 100%)
phaseType: 0 (INHALE)
rawProgress: 0 → 1 (linear)
easedProgress: 0 → 1 (eased - fast start, slow end)
orbitRadius: 2.8 → 1.2 (particles contract)
sphereScale: 0.6 → 1.4 (sphere expands)
crystallization: 0 (no stillness effect)
What your entity SHOULD do:
- ✅ Size/scale increases (if using breathPhase directly)
- ✅ Position moves inward (if using orbitRadius - particles)
- ✅ Color shifts toward "inhale" color (if phaseType-based)
- ✅ Emission/glow increases (if using breathing)
- ✅ Movement is smooth and accelerates
To verify:
- Run
npm run dev - Watch your entity from 0-4s of the breathing cycle
- You should see clear expansion or movement
- If nothing visible: Check Checks 1-6 above
Visual examples:
- Sphere: grows from 0.6 to 1.4 scale (230% increase)
- Particles: move from 2.8 to 1.2 units from center (57% closer)
- Custom entity: your min→max range based on breathPhase
Phase 1: HOLD-IN (4-8s)
Time range: 4000-8000ms into 16-second cycle
Expected breath state:
breathPhase: ~1.0 (stays at peak of inhale)
phaseType: 1 (HOLD-IN)
orbitRadius: ~1.2 (particles stay contracted)
sphereScale: ~1.4 (sphere stays expanded)
crystallization: 0.5 → 0.9 (increases during hold)
What your entity SHOULD do:
- ✅ Size/scale stays constant (or uses crystallization)
- ✅ Position stays constant (particles don't move)
- ✅ Movement velocity decreases (if damped by crystallization)
- ✅ Wind/turbulence reduces (calmer motion)
- ✅ Opacity/blur may increase (stillness effect)
To verify:
- Watch your entity from 4-8s of the cycle
- Should see less movement than INHALE phase
- Should feel "held" or "still"
- If entity still moving wildly: Check dampingconstants (Check 6)
Crystallization effect: If your entity uses crystallization, apply like:
const crystal = breathState.crystallization;
const opacity = 0.5 + crystal * 0.5; // Fades during hold
const blur = crystal * 5; // Blurs during hold
const jitter = 1 - crystal * 0.8; // Jitter reduces
Phase 2: EXHALE (8-12s)
Time range: 8000-12000ms into 16-second cycle
Expected breath state:
breathPhase: 1 → 0 (progress from 100% to 0%)
phaseType: 2 (EXHALE)
rawProgress: 0 → 1 (linear)
easedProgress: 0 → 1 (eased - slow start, fast end)
orbitRadius: 1.2 → 2.8 (particles expand)
sphereScale: 1.4 → 0.6 (sphere shrinks)
crystallization: 0 (no stillness effect)
What your entity SHOULD do:
- ✅ Size/scale decreases (opposite of INHALE)
- ✅ Position moves outward (if using orbitRadius - particles)
- ✅ Color shifts toward "exhale" color (if phaseType-based)
- ✅ Emission/glow decreases (calmer)
- ✅ Movement is smooth and accelerates (like INHALE but reversed)
To verify:
- Watch your entity from 8-12s of the cycle
- Should see clear contraction or outward movement
- Should be mirror of INHALE phase
- If behavior is inverted: Check scale/range calculations
Common mistake: If entity contracts during INHALE and expands during EXHALE (opposite), check:
// ❌ Wrong (inverted):
const scale = max - breathState.breathPhase * range;
// ✅ Right:
const scale = min + breathState.breathPhase * range;
Phase 3: HOLD-OUT (12-16s)
Time range: 12000-16000ms into 16-second cycle
Expected breath state:
breathPhase: ~0.0 (stays at low point of exhale)
phaseType: 3 (HOLD-OUT)
orbitRadius: ~2.8 (particles stay expanded)
sphereScale: ~0.6 (sphere stays small)
crystallization: 0.4 → 0.75 (increases during hold)
What your entity SHOULD do:
- ✅ Size/scale stays constant (at minimum)
- ✅ Position stays constant (particles don't move)
- ✅ Movement velocity decreases (crystallization)
- ✅ Wind/turbulence reduces (very calm)
- ✅ Color very faded (deep stillness - if phaseType-based)
- ✅ Opacity very low (if crystallization-based)
To verify:
- Watch your entity from 12-16s of the cycle
- Should be opposite of HOLD-IN (holding at exhale instead of inhale)
- Should be very still and calm
- If entity is active: Check crystallization dampening
Then it repeats: At 16s, cycle returns to 0ms and INHALE starts again.
Common Issues & Fixes
Issue 1: No Visible Response at All
Symptoms:
- Entity doesn't animate at all
- Looks completely static across all 4 phases
- Breathing is happening but entity ignores it
Root causes (in order of likelihood):
Cause A: Breath Entity not spawned
- Check: Is
<BreathEntity />in your scene? (Check 1) - Fix: Add
<BreathEntity />tosrc/levels/breathing.tsx
Cause B: System not registered
- Check: Is your system in
src/providers.tsx? (Check 4) - Fix: Add system to
KootaSystemscomponent
Cause C: Not querying breath traits
- Check: Does your system/component read breath data? (Check 3)
- Fix: Add breath trait query and use the data
Cause D: System disabled
- Check: Is
systemEnabledcondition true? (Check 4) - Fix: Check component props, verify
enabled={true}
Debug steps:
- Add a console.log in your system to verify it's running
- Add a console.log to print breath data
- Verify both print every frame
- If one doesn't: go back to its check
Issue 2: Very Subtle Response (Barely Visible)
Symptoms:
- Entity does animate but changes are hard to see
- Have to look very closely to notice breathing
- Might look like nothing's happening at normal viewing distance
Root causes (in order of likelihood):
Cause A: Pulse intensity too low
- Check: Visual parameter ranges (Check 5)
- Example:
breathPulseIntensity: 0.3(30%) is barely visible - Fix: Increase to
0.8or1.0(80-100%)
Cause B: Damping too heavy
- Check: Damping speed constants (Check 6)
- Example:
speed: 0.1smooths changes over 166ms - Fix: Increase speed to
0.3or0.5
Cause C: Parameter range too small
- Check: Min/max values in visual calculation (Check 5)
- Example:
scale = 1 + breathPhase * 0.1only ranges 1.0-1.1 (10% change) - Fix: Increase multiplier:
scale = 1 + breathPhase * 0.5(50% change)
Cause D: Physics drag hiding motion
- Check: Particle physics damping (Check 6)
- Example: Particle physics might have separate drag smoothing orbit radius changes
- Fix: Reduce particle physics damping or increase breath effect intensity
Debug steps:
- Increase parameter range by 5x and test
- If entity suddenly visible: issue was parameter range
- Reduce damping speed and test
- If entity becomes jerky but more visible: issue was damping
Issue 3: Jerky/Stuttering Response
Symptoms:
- Animation skips or stutters
- Not smooth over the 4-second phase
- Looks choppy or discontinuous
Root causes:
Cause A: Not updating every frame
- Check: useFrame or system loop (Check 8)
- Fix: Add useFrame if missing, verify system is called each frame
Cause B: Using rawProgress instead of easedProgress
- Check: Which progress value are you using?
- Example:
rawProgressis linear (constant speed) - Fix: Use
easedProgressfor smooth acceleration/deceleration - Or add easing yourself:
easeInOutQuad(rawProgress)
Cause C: Spring/damping too light
- Check: Spring constant (not damping speed, opposite)
- If you have a spring force, it might be too weak
- Fix: Increase spring stiffness or reduce damping
Debug steps:
- Verify useFrame is called every frame
- Switch from rawProgress to easedProgress
- Test both and compare smoothness
Issue 4: Delayed Response
Symptoms:
- Entity animations lag behind breathing
- Takes a moment to respond to breathing changes
- 1-2 second delay noticed
Root causes:
Cause A: Damping too heavy
- Check: Damping speed constant (Check 6)
- Example:
speed: 0.05creates 200+ms lag - Fix: Increase speed to
0.3or0.5
Cause B: System runs too late
- Check: Which phase your system runs in (Check 4)
- Running in Phase 6 vs Phase 2 = 4 phases of delay (~67ms per phase)
- Fix: Move system to earlier phase (Phase 2-3 preferred)
Cause C: Using cached/stale breath data
- Check: How do you get breath state?
- Example: Storing breath value in state and not updating every frame
- Fix: Call
calculateBreathState(Date.now())directly in useFrame/system
Debug steps:
- Reduce damping speed and test
- Check what phase your system runs in
- Move system to Phase 2 if possible
- Verify you're reading fresh breath data (Date.now())
Issue 5: Response Opposite Expected
Symptoms:
- Entity expands during exhale instead of inhale
- Particles move outward when they should move inward
- All behavior reversed
Root causes:
Cause A: Inverse calculation wrong
- Check: Min/max or formula (Check 5)
- Example:
max - breathPhase * rangeinstead ofmin + breathPhase * range - Fix: Reverse the formula or invert breathPhase:
1 - breathPhase
Cause B: Using wrong trait value
- Check: Which breath trait are you reading? (Check 3)
- Particles should use
orbitRadius(inverse) - Sphere should use
sphereScale(direct) - Fix: Use correct trait for your entity type
Cause C: Phase type condition inverted
- Check: Phase-based logic (Check 5)
- Example:
if (phaseType === 2)means EXHALE, not INHALE - Fix: Verify phase numbers: 0=INHALE, 1=HOLD-IN, 2=EXHALE, 3=HOLD-OUT
Debug steps:
- During INHALE, manually check what breathPhase value is
- Verify your calculation produces expected min/max
- Check which trait you're reading (orbitRadius vs breathPhase)
- Add console.log for both before/after calculation
Validation Report Template
After performing all 8 checks and phase analysis, generate a report:
# Breath Sync Validation Report: [EntityName]
## Summary
✅ All checks passed / ⚠️ Issues found: [count]
## Integration Checks
- [✅/❌] Check 1: Breath entity spawned
- [✅/❌] Check 2: Breath system registered & enabled
- [✅/❌] Check 3: Entity queries breath traits
- [✅/❌] Check 4: Entity system registered & enabled
- [✅/❌] Check 5: Visual parameter ranges adequate
- [✅/❌] Check 6: Damping constants reasonable
- [✅/❌] Check 7: Adaptive quality not disabling entity
- [✅/❌] Check 8: Entity updates every frame
## Parameter Analysis
- **Pulse/scale intensity:** [value] (Recommended: > 0.5 for visibility)
- **Orbit radius delta:** [value] units (Recommended: > 1.0)
- **Damping speed:** [value] (Recommended: > 0.2 for responsiveness)
- **Quality impact:** [none/low/medium/high]
## Phase Behavior Analysis
### Phase 0: INHALE (0-4s)
**Expected:** [what should happen]
**Actual code:** [what entity does]
**Status:** ✅ Correct / ⚠️ Issue
### Phase 1: HOLD-IN (4-8s)
**Expected:** [stillness, crystallization increases]
**Actual code:** [what entity does]
**Status:** ✅ Correct / ⚠️ Issue
### Phase 2: EXHALE (8-12s)
**Expected:** [reverse of INHALE]
**Actual code:** [what entity does]
**Status:** ✅ Correct / ⚠️ Issue
### Phase 3: HOLD-OUT (12-16s)
**Expected:** [stillness at exhale point]
**Actual code:** [what entity does]
**Status:** ✅ Correct / ⚠️ Issue
## Recommendations
1. [Specific fix with file:line reference]
2. [Another fix with impact assessment]
3. [Debug suggestion or test to verify]
## Code Locations
- Entity component: `[file:path]`
- System: `[file:path]`
- Config/traits: `[file:path]`
- Registration: `src/providers.tsx:line`
## Next Steps
1. Apply recommendations 1-3
2. Rebuild and test with `npm run dev`
3. Verify entity animates smoothly across all 4 phases
4. If issues persist, re-run checks on modified files
Reference & Further Reading
See the following for comprehensive details:
- reference.md — Complete breath system architecture and API
- examples.md — Real validation reports and fixes
- checklist.md — Quick reference for manual validation
Tips for Success
- Start with a working baseline — Compare unknown entity to BreathingSphere (proven to work)
- Check in order — Don't skip checks; missing Check 1 wastes time debugging Check 8
- Use console.log liberally — Print breath state and entity values every frame to debug
- Test at 60fps — Run
npm run devwith DevTools performance profiling - Increase visibility — Temporarily increase parameter ranges 5-10x to test integration
- Compare to BreathingSphere — It's the canonical working example (see
src/entities/breathingSphere/index.tsx)
Let's validate and debug your breathing synchronization! 🫁