PURPOSE
Canvas-2D entity drawing primitives — ship, enemies, bullets, terrain, pickups, shields, HP bars, event signposts, damage numbers, and regen stations. Ported from the legacy 08b-render-gameplay.js. Every per-frame world-space drawable except the post-effects pipeline lives in this module.
OWNS
- Player ship draw:
drawShip,drawShipSilhouette, hi-res sprite cache_hiResMap,setActiveShipRarity,setActiveHullClass,getShipDrawSize,getShipVisualScale. - Ship-scale constants:
SHIP_DRAW_SCALE = 6.3,SHIP_SPRITE_PAD_MULT = 1.6,RARITY_SIZE_MULT(common 0.92 → legendary 1.12). - Enemy + shape rendering:
drawEnemy,drawShape,drawShapeSilhouette,drawEnemySilhouette. - Bullet rendering:
drawBullet— single switch overarchwith branches forpulse_ball,cannonball,projectile,explosive,sniper,beam,chain,homing,mortar_shell,blade/sweep_laser,ember,disc_ring,shield_arc,tesla_line,mega_bullet,plasma_arc,quad_round,phoenix_pulse,plasma_ball,plasma_fire_zone,fire_patch,carpet_bomber,star_halo_root,beam_decay,artillery_strike, plus a generic fallback circle. - Prebaked stamps: missile rocket stamp (
_getMissileStamp, 64px), XP-orb stamps in four power-of-two tiers (32/64/128/256, picked by_pickXpOrbTier), shield ring stamp (_getShieldRingStamp, quantized to 2px), shield top-shell stamps (128px). - Shield rendering:
drawShield,drawShieldBottom,drawShieldTop,drawShieldRingShadow,getShieldVisualRadius,RARITY_SHIELD_COLORS, base paletteSHIELD_COLOR/SHIELD_COLOR_LITE/SHIELD_COLOR_DEEP, plus internal rim/hit-flash/ripple/rune helpers. - HP UI in world space:
triggerHpFlash,drawHpBar,drawHpRingShadow(usesSHADOW_OFFSET = 3,SHADOW_BLUR = 6,HP_FLASH_DURATION = 0.5). - Pickups + orbs:
drawPickup,drawXpOrb(animated viaXP_ORB_BOB_AMP,XP_ORB_BOB_SPEED,XP_ORB_SWIRL_SPEED,XP_ORB_SWIRL_COUNT,XP_ORB_TWINKLE_PERIOD,XP_ORB_TWINKLE_WINDOW,XP_ORB_MAX_SCALE). - Terrain:
drawTerrainFill,drawTerrainMergedStroke,drawTerrain, internalbuildTerrainPath, offscreen-buffer helperensureOffscreen. - Misc effects:
drawExplosion,drawGlow,drawJunk,drawAllDamageNumbers(with cached_dmgTextCache,_fmtDmg,_getDmgCanvas). - Events:
drawEvent,drawEventSignpost,drawEventStars,drawRegenStations,EVENT_COLORStable,COIN_NEONcolor table, helpers_starPath,_drawFivePointStar,_drawEventFlash.
READS FROM
../core:camera,uiScale,game,ship,playerInput,world— view transform, current time, player position.../core/clock:Clock.now()for time-based pulse/breath animations independent ofgame.time.../core/config:PERF_FLAGSfor perf-gated rendering paths../camera:Camera.toS(x, y)— world-to-screen projection used by every branch../shapes:Shapes,ShapeDef— polygon data for ship/enemy silhouettes../renderer:PAL(XP and palette colors)../ships-v4-loader:getShipV4— v4 hull-class diffuse texture lookup../glow-stamp:drawGlow as _drawGlow,drawWhiteGlow as _drawWhiteGlow— additive color and white-hot stamps used by the universal projectile glow pass../weapon-colors:getWeaponColor(_weaponId)— RGB float color keyed by weapon id for the neon glow pass.../vfx/juice:ShipRecoil— recoil offset applied to the ship draw.../vfx/post-fx:PostFx.spawn(...)— sniper beam afterimage spawns a'scar'PostFx on the first frame.../vfx/squash-stretch:getShipSquash,getEnemySquash— per-entity squash multipliers.../../data/ships: typeShipRarity.- Bullet branches read per-bullet hints from the live bullet object:
b.weaponId,b.weaponLevel,b._beamAngle,b._beamRange,b._beamWidth,b._beamLingerAge,b._beamHitboxSec,b._hideBeamLine,b._phaseAlpha,b._orbitAngle,b._orbitRadius,b._beamLengthFrac,b._bladeSize,b.c1/c2/c3,b._arcHeight,b._artPhase,b._artBlastR,b._artLandX/Y,b._artTelegraphProgress,b._ghostSpawned. Anything missing falls back to a hardcoded constant.
PUSHES TO
- The supplied
CanvasRenderingContext2D(only side-effect output of the draw calls themselves). PostFx.spawn('scar', ...)from the sniper branch — one persistent ghost scar per railgun beam, marked viab._ghostSpawnedso subsequent frames don’t respawn.- Internal stamp caches (
_hiResMap,_shieldRingMap,_xpOrbStamps,_missileStamp,_dmgTextCache,_shieldTopShellCache) — lazily populated on first request. - Module-local mutable state:
_activeShipRarity,_activeHullClass, HP-flash timer, ghost-scar spawn flag on the bullet.
DOES NOT
- Does not own the render loop or pass order —
bridge.tscalls these functions in the correct order each frame. - Does not run physics, collision, AI, input, or game logic.
radiusfor collision is passed in unchanged; the visualszderived insidedrawBulletdoes not feed back into hit-testing. - Does not own beam hitscan, sword orbit, gravity-mine logic, sweep-laser orbit motion, or mortar arc physics — only their visual representation.
- Does not draw cone-beam shader effects, warp puddles, smoke, thrusters, screen-space post-FX, or HUD overlays — those live in sibling modules under
engine/rendering/andengine/vfx/. - Does not enforce frame budgeting or feature-flag gating beyond reading
PERF_FLAGS. - Does not write to Supabase, telemetry, or any persistent store.
Signals
drawBulletwrites telemetry only via the implicit canvas draws; it does not log or measure on its own.setActiveShipRarity/setActiveHullClassare call-site signals from the run-assembly layer — once set, everydrawShipcall uses them until the next override.triggerHpFlash(intensity)is a one-shot signal that starts a flash timer consumed by the nextdrawHpBarcalls overHP_FLASH_DURATIONseconds.- Sniper branch sets
b._ghostSpawned = trueto signal “scar already issued for this bullet” to subsequent frames. - Sweep-laser branch reads
b._beamLengthFracas an emergence-animation signal owned by the bullet update.
Entry points
bridge.tscallsdrawBullet(ctx, b.x, b.y, b.rad, b.c1, 1, bArch, b.c2 || '#ffffff', b.vx, b.vy, wid, b)once per active bullet during the per-frame render pass.bridge.tsis the single importer of most draw functions and stitches them into the frame in order: terrain fill → terrain stroke → pickups/XP orbs → enemies → bullets → ship → shield bottom/top → HP ring/bar → event signposts/stars → regen stations → damage numbers → junk.combat/damage.tsis the only other importer — it calls into damage-number drawing helpers.- Many small exports (
drawShipSilhouette,drawEnemySilhouette,getShipVisualScale,getShieldVisualRadius,getActiveShipRarity) are public helpers for UI/HUD modules.
Pattern notes
- Branch-on-archetype in
drawBullet. All projectile variety is a single linearif (arch === 'X') / else if ...switch onarch, with a generic fallback that paints a soft additive halo + solid colored body + white center dot. There is no archetype registry; new bullet types are added by editing one branch in this file. - Legendary VFX scale. A bullet is treated as legendary when
b.weaponIdstarts withlgd_. Visual size multiplier_lgdVfxScale = 0.15 + (lvl - 1) * 0.35 / 19, clamped via the(1..20)clamp on_lgdLvl, gives 0.15× at L1 climbing linearly to 0.50× at L20. Visual alpha is also nerfed to 0.55 (_lgdAlphaScale) for legendaries on every branch. Damage/collision radii are untouched — only the renderedszshrinks. BULLET_VISUAL_SCALE = 1.04. Replaces an older 0.8 factor and intentionally keeps the VFX slightly smaller than the collision radius so projectiles read crisp but hitboxes stay generous.- Universal-glow inline pass is disabled. The early-bullet universal-glow block at the start of
drawBulletis gated behindif (false && ...), with a no-op skip listNO_UNIVERSAL_GLOW = ['sniper', 'orbit', 'gravity_mine', 'shield_arc', 'tesla_line', 'homing', 'mortar_shell']. Kept for historical reference but does not run. - Tail neon glow pass. After the archetype switch, every projectile body is followed by a unified additive glow drawn via
_drawGlow(outer color stamp) +_drawWhiteGlow(inner white-hot core). Radius isMath.max(15, sz * 6), doubled to× 1.8forcannonball. This pass is skipped for the archetypes that do their own bespoke glow:sniper,star_halo_root,beam_decay,artillery_strike,phoenix_pulse,plasma_fire_zone,fire_patch,carpet_bomber,mega_bullet. Color is keyed off_weaponIdviagetWeaponColor. - Draw-call layering within a branch. Every bullet branch follows the same stacking order: optional ground shadow → wide soft additive halo (
globalCompositeOperation = 'lighter') → mid color glow → solid colored body ('source-over') → outline if any → white-hot center dot/streak → optional secondary effects (crackle, glitch lines, endcap orb). Stacks of three or more concentric arcs with decreasing radius and increasing alpha are the dominant idiom. ctx.save()/restore()discipline.drawBulletopens one outer save, then a second save before the tail neon pass to undo per-branchtranslate/rotatetransforms. Branches that need their own coordinate framesave/restoreinternally (e.g.homingfor missile sprite rotation).- Hi-res sprite cache.
_getHiResShippre-bakes each loaded ship PNG to a 2× offscreen canvas keyed bysprite.srcand returns null if the image isn’t decoded yet, so the polygon fallback path can run. - Stamp baking pattern. Shields, missiles, XP orbs, and damage numbers all bake static glyphs once into an offscreen canvas and animate via
globalAlphaper frame — no per-frameshadowBlur(which is the perf hot path the comments call out). Shield ring stamps quantize their radius to the nearest 2px to keep the cache small. - Sniper beam two-phase fade.
lingerAge < hitboxSec(~0.10s) holds full brightness; afterwards an ease-out(1 - decayT)^1.6runs over the remaining lifetime up tomaxLifetime(~1.0s). The first full-brightness frame spawns aPostFx 'scar'that fades over 4 seconds, providing the ghostly afterimage. - Color sourcing. Bullet colors come from the bullet itself (
color,color2,b.c1/c2/c3) for branch-internal strokes, but the tail neon glow usesgetWeaponColor(_weaponId)so the halo follows the weapon’s canonical palette regardless of per-bullet overrides.