Hands-on debugging workflows for investigating live bugs in the card battle game. Covers debug instrumentation, state inspection, diagnostic trees, and step-by-step domain-specific debugging procedures. Use for "debug why X happens", "investigate bug", "trace issue", "find root cause" requests. 使用時: 「debugging-activeを使用します」
Hands-on investigation workflows for tracing live bugs to their root cause. Complements debugging-error-prevention (which covers prevention patterns) by providing operational debugging procedures.
Create src/utils/debugLogger.ts during investigation, remove after fix is confirmed.
type DebugCategory =
| 'battle' | 'buff' | 'damage' | 'phase'
| 'inventory' | 'dungeon' | 'save'
| 'context' | 'element' | 'summon';
const ENABLED: Set<DebugCategory> = new Set(['battle', 'buff']); // Toggle per investigation
export function dbg(category: DebugCategory, label: string, data?: unknown) {
if (import.meta.env.DEV && ENABLED.has(category)) {
console.log(
`%c[${category.toUpperCase()}]%c ${label}`,
'color: #f59e0b; font-weight: bold',
'color: inherit',
data ?? ''
);
}
}
export function dbgTable(category: DebugCategory, label: string, rows: Record<string, unknown>[]) {
if (import.meta.env.DEV && ENABLED.has(category)) {
console.log(`%c[${category.toUpperCase()}]%c ${label}`, 'color: #f59e0b; font-weight: bold', 'color: inherit');
console.table(rows);
}
}
| Category | File | Function / Hook | What to Log |
|---|---|---|---|
battle | useBattleOrchestrator.ts | Top-level orchestrator return | Composed state snapshot |
phase | useBattlePhase.ts | advancePhase() | Phase queue before/after advance |
phase | phaseCalculation.ts | generatePhaseQueue() | Actor speeds, randomness, resulting order |
damage | damageCalculation.ts | calculateDamage() | Attacker/defender stats, modifiers, result |
buff | buffCalculation.ts | attackBuffDebuff() | Active buffs, computed multiplier |
buff | executeCharacterManage.ts | Phase start/end buff tick | Buff map before/after duration decrement |
element | elementalSystem.ts | onCardPlay() | Resonance level change, triggered effect |
summon | useSummonSystem.ts | onCardPlayed() / onTurnStart | Active summons, decay, expiry |
inventory | InventoryContext.tsx | moveItem() | Direction, source/dest counts, capacity check |
dungeon | dungeonLogic.ts | completeNode() | Node result, run state update |
save | saveManager.ts | save() / load() | Data size, version, success/error |
context | PlayerContext.tsx | decreaseLives() | Lives before/after, isGameOver |
context | ResourceContext.tsx | useGold() / addGold() | Gold before/after, transaction amount |
Paste into browser console to inspect live state:
// React DevTools hook — works in dev builds
const fiber = document.getElementById('root')?._reactRootContainer?._internalRoot?.current;
// Alternative: use React DevTools Components tab
// Select a component → check hooks panel for context values
When debugging a battle issue, collect ALL of the following from useBattleOrchestrator:
[ ] playerHp, playerMaxHp, playerAp, playerMaxAp
[ ] playerGuard value
[ ] playerBuffDebuffs (Map — use inspectBuffMap below)
[ ] enemies array — each enemy's hp, maxHp, buffDebuffs
[ ] currentPhase, phaseQueue contents
[ ] deck count, hand cards, discard pile count
[ ] classAbilityState (energy / resonance / summons)
[ ] battleResult (null during battle, set at end)
Buff state uses Map<string, BuffDebuffState>. Console shows Map(N) unhelpfully. Use:
function inspectBuffMap(buffs: Map<string, BuffDebuffState>): void {
const rows = Array.from(buffs.entries()).map(([key, b]) => ({
key,
type: b.type,
value: b.value,
duration: b.duration,
appliedBy: b.appliedBy,
}));
console.table(rows);
}
function checkInventoryIntegrity(state: InventoryState): string[] {
const issues: string[] = [];
if (state.items.length > state.itemCapacity) {
issues.push(`Items overflow: ${state.items.length}/${state.itemCapacity}`);
}
if (state.equipment.length > state.equipmentCapacity) {
issues.push(`Equipment overflow: ${state.equipment.length}/${state.equipmentCapacity}`);
}
// Check for duplicate IDs
const ids = state.items.map(i => i.id);
const dupes = ids.filter((id, i) => ids.indexOf(id) !== i);
if (dupes.length > 0) {
issues.push(`Duplicate item IDs: ${dupes.join(', ')}`);
}
return issues;
}
Buff duration not decreasing:
buff.appliedBy — duration only ticks during the applier's phaseexecuteCharacterManage.ts → calculatePlayerPhaseStart/End or calculateEnemyPhaseStart/End — is the decrement logic running?type matches the decrement filter (some buff types may be excluded)Wrong damage output:
damageCalculation.ts → calculateDamage() — check base ATK, DEF valuesbuffCalculation.ts → attackBuffDebuff() and defenseBuffDebuff() — verify multiplierscriticalRateBuff())reflectBuff()) — damage might be redirectedelementalSystem.ts if mage classPhase order wrong / stuck:
phaseCalculation.ts → generatePhaseQueue() — check actor speedsapplySpeedRandomness() — mean-reversion variance may produce unexpected orderadvancePhase() is called — check if awaiting animation completionEnemy targeting wrong target:
enemyAI.ts — selectTarget() logic and targeting priorityBattle stuck (no phase advancing):
battleResult is already set (ended state)advancePhase() callback is wired to phase completionItem disappears after purchase:
shopLogic.ts → generateConsumableFromData() — is item created correctly?InventoryContext.tsx — is addItem() called with the generated item?Can't buy item (button disabled or no effect):
canAfford(playerGold, price) — gold comparisonhasInventorySpace() — capacity checkavailable flag is falseuseGold() in ResourceContext returns successData lost after load:
localStorage.getItem('card_battle_save') in console — is data present?saveManager.load() return — is success true?Save version mismatch:
saveTypes.ts for current SAVE_VERSIONStyle bleeding between screens:
.battle-screen .my-class not just .my-classLESSONS_LEARNED.md for known CSS collision patternsElement sizing wrong:
vh/vw not px (project convention)1. Reproduce the bug (see Section 6)
2. Open React DevTools → find BattleScreen component
3. Drill into useBattleOrchestrator hooks
4. Collect battle state snapshot (Section 2 checklist)
5. Identify which subsystem is wrong (damage? buffs? phase? targeting?)
6. Follow the relevant diagnostic tree (Section 3)
7. Add targeted dbg() calls (Section 1)
8. Reproduce again with logging, read console output
9. Fix the root cause
10. Remove all dbg() calls and debugLogger.ts
Step 1: Add logging in executeCharacterManage.ts at phase start
dbg('buff', 'Phase start buffs', inspectBuffMap(buffs))
Step 2: Add logging at the buff duration decrement point
dbg('buff', 'Decrementing', { key, appliedBy: buff.appliedBy, currentPhase })
Step 3: Add logging at phase end
dbg('buff', 'Phase end buffs', inspectBuffMap(buffs))
Step 4: Play one full turn, compare phase-start vs phase-end logs
→ If no change: decrement condition is wrong (check appliedBy match)
→ If buff removed too early: duration was set wrong at application time
Step 5: Trace buff creation back to the card effect or enemy action that applied it
→ Check initial duration value at source
Step 1: Log at entry of calculateDamage()
dbg('damage', 'Input', { attackerAtk, defenderDef, card, element })
Step 2: Log each modifier stage
dbg('damage', 'After buff mod', { atkMultiplier, defMultiplier })
dbg('damage', 'After element mod', { elementMultiplier })
dbg('damage', 'After critical', { isCritical, critMultiplier })
Step 3: Log final result
dbg('damage', 'Result', { rawDamage, finalDamage, reflected, healed })
→ Compare expected vs actual at each stage to find divergence
Step 1: Log generatePhaseQueue() input (actor speeds)
Step 2: Log applySpeedRandomness() output for each actor
Step 3: Log final sorted queue
Step 4: Compare with expected order
→ If speeds look correct but order wrong: check sort comparator
→ If speeds wrong: check buff modifiers on speed stat
Step 1: Log moveItem() call with direction and item details
Step 2: Log capacity check result (source count, dest count, limits)
Step 3: Log state before and after the move operation
Step 4: Verify UI reflects the new state
→ If state correct but UI wrong: React re-render issue (check key props)
→ If state wrong: check reducer case for the direction
Step 1: Before save — log the data object being saved
dbg('save', 'Saving', { keys: Object.keys(data), version })
Step 2: After save — check localStorage directly
JSON.parse(localStorage.getItem('card_battle_save'))
Step 3: On load — log the parsed data
dbg('save', 'Loaded raw', parsedData)
Step 4: After load processing — log the hydrated state
dbg('save', 'Hydrated', { playerData, inventory, resources })
→ Compare Step 1 output with Step 3 to find data loss
→ Check Map fields specifically (mastery, buffs)
Components tab → search "BattleScreen"
→ Expand hooks panel
→ Find useBattleOrchestrator return values
→ Drill into each sub-hook:
useBattleState → playerHp, playerAp, buffs
useBattlePhase → currentPhase, phaseQueue
useCardExecution → hand, playedCards
useClassAbility → energy/resonance/summons (class-dependent)
// Quick state checks — paste in console during battle
// Check all enemy HP
document.querySelectorAll('[class*="enemy"]') // Find enemy elements
// Then use React DevTools $r to access selected component's props/state
// Check localStorage save data
JSON.parse(localStorage.getItem('card_battle_save') || '{}')
// Check current screen (from GameStateContext)
// Select any component in React DevTools, then:
// $r.props or hooks panel → find gameState context value
| Bug Type | File to Open | Where to Set Breakpoint |
|---|---|---|
| Wrong damage | damageCalculation.ts | calculateDamage() entry + return |
| Buff not applied | executeCharacterManage.ts | Buff application block |
| Phase stuck | useBattlePhase.ts | advancePhase() entry |
| Card not playing | useBattleOrchestrator.ts | Card play handler |
| Item lost | InventoryContext.tsx | moveItem() entry |
| Gold wrong | ResourceContext.tsx | useGold() / addGold() |
| Enemy AI wrong | enemyAI.ts | selectTarget() / action selection |
| Save fails | saveManager.ts | save() try/catch block |
| Elemental bug | elementalSystem.ts | onCardPlay() resonance update |
| Summon bug | useSummonSystem.ts | onCardPlayed() spawn logic |
DevTools → Application → Local Storage → localhost:5173
Key: card_battle_save
→ Right-click value → Copy → paste into console: JSON.parse('...')
→ Check: version, playerData, inventory, resources, dungeonState
1. Start dev server: npm run dev
2. Select character (swordsman is simplest)
3. Enter dungeon at depth 1
4. Click first battle node
→ You're now in battle state for testing
1. Enter battle, play several cards
2. Mid-battle, use browser back button
3. Navigate back to dungeon map
4. Re-enter the same battle node
→ If state desyncs, DungeonRunContext persistence is the issue
1. Play through several battles, buy items
2. Save game
3. Modify code to change a type definition (add/remove field)
4. Load game
→ If fields are missing/wrong, save migration is needed
1. Enter battle with an enemy that applies debuffs
2. Let the enemy apply a debuff to player
3. Observe debuff duration over multiple turns
4. Check: does duration decrease on correct phase? (applier's phase only)
After the bug is fixed, before committing:
[ ] Remove src/utils/debugLogger.ts (if created)
[ ] Remove all dbg() / inspectBuffMap() / console.log calls added for debugging
[ ] Remove any temporary helper functions
[ ] Run npm run build — verify no unused imports/variables
[ ] Run npm run lint -- --fix
[ ] Test the fix in browser
[ ] Test that the fix survives page refresh
[ ] Test that save/load still works if state was modified