The pack
trails3d
function trails3d(): Pack<{ trails: Trails3dApi }>
The trails-3d pack: shader-drawn ribbons with turbulence, erosion, a hot core, fibre, edge hardness,
crackle and path-shed sparks. Requires a 3D world and render-3d, whose depth pass and camera it joins;
sparks need particles-3d too.
Functions
crTrail
function crTrail(a: TrailPoint, b: TrailPoint, c: TrailPoint, d: TrailPoint, t: number): TrailPoint
Catmull-Rom through four control points at t in 0..1 between b and c, interpolating the fade too, so
k stays smooth along the curve.
The uniform span is the kernel's catmullRom1, whose t³ coefficient vanishes exactly for collinear, evenly spaced
points; a sign slip there would make the ribbon bowtie once per cell.
curveAt
function curveAt(pts: readonly TrailPoint[], s: number): TrailPoint
Evaluate the smoothed curve at s in 0..1, 0 at the head, over an array of control points.
registerTrail3d
function registerTrail3d(k: { registry: { add(kind: string, def: TrailDef): void } }, def: TrailDef): void
A trail-* pack's attach calls this: register a preset under the registry kind 'trail3d'.
resampleTrail
function resampleTrail( points: ReadonlyArray<{ x: number; y: number; z: number; age: number }>, n: number, life: number, ): TrailPoint[]
Resample a trail's raw point history — oldest first — into n control points running HEAD (newest) to TAIL,
each carrying a fade k of 1 at the head down to 0 at the end of life, spaced UNIFORMLY IN ARC LENGTH.
Arc-uniform spacing is what makes the ribbon's parameter proportional to DISTANCE along the path. That gives stable curves at any speed, and it lets the shader reconstruct absolute travelled distance linearly — which is the anchor every noise pattern on the ribbon is keyed to.
trailLength
function trailLength(points: ReadonlyArray<{ x: number; y: number; z: number }>): number
The arc length of a raw point history.
Constants
TRAIL_WGSL_FOR_TEST
const TRAIL_WGSL_FOR_TEST: "\nstruct Cam {\n viewProj : mat4x4<f32>,\n eye : vec4<f32>,\n sunDir : vec4<f32>, // xyz = direction the light travels, w = intensity\n sunCol : vec4<f32>,\n skyCol : vec4<f32>, // w = ambient strength\n groundCol: vec4<f32>,\n fogCol : vec4<f32>,\n params : vec4<f32>, // shininess, spe... …
Trail shader source exported so its rendering contract can be regression tested.
Types
Trail
interface Trail
A live trail. Every style field is mutable.
point(x: number, y: number, z: number): void— Feed a point by hand when nothing is followed.jump(x: number, y: number, z: number): void— Move without laying a point — a teleport should not draw a streak across the world.stop(): void— Stop recording; the tail runs out and it removes itself.start(): void— Record again.clear(): void— Forget the path at once.readonly length: number— Points held.readonly alive: boolean— Still alive.kill(): void— Remove it now.width: number— Ribbon width at the head.life: number— Seconds a point survives.alpha: number— Alpha at the head.turbulence: number— Sideways flutter at the tail.erode: number— Erosion toward the tail.core: number— White-hot centre-line.fiber: number— Fibre depth.hard: number— Edge hardness.crackle: number— Electric glint.
TrailDef
interface TrailDef
A registered trail preset — what a pack ships.
name: string— The nametrails.preset()is called with.description?: string— One line for the planner.spec: TrailSpec— The spec it starts from.
TrailPoint
interface TrailPoint
One control point of a smoothed trail: a position and its fade, 1 at the head and 0 at the tail.
x: number— The X position, in world units.y: number— The Y position, in world units.z: number— The Z position, in world units.k: number— The fade at this point, 0 to 1: 1 at the head, 0 at the tail.
Trails3dApi
interface Trails3dApi
game.trails.
add(spec?: TrailSpec): Trail— Start a trail.preset(name: string, overrides?: Partial<TrailSpec>): Trail— Start a registered preset, overriding any of it.readonly available: ReadonlyArray<string>— Every registered preset name.readonly defs: ReadonlyArray<TrailDef>— The registered definitions, for a menu.readonly all: ReadonlyArray<Trail>— Every live trail.
TrailSparks
interface TrailSparks
Particles shed along a trail's laid path, by a particles-3d emitter (compose particles-3d to use them).
per?: number— Particles shed per world unit of head travel. Default 0.25.speed?: number— Jitter speed, world units/s: each axis of a spark's launch velocity is drawn from −speed to speed. Default 3.life?: number— Spark lifetime, seconds; each lives 0.6 to 1.4 times it. Default 0.45.size?: number— Spark size, world units, shrinking to nothing over its life. Default 0.2.colors?: ReadonlyArray<string>— Palette picked per spark. Default the trail's own colours.add?: boolean— Additive blend. Default the trail's own.gravity?: number— Downward acceleration. Default 0.drag?: number— Velocity damping per second — sparks skitter to rest. Default 1.5.twinkle?: number— Brightness flicker 0..1, about five times a second; electric crackle reads at 0.6. Default 0.6.facing?: 'velocity' | 'camera'— How a spark faces: 'velocity' (the default) runs its dash along the way it flies, so a shower of them reads as streaks thrown off the trail; 'camera' faces the camera, each at its own turn, for a round glint or a star.stretch?: number— With 'velocity', extra length per unit of speed, world units: a fast spark is a long streak. Default 0.04.frame?: string— The atlas frame. Defaultparticle:spark, a streak;particle:softorparticle:starfor a glint.lit?: boolean— Take the scene's lights, as a particles-3d emitter'slit: for dull grit, smoke flecks or dark ash that should darken in shadow. Default false: a spark is light, drawn exactly its colour.max?: number— Most sparks alive at once, the emitter's pool: past it no more are shed until some die. One step that would shed more than a quarter of it is taken as a jump and sheds none. Default 1024.
TrailSpec
interface TrailSpec
Everything a trail can be told. Every field is live on the handle.
follow?: Handle— Follow this object each step; the trail releases itself when the target dies.offset?: { x?: number; y?: number; z?: number }— Feed offset from the followed object — an exhaust point rather than a centre.x?: number— Start x when nothing is followed.y?: number— Start y.z?: number— Start z.width?: number— Ribbon width at the head, world units. Default 0.8.life?: number— Seconds a recorded point survives. Default 0.5.colors?: ReadonlyArray<string>— Head→tail colour stops, 1–8. Default white.add?: boolean— Additive blend — fire, magic, light. Default true.taper?: boolean— Width tapers to nothing at the tail. Default true.alpha?: number— Alpha at the head. Default 0.8.spacing?: number— Minimum distance between committed history points; the live head still follows every movement. Default width × 0.25.points?: number— Most points held. Default 96.turbulence?: number— Noise-driven sideways flutter at the tail, world units. Default 0.35.erode?: number— Noise erosion toward the tail, 0..1 — the ribbon shreds into wisps instead of fading as a solid strip. Default 0.55.core?: number— White-hot centre-line intensity, 0..1; best withadd. Default 0.5.fiber?: number— Fibre-texture depth, 0..1 — 0 is a clean ribbon of light (Tron), 1 fully streaked (fire, smoke). Default 1.hard?: number— Edge hardness, 0..1 — 0 is a soft gaussian falloff (gas), 1 a crisp flat-topped band (neon). Default 0.crackle?: number— Electric glint crawling INSIDE the ribbon, 0..1 — lightning, plasma. Independent ofsparks; they compose. Default 0.facing?: 'view' | 'up'— 'view' faces the camera (comets, magic); 'up' is a vertical base-anchored wall — the Tron light-cycle trail. Default 'view'.sparks?: TrailSparks— Shed particles along the laid path.layer?: LayerName— The layer.


