Boss Behavior (DRAFT)
The key words MUST, MUST NOT, REQUIRED, SHOULD, MAY, and OPTIONAL below are used as in RFC 2119.
Status: DRAFT. The shared contract below (schema, timeline reuse, anchor semantics) is normative now — it is what the tooling and data are built against. The per-boss tables are first drafts read off the rig clip vocabularies (#/boss-room, #493) and MUST be verified by observation — in the boss rooms and against original-game capture — before any Godot implementation starts. Unverified clip readings are marked in the data's clip_notes / note fields. Godot work on a boss MUST NOT begin while its table still carries unresolved readings for the attacks being implemented.
Why bosses are not enemy-room archetypes
Per /mechanics/enemy-attacks, bosses are out of scope for the flat enemy sandbox: each of the five vocabularies is unique, and the behavior is arena-bound — fly passes, surfacing pools, relocation spots are positions in the boss's own stage. The observation tool is #/boss-room (one room per boss, loaded inside its arena); the data is data/boss_arenas.json (schema_version: 2).
Shared contract
Data model (boss_arenas.json v2)
| Field | Semantics |
|---|---|
phases[] | Ordered; the first phase is the opener. hp_frac on a phase means it is entered when the boss's HP fraction drops to that value; a phase without hp_frac has a per-boss trigger (documented in its note — timer, attack-string completion, break threshold). A phase MAY be a modifier overlay rather than a moveset switch: speed_mult / cooldown_mult apply from entry onward (Reyburn's enrage). |
attacks[] | Reuses the enemy attack model: unset numeric fields (weight, ranges, window fractions, hit shape, damage_mult) default per that spec's defaults. phases gates availability (omitted = all phases); selection within a phase follows the enemy rules (range bands, weighted random). Boss-only attack fields: knockback (a hit also shoves the player that many units along the boss's facing — Reyburn's wing flap), lp_loops (repeat count for lp chain tokens; for projectile attacks one shot fires per rep — the fireball volley), split (a lob payload splits into this many impact points around the target). |
attacks[].chain | Explicit clip-token sequence — the generalization of the enemy model's windup_clips. Tokens resolve per the enemy clip-resolution order (exact, then *_<token> suffix); tokens ending in lp loop. Every clip before the last is pure telegraph; the timeline fractions (windup_frac / damage_end_frac) apply to the final clip of the chain. |
anchors[] | Named positions in the boss's default arena frame (meaningless elsewhere), authored with the room's click tool and round-tripped via its config export. Relocation targets and anchor-bound attacks (attacks[].anchor) reference them by name. |
spawn_pos | Authored boss position in the default arena frame — trusted verbatim including y, because the spot may be off the walkable collider (Humilias stands at the broken stage edge and attacks into the field). Authored in the room: move boss click → the config export captures the current position. Omitted = the room settles the boss at the collider center. |
parts[].instances[] | Placed copies of a multi-part rig piece in the boss's local frame (+z = facing): pos plus yaw_deg/pitch_deg. A part MAY set animated: false — the piece never mirrors the body's clips and holds its wrapper/curve pose while only the boss body animates (the octopus tentacles); default true (Humilias's faces play in sync). A tube-rigged instance (the octopus tentacles — flat sibling bones evenly spaced along local +X, clips pose bones absolutely) MAY instead carry curve: ≥2 control points in the boss's local frame, authored with the room's spline poser (#508). When curve is set it — not pos/yaw_deg — places the piece: the runtime MUST fit a Catmull-Rom through the points and pose each bone at its rest arc-offset along the fit (arc-length parameterized; past the curve's end the pose extrapolates straight along the end tangent so the tube never stretches or bunches), keep the bone's rest lateral offset in the tangent frame (the z_002_st tip prongs), and align bone +X to the tangent. A playing clip MAY overlay the curve: the clip's bone positions are read as (arc, lateral) curvilinear coordinates and re-mapped onto the authored curve, so the rig's wave animation rides the authored bend. The Godot implementation reads the same data. |
clip_notes | Documentational clip semantics (MAY), same as the enemy schema — the draft's observation notebook. Unverified readings say so. |
stats / fsm | Draft sim tuning (hp, move/turn speed, attack cooldown, flight cadence, punish break/multiplier) driving the room's behavior sim — web/src/boss-room/sim.ts, the runnable form of these tables (attack selection and the melee arc are imported from the enemy room's normative fsm.ts). Sim numbers are tuning draft, not observed truth; they harden through the same workflow below. |
Attack kinds
Boss attacks reuse the enemy kinds (melee_arc, projectile, lob, charge, leap) and add the boss-only kinds below. The additions extend — and MUST NOT alter — the enemy set:
| Kind | Draft semantics |
|---|---|
beam_sweep | A sustained beam/breath swept across an angle while the release clip plays (Reyburn's fire breath, Humilias's laser). Damage is continuous inside the sweep during the window — not a one-shot arc test. |
aoe_burst | Radial burst centered on the boss at the window open (Dark Falz's radiate). |
grab | On connect, the target is held and takes the attack's damage over the hold; damaging the boss past a threshold cancels it (Octo Diablo's absorb→eat, whose rig carries explicit *cnc cancel clips). |
fly_pass | An airborne strafing run along an anchor path (Reyburn). Anchor-bound by definition. |
spout | An aimed sustained jet from a fixed emplacement/surfacing spot (Octo Diablo). Like beam_sweep but position-locked rather than angle-swept. |
heal | Self-restore at the window open (Chaos Sorcerer's green gem): heals damage_mult×maxHP. |
evade | A no-damage disengage — the boss leaps AWAY from its locked facing during the telegraph (Sinow Beat's backstep). |
clone | Spawns split decoy copies of the boss in a ring around the player (fsm.clone_ring_radius). The decoys deal no damage, dispel when hit, and clear when the real boss's next melee_arc lands or it takes a hit (Sinow Beat's transform). |
trap | Deploys split floor traps scattered within hit_reach of the player. Stepping on one (player, or the boss itself when fsm.self_trap) discharges the attack's freeze Zonde: damage + a fixed-duration freeze (Sinow Beat's Zonde traps). |
Boss-only attack fields on top of these: freeze — a hit (a trap, a Zonde projectile, or the post leap) also FREEZES the target in place for that many seconds (Sinow Beat's Zonde). A boss's fsm may set relocate_kind: post (Sinow Beat) — the RELOCATE cycle is a hop onto an authored perimeter-post anchor (fsm.post_height), a brief perch, then a leap-down onto the player with the leap attack.
Breathing room (post-attack disengage)
After an attack resolves the boss MUST disengage — a loafing walk away from the player on a slowly curving path (fsm.loaf_duration_min/max seconds, the enemy model's LOAFING) — before it re-engages. This is the fairness contract: a boss that re-acquires the player immediately after every attack pins them permanently (a downed player could never stand up). Aggression modifiers scale it: an enrage phase's cooldown_mult shortens the disengage along with the cooldown, so "more aggressive" compresses the breathing room without removing it.
Punish windows
Every boss fight MUST expose deliberate vulnerability windows — that is the genre contract. The draft recognizes two shapes, both visible in the rigs:
- Fatigue — a tired idle after an attack string (Octo Diablo's
wattir): triggered byfsm.fatigue_attacksattacks since the last rest, the punish clip loops forfsm.punish_duration, and for a submerge-relocating boss the window ends in the relocation. - Knockdown — a break threshold (
fsm.punish_break_damage) topples the boss: either a one-shot reaction clip (Reyburn'sdmg1— though the observed pattern list has no knockdown, unconfirmed) or the full chainpunish_start_clip → punish_clip loop → punish_end_clip(Humilias'sfall → fallwat → stdup). Damage taken during the window reuses the enemy model'srecovery_vulnerable_multsemantics.
Per-boss drafts
Numbers live in data/boss_arenas.json — these sections describe the fight shape and flag what is unverified.
Reyburn (boss_dragon — Gurhacia Valley, claiming_a_stake)
Observed patterns (from the real fight — these supersede the earlier clip-derived guesses and are what the table encodes):
- Walk + bite (
atkh1) and walk + tail swipe (atkrl/atkrr, wide arcs). - Wing flap (
atkwg) — damages and blows the player away along the facing (knockback: 8). - War cry (
tht) — a blast radius in a circle around the dragon (aoe_burst, 360°). The same clip serves as the fight opener and the enrage growl. - Fireball volley — three fireballs while standing (
brsst→brslp×3→brs2,projectile, one shot perlprep). - Rush bite — runs toward the player (
wlk2) and bites (atkh2) —charge, closes during the telegraph. - Flight cycle — takes off (
flst), flies away, and deals damage in a circle where it lands (land_slam); from the air it can drop a fireball that splits into 3 parts on the ground (sky_fireball,lob split: 3— impact rings shown in the room). - Low-HP enrage — the boss growls and its edges turn red, becoming more aggressive: the
enragemodifier phase (hp_frac 0.35,speed_mult 1.3,cooldown_mult 0.6). The room approximates the red edges with an emissive tint.
Sim'd end-to-end in #/boss-room/reyburn: intro roar → grounded chase/attack from the bands → timer-driven flight cycle → enrage → death, with the J-swing driving HP so every transition is observable.
- Open: flight-entry cadence (flat 22 s timer is a draft; HP-step?); whether
rdst/rdgare the landing dive;brs1as the single-fireball variant; whether a knockdown punish exists at all — the observed list has none, so the draft's damage-break (dmg1, ×2) stays unconfirmed until checked against the real fight.
Octo Diablo (boss_octopus — Ozette Wetlands, poisoned_water)
A different fight shape from Reyburn: a rooted surfacing emplacement (fsm.stationary) that relocates by submerging. The cycle:
- Surfaced — rooted at a spot, faces the player, attacks from the bands: spout (sustained water jet, the position-locked
beamvariant), vomit (a ground-firedlobat the player), and absorb→eat — agrab: damage ticks while the player is held, and damaging the boss breaks the grip (cancel_clip: eatcnc— the rig carries explicit cancel clips, so the mechanic is native). Its breathing room is a rest beat in place (a rooted boss can't loaf-walk). - Tired (punish) — after a 3-attack string (
fsm.fatigue_attacks) thewattiridle loops forfsm.punish_duration(4 s, ×2 damage) — the fatigue shape of the punish contract. - Submerged (relocate) — the tired window ends in a submerge, not a re-engage: sinks below the floor (
relocate_untargetable— hits don't land), swims (float) to the next surfacing spot, rises rested. Surfacing spots are the first real use of authored anchors: with none authored it falls back to resurfacing ~10 u from the player — author the pool spots in the room and the sim uses them.
- Multi-part rig. The tentacles are separate skinned meshes —
parts/z_002_ttandparts/z_002_st, each carrying all 20 of the body's clip names — imported viaimport_objects.py parts(they live under the viewer's enemy parts, not the object sets) and rendered in the room in sync with the body (BossDef.parts). The same mechanism covers Humilias's faces (z_004_kao_a/b) and Reyburn's static horn. - Staging. The body sits in the pool (
model_offset_ysinks the rendered group to the waterline; the logical position stays on the collider), and four tentacle instances of thez_002_ttrig are placed evenly around it in the boss's facing frame — two flanking the face, two behind (parts[].instances: local pos + yaw). The tentacle pieces areanimated: false— only the body plays clips; each tentacle holds an authored pose. The wrapper pos/yaw approximation hit its ceiling (#508): the room's tentacle poser authorsinstances[].curvecontrol points per the parts contract above — click the water to place (the emergence dips below the clicked surface: the tentacles start in the water), click a tip target to bend, sliders for apex lift and start depth — so a tentacle's spine is staged exactly. The fourz_002_stsurfacing coils are curve-staged evenly around the body (diagonals, emergence at ±4/±4 submerged → apex r≈12.7 above water → tip r≈19.7 at the surface); thez_002_ttstrike arm (posed it plunges 11.4 u below its base) is NOT statically staged — it is reserved for attack presentations aimed at the player (#508 follow-up). Bone frames are twist-free by contract: bone +Y stays pinned to world up (fallback +Z near vertical), so the sucker side never corkscrews along the arm or differs between tentacles. Which bone-local side carries the suckers is a modeling artifact, so an instance MAY setroll_deg— a constant roll about the tube axis on top of the twist-free frame (thez_002_strig needs90to face its suckers down; the tip-prong lateral offsets roll with the tube). - Draft simplifications: the absorb pull-in is skipped (the grab ticks in place); the spout is a ticked wide arc, not a swept jet; submerging is a plain sink below the floor mesh.
- Open: the real attack-string length and tired duration; no death clip in the rig (scripted sink?); whether
collisnis a wall-stun or an attack; whether surfacing spots are fixed pools (likely — author and verify); where theo0b_kaerufrog prop from the parts set belongs.
Humilias (boss_mother — Dark Shrine A, control_system)
- Advancing juggernaut — sim'd. Slow advance (
move_speed 2, narrow turn); laser sweep at range (beam_sweep), arm swing / headbutt press (charge) / fullcmbcombo string up close. The face parts (z_004_kao_a/b) render in sync. - Staging (per Kion): she does NOT stand in the middle of the field — she stands at the broken section of the stage, off to the side, and attacks into the field on sheer size.
spawn_posencodes this once authored in the room (move boss → copy config); whether her "advance" is real locomotion or she fights rooted from the ledge is now an open question. - Knockdown chain — the punish contract's third shape. 80 accumulated damage topples her:
punish_start_clip fall(one-shot) →punish_clip fallwatloops forpunish_duration(4 s, ×2 damage — the weak point exposed) →punish_end_clip stdup(one-shot recovery) → re-engage. - Open: what drives the knockdown (cumulative damage vs weak-point hits — the faces?);
hedbhitlpgrinding-on-hit semantics (sustained contact damage?).
Chaos Mobius (boss_robot — Arca Plant, heretic)
- Unblocked. The full 26-clip kit imported via the patched apicula (
psz-asset-viewer scripts/processBossRobot.ts): the PSZ boss rigs carry one extra unanimated root object — named after the model, last in the object list — that the clips don't cover, and apicula's exact object-count match rejected every clip. The patch (local~/Projects/apicula) accepts animations with exactly one fewer object. The game rig isboss_robot_cmb's combinedz_003. - Draft table — sim'd. Left/right drills and the big sweep (segmented
st→lp→atchains; theedrecovery clips are not yet played), left/right gun projectiles,mov_lplocomotion, and thedwn_st → dwn_lp → dwn_edknockdown chain (same shape as Humilias). - Open — the two-robot mechanics: "Chaos & Mobius" are two robots. The narc carries their entrances (
stt_n/stt_u), combine/split transitions (ch_in/ch_outper robot), separate deaths (ded_n/ded_u), and separated idle forms (ve_*, the plainboss_robotnarc) — all now converted onto thez_003n/z_103upart rigs, pending fight design. Thefloor_lazerarena props (colored floor tiles!) ride the cmb narc too.gl/gr= gun left/right andstpreadings unverified. - This is the Arca (heretic) encounter only. The Paru first encounter — two floating robots, one drill-like (per Kion) — is absent from the PSZ extraction (all 66 enemy narcs checked;
boss_robotis the sole robot boss, the Arca/union humanoid). Paru is covered by the realshinowaextraction (the Sinow line) instead (see below). The two-robot union material here (ch_in/ch_out,ve_*, thefloor_lazerprops) remains for the Arca fight design.
Heaven's Mother (composite — Eternal Tower summit, s087_na1)
- A composite boss: the fight is the three tower mother elites —
blade_mother(mother_sword),force_mother(mother_tech),shot_mother(mother_gun) — cycling as phases (per Kion). They are regular roster enemies (blade_mother also spawns ininvestigate_tower), so the data models them asforms[]on one boss entry rather thanis_bossflags; the room gets a form picker that swaps the loaded rig. - Shared 15-clip vocabulary across all three: melee swings (
atk_sa_s/atk_sb_s), a gun-volley chain (atk_gu_a_*on blade,atk_gu_g_*on force/shot), charged techniques (tec_t_st/lp/ed+ instanttec_a), and — the signature —warp_st/warp_ed: she teleports, which likely serves as both relocation and the form-transition presentation. - Open: form order and transition trigger (HP thirds? break?); which technique each form casts; the
boss_mother_piecerig in the viewer (9km*clips — floating pieces/adds?) is unimported pending observation; the arena is the one non-_zboss stage (tower floor 7, and it ships real minimap SVGs unlike the z arenas).
Sinow Beat (Paru — Oblivion City, s05z_na1)
The Paru boss room now runs the real PSZ mission extraction — shinowa (roster sinow_beat, model_scale 2.0) — replacing the earlier PSO Sinow stand-in (#516). It is a full animation set (tht, wlk, wat/wat2, backstep, atk, atk2, transform, dmg, ded), so it needs no stand-in caveats. The rare sinow_gold palette-swap shares the encounter and is a later tinted-variant follow-up (no separate room). The fight is enhanced above field PSO/PSZ behavior (#516):
- Core combat + range sense — sim'd. A melee bot with distinct distance behavior: up close it jumping-slashes (
atk,melee_arc) with a rare point-blank backstep evade (backstep, theevadekind — leaps away, no damage, low weight so it doesn't kite); mid-range it dash-rolls IN (dash_in,charge, chaintht → atk— the front-flip roll closes the gap, then the slash) so it commits to melee instead of backing off; and only from far (13m+) does it zone with Zonde. - Transform → decoy ring — sim'd.
transform(clonekind) spawns 6 fake Sinow Beats in a ring around the player (rendered atfsm.clone_alpha 0.9, each looping the slash clip so they fake-attack). The decoys deal no damage; a clone disappears the instant any player attack hits it; the ring exists to open a window for the real boss's jumping slash — while it is up the real boss commits to closing in and slashing (it will not stack another ring or cast from range). The ring clears when thatslashlands, the real boss is hit, orfsm.clone_life(≤15 s) elapses — then the cycle repeats. - Zonde traps + direct cast — sim'd.
zonde_traps(trapkind) deploys floor traps; stepping on one casts Zonde — damage + freeze the player in place (freeze).fsm.self_trap: Sinow Beat triggers and freezes on its own traps if it walks over them.zonde_cast(projectile+freeze, far-only) is a direct bolt that freezes on hit. - 4-post high ground — sim'd. Every
fsm.flight_intervals the RELOCATE cycle is arelocate_kind: posthop: Sinow Beat leaps onto one of the arena's 4 perimeter posts (authored anchors,fsm.post_height), perches, then leaps down onto the player withatk2(theleapattack, damage + Zonde freeze). - Draft simplifications:
thtis a front flip/roll (the entrance flourish and thedash_ingap-closer — not a tech cast);zonde_cast/zonde_trapsREUSEwat2since the rig has no dedicated cast/deploy animation — verify. Decoy fake-attacks are theatkclip looped, not bespoke per-clone timing. The 4 post anchors are DRAFT positions — author them against the reals05z_na1posts in the room (move/anchor tool). - Open: the real transform/clone count and cadence; whether Zonde has a distinct clip in the source; the true post geometry; the exact range bands (tune in the room); whether Gold is a re-skin or a distinct moveset.
PSO stand-in boss — Chaos Sorcerer (Makara)
The Makara arena has no usable PSZ boss rig, so we substitute a PSO asset (per Kion). The rig is ~25–30 units tall; a uniform 0.09 scale is baked into the geometry once (positions, inverse bind matrices, node translations, and animation translation tracks) so it ships pre-sized (~2.3–2.7 m) with identity transforms — no runtime scale to reconcile between three.js and Godot. Textures are embedded, so the GLB is self-contained. Its rig lives on R2 but not yet in the game pack (pack_pending), so the boss room loads it while the pack publish + Godot .tres follow.
- Makara — Chaos Sorcerer (
s04z_na1): a gem caster (per Kion). Two gems float on either side; colour → school: purple = dark, red = fire, blue = ice, green = self-heal. The two gems always show before a cast — they telegraph the two possible next spells without revealing which fires (a genuine 50/50 coin flip; the older gem is not preferred). The cast cycle (per Kion): start with two gems → one gem is used to cast (50/50), moving in front of the sorcerer, spinning rapidly, and vanishing as the spell releases → a small cooldown until a new gem respawns (gem_respawn_delay, a colour rolled at random but never the one still held, so the pair stays distinct) → only then a small cooldown until it casts again (gem_cast_delay, the telegraph window where both gems are visible) → cast again (50/50). The next-cast timer starts after the refill, so a fresh gem is always shown for the full telegraph window before it can be used — it is never spawned-and-immediately-consumed. (attack_cooldownis unused for the gem caster; timing is the two fsm delays.) It hovers at a fixed height off the floor (fsm.hover_hold), never loaf-walks (the cooldown is its rest), and periodically cancel-casts to teleport elsewhere in the arena (teleport_interval). Rendered atmodel_scale 1.5. Sim additions:fsm.gem_caster+ the gem state machine (two-gem telegraph, the casting-gem front-spin, distinct-colour cooldown refill), ahealattackkind(green restoresdamage_mult×maxHP), and the hover hold. Draft clip→element mapping (attack1=fire,attack2=ice,attack3=dark,cure=heal) is a guess — tune by observation. Follow-ups: interrupting a heal mid-cast should cancel it; the teleport is an instant reposition (no warp VFX yet).
Enemy-arena boss room — Snowfield
The Rioh Snowfield arena (s03z_na1) is a pack fight, not a unique boss: it spawns a Hildegao pack. It needs no boss_arenas.json entry — regular enemy spawns in the arena cell cover it, via debug_boss_snowfield.
Dark Falz (boss_darkfalz — Dark Shrine B, dark_castle)
- Relocating ranged finale — sim'd. Walks and runs the ranged kit: throw volley (
thr*, one projectile perthrlprep), diffusion spread (dif*— the release fanssplit: 5projectiles across ±25°), radiate AoE burst (rad*, 360°), single heavy shot (bhsht), and the widenagimow-down sweep up close. Every ~25 s (flight_interval+relocate_kind submerge) it sinks untargetable and swims (relocate_loop_clip swm) to a new spot — the timer-driven variant of the octopus's fatigue-driven cycle. - Open:
up/downcould instead be the knockdown pair (the draft punishes withdam3once);orcais unidentified; whether the original fight is multi-form (one rig imported — if forms exist they are separate future work).
Debug missions (the in-game instrument)
Each boss arena has a debug mission — debug_boss_<id> in data/quests/ — that warps the player straight into the boss cell. They are always unlocked (no parent_quest, never disabled), sit at the end of the guild-counter list with a DEBUG: name prefix, and award nothing (empty rewards). They deliberately ship in beta builds: zero-friction boss testing on real hardware. Remove them from data/quests/manifest.json for a real release.
- Structure: a single
type: "boss"section whose one cell is both start and end, spawning the boss (roster id) at the arena origin — the same shape as the story quests' boss sections. - Snowfield (
s03z_na1) has no unique boss: its debug mission spawns a Hildegao pack (×4) — matching the real game's snowfield boss room. Heaven's Mother's mission spawns all three forms together in the tower summit arena for observation (the real fight cycles them). Makara's mission is pending its composition. - Each has a matching
data/quest_plans/entry (onekill_all + wait_quest_completestep), soPSZ_AUTOPILOT_QUEST=debug_boss_<id>drives the whole accept → warp → clear → report loop headlessly — the autopilot probe for boss work. - Bosses currently spawn through the generic
EnemyBasemelee skeleton (their.tresroster entries) — these missions are where the per-boss Godot implementations get exercised as they land, mirroring this page's tables.
Draft → normative workflow
- Observe in
#/boss-room(attack buttons play the drafted chains; anchors are authored with the click tool and round-tripped via the config export) and against original-game capture. - Correct
data/boss_arenas.json; resolve or strike each "open" item above. - Lift this page's DRAFT marker per boss — only then does the Godot implementation for that boss start, with the two test layers (seeded
test_runnerunit test + autopilot probe on the boss's quest) pinned against these tables.
Implemented by
- No Godot implementation yet — by design (spec-first).
web/src/boss-room/— the observation/authoring instrument: behavior-panel playback of the drafted attacks, anchor authoring, config round-trip.data/boss_arenas.json(v2) — the tables; validated byweb/src/__tests__/boss-arenas-data.test.ts.