# fx_spark_mat
Additive glow material for the sparks of the energy-shield effect. `ShieldFx.module` resolves it by
name in `M.start` and makes it the material of one of its GPU-instanced pools: a single draw whose
instance transforms and lane data the module writes from Luau every frame. `ShieldFxDriver` on the
`ShieldDome` entity in the `fx_shield` scene starts the effect and steps it each frame. This
material's pool is "spark": up to 260 instances of a 6-sided tapered prism along +x with a pointed
tail at x = 0 and a blunt head at x = 1, whose uv.x runs from tail to head. Each spark is stretched
along its velocity (length 0.08 + 0.028 x speed, at most 0.75, width 0.075). Every impact sprays 22
or more sparks that live 0.55 to 1.85 s, and each incoming streak sheds shorter-lived sparks (0.25
to 0.6 s) from its head while in flight; lane 0 carries (age / life, 1, 0, 0).
Shader: `fx_glow` (`fx_glow.shader`). Its header describes one additive shader for every small
emissive thing in the shield effect: `form` picks the look, and per-instance lane 0 carries (life
0..1, brightness, seed, 0). The forms are 0 tracer streak, 1 soft flare blob, 2 spark and 3 burning
fragment. The output is the form's colour times brightness times `gain`, with alpha 1. For form 2
the heat is `1 - life`; the colour is `mix(color1, color2, heat^2)`, brighter toward the head of the
streak and fading out with the heat.
Render state: `blend: additive`, `cull: none`, `depth_write: false`.
## Properties
- `form: 2`: selects the spark.
- `gain: 1.8`: multiplier on the output.
- `color1: { r: 1.0, g: 0.35, b: 0.05, a: 1.0 }`: colour of an aged spark.
- `color2: { r: 1.0, g: 0.95, b: 0.7, a: 1.0 }`: colour of a new spark.
# fx_frag_mat
Additive glow material for the burning fragments of the energy-shield effect. `ShieldFx.module`
resolves it by name in `M.start` and makes it the material of one of its GPU-instanced pools: a
single draw whose instance transforms and lane data the module writes from Luau every frame.
`ShieldFxDriver` on the `ShieldDome` entity in the `fx_shield` scene starts the effect and steps it
each frame. This material's pool is "frag": up to 90 instances of a faceted octahedron shard with
flat normals. Each impact throws four or more fragments that live 1.8 to 4.2 s, fall under gravity,
bounce off the dome and the ground and spin; lane 0 carries (age / life, 1, seed, 0).
Shader: `fx_glow` (`fx_glow.shader`). Its header describes one additive shader for every small
emissive thing in the shield effect: `form` picks the look, and per-instance lane 0 carries (life
0..1, brightness, seed, 0). The forms are 0 tracer streak, 1 soft flare blob, 2 spark and 3 burning
fragment. The output is the form's colour times brightness times `gain`, with alpha 1. For form 3
the heat is `(1 - life)^1.5`; the colour moves from `color2` toward a dark red (0.5, 0.05, 0.0) as
the heat falls, is shaded by a fixed facet light and a per-seed flicker, and has `color1 * heat *
0.4` added.
Render state: `blend: additive`, `cull: none`, `depth_write: false`.
## Properties
- `form: 3`: selects the burning fragment.
- `gain: 1.8`: multiplier on the output.
- `color1: { r: 1.0, g: 0.3, b: 0.04, a: 1.0 }`: colour added in proportion to the heat.
- `color2: { r: 1.0, g: 0.75, b: 0.3, a: 1.0 }`: colour of a freshly hot fragment.
# fx_streak_mat
Additive glow material for the incoming fire that hits the energy shield. `ShieldFx.module` resolves
it by name in `M.start` and makes it the material of one of its GPU-instanced pools: a single draw
whose instance transforms and lane data the module writes from Luau every frame. `ShieldFxDriver` on
the `ShieldDome` entity in the `fx_shield` scene starts the effect and steps it each frame. This
material's pool is "streak": up to 16 instances of a 6-sided tapered prism along +x with a pointed
tail at x = 0 and a blunt head at x = 1, whose uv.x runs from tail to head. Each shot starts far out
over the horizon, flies at 75 to 120 m/s toward a point on the dome, and is drawn as one streak 8 to
15 m long and 0.34 to 0.56 wide stretched from its tail to its head; lane 0 carries (0, 1, 0, 0).
Shader: `fx_glow` (`fx_glow.shader`). Its header describes one additive shader for every small
emissive thing in the shield effect: `form` picks the look, and per-instance lane 0 carries (life
0..1, brightness, seed, 0). The forms are 0 tracer streak, 1 soft flare blob, 2 spark and 3 burning
fragment. The output is the form's colour times brightness times `gain`, with alpha 1. For form 0,
with `a` the uv.x along the streak, it returns `(mix(color1, color2, a^5) * (0.12 + 2.4 * a^1.6) +
color2 * a^5 * 4.5)` times a soft edge term: `color1` along the tail and `color2` at the head.
Render state: `blend: additive`, `cull: none`, `depth_write: false`.
## Properties
- `form: 0`: selects the tracer streak.
- `gain: 1.0`: multiplier on the output.
- `color1: { r: 1.0, g: 0.32, b: 0.06, a: 1.0 }`: tail colour.
- `color2: { r: 1.0, g: 0.85, b: 0.6, a: 1.0 }`: head colour.
# shield_dome_mat
Material for the energy-shield dome in the `fx_shield` scene. The `ShieldDome` entity at (110, 0, 150)
draws `shield_dome_mesh` with it and carries the `ShieldFxDriver` component (radius 9, impact
lights named `ImpactLight`). `ShieldFx.module` resolves the material by name in `M.start` and writes
its `center` once; every frame it writes the twelve hit slots and `strain` in one
`Material.setProperties` call, the same values it writes to `ground_arcs_mat`.
Shader: `shield_dome` (`shield_dome.shader`). Its header: an energy shield dome whose hits arrive as
12 float4 material properties, with the hex lattice, ripples, flares, strain flicker and base arcs
all evaluated per pixel. A conformal projection of the sphere onto a plane keeps the hex cells
hexagonal. Each hit sends a ring and a hex-quantised wave across the dome and blooms at the impact
point; the rim brightens with a fresnel term; strain makes cells strobe, the whole shield stutter
and the colour drift toward magenta-white; back faces draw at 55% brightness. The vertex stage dents
the surface at each impact and pushes a travelling swell outward from it.
Render state: `blend: additive`, `cull: none`, `depth_write: false`.
## Properties
- `hex_scale: 12.0`: density of the hex lattice on the projected dome (default 9, range 2 to 30); a
larger value makes more, smaller cells.
- `tint` (default (0.08, 0.55, 1.0, 1.0)): base glow colour.
- `hot` (default (0.75, 0.95, 1.0, 1.0)): colour mixed in for arcs and hit heat.
- `center` (default (0, 0, 0, 9)): xyz is the dome centre in world space and w the dome radius.
ShieldFx writes the `ShieldDome` entity's position (110, 0, 150) and the driver's radius, 9.
- `strain` (default 0, range 0 to 3): overload level; the shader divides it by 2.5 and clamps it to 1.
ShieldFx raises it with every hit, more when the hit lands near recent ones, caps it at 3 and
lets it decay over time.
- `hit0` to `hit11` (default (0, 1, 0, -1)): one slot per recent hit. xyz is the unit direction of
the hit from the dome centre times its strength, w its age in seconds; an age below 0 or above 3.5
leaves the slot idle.
# ground_arcs_mat
Material for the ring of energy on the ground around the shield dome in the `fx_shield` scene. The
`ShieldGroundArcs` entity at (110, 0, 150) draws `shield_ground_mesh` with it. `ShieldFx.module`
resolves it by name in `M.start` and writes its `center` once; every frame it writes the twelve hit
slots and `strain` in one `Material.setProperties` call, the same values it writes to
`shield_dome_mat`.
Shader: `ground_arcs` (`ground_arcs.shader`). Its header describes a flat annulus lying on the
ground around the dome's foot: a soft energy glow, jagged radial arcs crawling out from the base,
and hot spots on the ground under whichever part of the dome was just hit. Hits low on the dome heat
the ground more than hits high on it, the ring fades out between 4.5 and 9.5 m outside the dome
radius, and under strain the whole ring stutters. It is an unlit `fragment()` shader returning alpha 1.
Render state: `blend: additive`, `cull: none`, `depth_write: false`.
## Properties
mat.yaml sets only the render block. The shader's properties, and what writes them:
- `tint` (default (0.08, 0.55, 1.0, 1.0)): base glow colour.
- `hot` (default (0.75, 0.95, 1.0, 1.0)): colour mixed in for arcs and hit heat.
- `center` (default (0, 0, 0, 9)): xyz is the dome centre in world space and w the dome radius.
ShieldFx writes the `ShieldDome` entity's position (110, 0, 150) and the driver's radius, 9.
- `strain` (default 0, range 0 to 3): overload level; the shader divides it by 2.5 and clamps it to 1.
ShieldFx raises it with every hit, more when the hit lands near recent ones, caps it at 3 and
lets it decay over time.
- `hit0` to `hit11` (default (0, 1, 0, -1)): one slot per recent hit. xyz is the unit direction of
the hit from the dome centre times its strength, w its age in seconds; an age below 0 or above 3.5
leaves the slot idle.
# fx_flare_mat
Additive glow material for the flare blobs of the energy-shield effect. `ShieldFx.module` resolves
it by name in `M.start` and makes it the material of one of its GPU-instanced pools: a single draw
whose instance transforms and lane data the module writes from Luau every frame. `ShieldFxDriver` on
the `ShieldDome` entity in the `fx_shield` scene starts the effect and steps it each frame. This
material's pool is "flare": up to 12 instances of a unit sphere (12 x 16 segments). A flare appears
where each shot strikes the dome (lasting 0.42 s) and at the far point each incoming streak starts
from (lasting 0.22 s); its size grows over its life and lane 0 carries (age / life, 1, 0, 0).
Shader: `fx_glow` (`fx_glow.shader`). Its header describes one additive shader for every small
emissive thing in the shield effect: `form` picks the look, and per-instance lane 0 carries (life
0..1, brightness, seed, 0). The forms are 0 tracer streak, 1 soft flare blob, 2 spark and 3 burning
fragment. The output is the form's colour times brightness times `gain`, with alpha 1. For form 1 it
takes `r = pow(abs(dot(n, v)), 2.2)` and returns `color2 * r^7 * 12 + color1 * r^2 * 1.1`, scaled by
`(1 - life)^2`: a hot core in `color2` inside a halo in `color1`, fading over the flare's life.
Render state: `blend: additive`, `cull: none`, `depth_write: false`.
## Properties
- `form: 1`: selects the soft flare blob.
- `gain: 1.0`: multiplier on the output.
- `color1: { r: 0.35, g: 0.75, b: 1.0, a: 1.0 }`: halo colour.
- `color2: { r: 1.0, g: 0.97, b: 0.9, a: 1.0 }`: core colour.