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.

IndexXY
0-0.900.37
1-0.90-0.37
2-0.37-0.90
30.37-0.90
40.90-0.37
50.900.37
60.370.90
7-0.370.90

Reference span:

PropertyValue
Vertex count8
Normalized X extent-0.90 to 0.90
Normalized Y extent-0.90 to 0.90
Short-axis half-extent0.37
Long-axis half-extent0.90
Coordinate framecontent 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 classFaction
Backwater_Killer CrocBackwater
Industria_EelIndustria
Industria_LoaderIndustria
Junkrats_BomberJunkrats
Junkrats_SkewerJunkrats
Junkrats_StingerJunkrats
Junkrats_TankJunkrats
Prism_CrystalPrism
Prism_ShardPrism
Solaris_CruiserSolaris
Solaris_FlyerSolaris
Solaris_HaulerSolaris
Solaris_TorqueSolaris

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>.

ChannelTriggerPayload / shape
console.warnFirst miss per hull, browser only[hull-hitboxes] no entry for "<hull>" — using octagon fallback. Regenerate with: npx tsx scripts/generate-hull-hitboxes.ts
logDiagFirst miss per hull, browser onlyevent_type: 'hull_hitbox_missing', level: 'warning', payload: { hull: <hullClass> }
SentryVia logDiagTagged bug=hull_hitbox_missing
SupabaseVia logDiagRow 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:

EffectWhy it matters
Over-coverage at thin profilesLong, 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 tipsWide hulls with wingspans near the 0.90 normalized edge along both axes can have visible pixels outside the octagon’s diagonal cuts.
High-shipScale amplificationThe 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.