What this is
FALLBACK_HULL_OCTAGON is a hard-coded 8-vertex convex polygon defined in src/starship-survivors/data/hull-hitboxes.ts. It is returned by getHullHitbox(hullClass) whenever the requested hull class has no entry in the auto-generated HULL_HITBOXES table. The fallback exists so that any ship — including hulls that were renamed or freshly imported before scripts/generate-hull-hitboxes.ts was re-run — still receives a valid Rapier2D collision body at spawn. Without a polygon the ship would get no body at all, and loop.ts would force-clamp the ship’s position and velocity to (0, 0) every frame, producing the “thruster fires but ship doesn’t move” failure mode.
The octagon shares the same normalized coordinate convention as the generated entries: vertices live in a ±0.9 range relative to the content bounding box, oriented to the engine-right (engine-x = sprite-forward) convention. This means callers can use it interchangeably with a generated hull polygon without changing the downstream scaling math in transformHullPoly.
The fallback is a permanent safety net, not a steady state. The intended hard-fix when it fires is to regenerate hull-hitboxes.ts from current sprite alphas.
Vertex layout
Eight vertices ordered counter-clockwise, forming a regular-looking octagon with the long sides aligned to the engine-x and engine-y axes.
| Index | X | Y |
|---|---|---|
| 0 | -0.90 | 0.37 |
| 1 | -0.90 | -0.37 |
| 2 | -0.37 | -0.90 |
| 3 | 0.37 | -0.90 |
| 4 | 0.90 | -0.37 |
| 5 | 0.90 | 0.37 |
| 6 | 0.37 | 0.90 |
| 7 | -0.37 | 0.90 |
Reference span:
| Property | Value |
|---|---|
| Vertex count | 8 |
| Normalized X extent | -0.90 to 0.90 |
| Normalized Y extent | -0.90 to 0.90 |
| Short-axis half-extent | 0.37 |
| Long-axis half-extent | 0.90 |
| Coordinate frame | content bbox, engine-right convention |
Which hulls use it
The current generated HULL_HITBOXES table holds 39 entries. The roster (SHIPS_V4_RARITY in src/starship-survivors/data/ships-v4-rarity.ts) defines 37 hull classes. The intersection is incomplete: 13 roster hulls have no generated polygon and therefore fall back to the octagon at runtime.
| Hull class | Faction |
|---|---|
| Backwater_Killer Croc | Backwater |
| Industria_Eel | Industria |
| Industria_Loader | Industria |
| Junkrats_Bomber | Junkrats |
| Junkrats_Skewer | Junkrats |
| Junkrats_Stinger | Junkrats |
| Junkrats_Tank | Junkrats |
| Prism_Crystal | Prism |
| Prism_Shard | Prism |
| Solaris_Cruiser | Solaris |
| Solaris_Flyer | Solaris |
| Solaris_Hauler | Solaris |
| Solaris_Torque | Solaris |
HULL_HITBOXES also contains entries (e.g. Backwater_Toad, Industria_Barracuda, Shark Patrol_Cruiser, Junkrats_Bombadier) for hulls not present in the current SHIPS_V4_RARITY roster; those entries are dormant and never queried. The mismatch between the two tables — both unused entries on one side and missing entries on the other — is the symptom that motivates the fallback: sprites get renamed, the roster gets edited, and hull-hitboxes.ts lags behind until someone re-runs the generator.
Side effects
When getHullHitbox falls back, it fires diagnostics exactly once per hull-class string per process. The dedupe set is _missingHitboxLogged, a module-level Set<string>.
| Channel | Trigger | Payload / shape |
|---|---|---|
console.warn | First miss per hull, browser only | [hull-hitboxes] no entry for "<hull>" — using octagon fallback. Regenerate with: npx tsx scripts/generate-hull-hitboxes.ts |
logDiag | First miss per hull, browser only | event_type: 'hull_hitbox_missing', level: 'warning', payload: { hull: <hullClass> } |
| Sentry | Via logDiag | Tagged bug=hull_hitbox_missing |
| Supabase | Via logDiag | Row in the diag events table with the same event_type and payload |
logDiag is loaded by dynamic import('../engine/telemetry/diag') and any rejection is swallowed; the fallback polygon is still returned regardless of telemetry outcome. In non-browser environments (typeof window === 'undefined') the warning and diag event are skipped entirely, but the polygon is still returned so headless tests and server-side code paths continue to function.
The gameplay consequence of running on the fallback is a visual silhouette vs collision shape mismatch. Generated entries trace the sprite alpha boundary at ~32 vertices simplified via Visvalingam-Whyatt and shrunk 5%; the octagon is a single coarse shape that ignores wings, prows, and tail spikes. Three knock-on effects:
| Effect | Why it matters |
|---|---|
| Over-coverage at thin profiles | Long, narrow hulls (eels, shards, skewers) take hits well outside their visible sprite — the octagon’s short-axis half-extent of 0.37 is wider than the actual silhouette. |
| Under-coverage at wing tips | Wide hulls with wingspans near the 0.90 normalized edge along both axes can have visible pixels outside the octagon’s diagonal cuts. |
High-shipScale amplification | The mismatch scales linearly with ship.shipScale in bridge.ts. At shipScale = 2.0 a 10-px silhouette mismatch becomes 20 px, and the octagon’s diagonals can sit visibly inside or outside the rendered sprite during debug overlay (#ffff00 hull poly draw in bridge.ts). |
The fallback keeps the run playable. It is not a substitute for a generated hitbox, and any hull seen logging hull_hitbox_missing in Supabase or Sentry should be fixed by re-running npx tsx scripts/generate-hull-hitboxes.ts and committing the regenerated file.