Enemy Hierarchy

The key words MUST, MUST NOT, SHOULD, and MAY are used as in RFC 2119.

Today every enemy is one class — EnemyBase (scripts/3d/enemies/enemy_base.gd) — driven by an EnemyData resource (/states/enemies). Beta keeps that as the default and adds justified behavior subclasses. This page is the contract both sides honor.

What the base owns (subclasses inherit, MUST NOT reimplement)

ResponsibilitySurface
AI state machine shellEnemyState enum (IDLE / CHASING / ATTACKING / LOAFING / HURT / DEAD) + the per-state tick dispatch. Subclasses override state handlers, not the dispatch.
Damage intaketake_damage → HP mutation → HURT/DEAD transitions, damage numbers, hit SFX.
Status effectsApplication via CombatManager.roll_element_status / apply_status_effect, non-stacking rule, DoT ticking, model tint (STATUS_COLORS).
Death & rewardsDrop rolls, EXP grant (#343), death animation/cleanup, session kill accounting.
Presentation plumbingModel load + texture application (MeshUtils), per-model SFX map, reticle, hurtbox construction.
Floor-edge movement guard_can_move_to all-3-probe rule — deliberately stricter than the player's 2-of-3 (#215-C6: intentional divergence; do not "unify").

When to subclass — and when NOT to

Invariants every leaf preserves

Testing (two layers, per /engineering)

Each new subclass ships a seeded unit test of its divergent handlers (state-transition table, the test_player_states pattern) and a post-build probe (a matrix phase or kill_all room exercising it under autopilot).