Date: After Attack System Refactor (2024) Status: MANDATORY - All future development must follow these principles
❌ FORBIDDEN:
if (p.charName === "cyboard") {
// Character-specific logic
}✅ REQUIRED:
const descriptor = AttackCatalog.getDescriptor(p, attackType);
if (descriptor?.specialProperty) {
// Data-driven logic
}- All attack properties (damage, knockback, FX, priority) come from
attack-catalog.js - New characters = new descriptor entries, NOT code changes
- Character-specific behavior via descriptor metadata (e.g.,
animSpeed)
- Fritz: Uses
p.attack.type = "r2_l2_ulti"(standard system) - HP/Cyboard: Uses
p.ultiPhase(special state machine) BUT must attach descriptors to hits - All ultimates: Must use
AttackCatalog.getDescriptor(p, "r2_l2_ulti")for priority/FX
- Add descriptors to
attack-catalog.js(duplicate from similar character) - NO code changes in
physics.jsfor basic attacks - Special interactions (like Cyboard sword) require code, but use descriptors for FX/priority
- Define in
attack-catalog.jswith proper tier/priority - Add FX references in descriptor.fx.hit/clank
- Use descriptor in attack logic for speed, FX, priority
// OLD (FORBIDDEN):
spawnEffect(state, p, "hardcoded_fx");
// NEW (REQUIRED):
const descriptor = AttackCatalog.getDescriptor(p, attackType);
const fxId = descriptor?.fx?.hit?.id || "fallback_fx";
spawnEffect(state, p, fxId, descriptor?.fx?.hit?.options);These character-specific checks are intentionally preserved:
// Cyboard sword recall logic (lines 680-707)
if (p.charName === "cyboard" && p.swordIsOut) {
// Complex projectile interaction - must stay
}// Fritz L3+R1 combo (lines 801-808)
if (inputs.l3UpR1Down && p.charName === "fritz") {
// Unique input mapping - must stay
}// L1/L2 per character (lines 834-870)
if (inputs.l1Down && p.charName === "fritz") {
// Each char has unique specials - must stay
}Before committing any attack-related changes:
- No new
p.charName ===checks added - New attacks use descriptor system
- FX routing checks descriptor first
- Trade logging shows correct priorities
- All characters still playable
- No regression in dance battle/match end
// Enable in console for debugging:
AttackCatalog.enableTradeLogging(true);
// Shows: [AttackTrade] win: r2 (tier=SMASH, prio=85) vs l1 (tier=COMBO, prio=55)// Check descriptor in console:
const desc = AttackCatalog.getDescriptor(player, "r2");
console.log(desc); // Shows tier, priority, FX, metadata- Before: 20+ hardcoded character checks in attack logic
- After: Data-driven system with 3 intentional exceptions
- Result: Maintainable, extensible, consistent attack system
Any deviation from these guidelines will result in:
- Technical debt accumulation
- Inconsistent attack behavior
- Difficult character additions
- Regression in priority/FX systems
When in doubt: Add to descriptor, don't add to code.
This document is living - update when new patterns emerge, but never remove these core principles.