Visuals and mod settings
Mods can change how fights look in two ways:
- Building blocks (
sf2.fx) let you design your own effects: particle emitters with your own sprites or built-in shapes (including bursts at the point of each hit), overlays placed at a chosen depth or on the screen, trails on weapons or any two fighter nodes, contact shadows, blade glints, lights carried by weapons and magic, fixed in the stage or flashed by hits, floor stains, and screen grading with film effects (split toning, god rays, heat haze, chromatic aberration, cinema bars, camera shake) that can also fire on hits, knockouts, round starts, at random intervals, or when your Lua says so. Any number of mods can add these, and they stack. - Presets (
sf2.visuals) are nine ready-made effects: deeper background parallax, weapon trails, depth haze, a rim light on the fighters, bloom, ambient particles, a short impact effect on heavy hits, a restyled fight HUD and a close-up on the winner after a knockout. You only tune their numbers, and each preset has one owner.
Everything is off until a mod asks for it. The engine does the drawing; a mod
describes effects with typed tables when it loads. A combat callback can fire
one of its own screen grades with sf2.fx.play, and a mod can
change its own effects while fights run: sf2.fx.on_frame
calls your Lua a few times a second with a read-only view of the fight, and
sf2.fx.set changes an effect’s numbers or switches it off.
Mod settings are simple on/off switches that appear under Options > Mod settings, grouped under a header with your mod’s name. Link an effect to a switch and players can turn it off without disabling your mod.
Things to know first:
- Capabilities.
sf2.fx.*andsf2.visuals.*requirepresentation.visuals.sf2.settings.togglerequiresui.settings.sf2.settings.getneeds no extra capability. - When. Every function except
sf2.settings.getis a registration: call it while your mod loads (at the top level of your scripts), not from callbacks. - Stacking. A mod may add up to 128
sf2.fxeffects, and effects from different mods all run together. Each preset has one owner: if two enabled mods configure the same preset, the second fails to load with a conflict error. - Presentation only. Effects and setting values are never written to the player’s profile and are not part of the content fingerprint, so adding or removing a visuals mod never affects saves. Setting values are stored per installation.
- Where they apply. Fighter effects (trails, glints, node particles, the
rim light) can also run on fighter previews in menus such as the shop and
profile; for
sf2.fxeffects setscenes = "everywhere". The shop renders the selected dojo’s animated layers, location overlays, ambient particles and depth haze, and supports continuous screen grades and bloom on its world camera. Locationmatch/excludefilters use that dojo’s name. Screen-space shop controls remain outside the world-camera effects. Background parallax responds to the moving fight camera; the shop uses a fixed view. Hit effects, contact shadows, stains and combat-triggered grades still require their fight events. - Triggers. Most effects run continuously. Hit particles, stains and screen
grades with a
triggerfire once per matching hit instead:hitis any unblocked hit,criticala critical hit,blocka blocked hit (particles only) andkothe hit that empties a fighter’s health. Screen grades and screen overlays also acceptround, which fires as each round begins, andoutroandoutro_land, which fire as the knockout close-up begins and as its zoom arrives. A screen grade withtrigger = "interval"fires by itself every few seconds (lightning, for example), and one withtrigger = "script"fires only when your Lua callssf2.fx.play. Hit lights flash onhit,critical,blockorko. - Motion triggers. Contact particles and screen grades can also fire from
how a fighter moves:
landwhen a fall ends on the floor,knockdownwhen the body hits the floor,sliderepeatedly while the feet skid fast along the floor (contact particles only) andwallwhen a fighter plays a wall-hit recoil. See contact particles. - Accessibility. The impact effect is also scaled by the player’s Critical hit shake accessibility slider on critical hits and by the Hit shake slider on other hits.
- Defaults. Every numeric field is optional. Omitted fields use the defaults shown. Out-of-range values and unknown fields raise an error.
Complete examples ship with Eclipse. Final Blow (Mods/final-blow) is a
kill cam built only from screen grades with zoom. Shadow Clones
(Mods/shadow-clones) and Umbra, the Last Eclipse (Mods/umbra) fire
script-triggered grades from Lua with sf2.fx.play. See Examples.
Custom FX Showcase
(Mods/example.custom-fx) builds weapon sparks, kick trails, ground fog, a
dojo light wash and a screen grade from building blocks. Chiaroscuro -
Cinematic Visuals (Mods/chiaroscuro) builds weapon trails, light shafts
with drifting dust, a slow-motion knockout fade, film halation and grain, floor
stains, and light from weapons and magic from building blocks. It uses presets for background
depth, depth haze and the rim light, and gives each effect its own switch.
Chiaroscuro 2 (Mods/chiaroscuro2) shows off nearly everything on this page:
about a hundred effects behind 30 switches, including weather and fog chosen by
stage, inked weapon trails with crisp edges and thrust streaks, god rays, heat haze, lightning storms, stage and hit lights that shade the
fighters, a knockout kill cam, a fight that reacts through sf2.fx.on_frame, and
the lacquer HUD. It owns the same background depth, depth haze and rim light
presets as Chiaroscuro, so enable one or the other.
Building blocks
Section titled “Building blocks”Every sf2.fx function takes one table and returns the effect’s qualified
name, <mod-id>.<id>. These fields work on every building block:
| Field | Required/default | Meaning |
|---|---|---|
id |
Required | 1–64 lowercase ASCII letters, digits, _ or -, unique among your mod’s effects. |
setting |
nil |
A handle from sf2.settings.toggle. The effect runs only while the switch is on. |
tags |
Every stage | Up to 16 stage tags. The effect runs only on stages with one of them, such as { "fire" } or { "day", "dusk" }. This is the usual way to pick stages. |
exclude_tags |
None | Up to 16 stage tags. The effect never runs on a stage with one of them, even if tags also fits. |
match |
Every location | Up to 32 lowercase words found in the stage’s name. Names are split on _, -, :, /, spaces and digits, so new_year_24_china_dojo gives new, year, china and dojo. A name says which stage it is, not what it looks like, so prefer tags; use match to pick out one particular stage. |
exclude |
None | Up to 64 lowercase words. The effect never runs in a location whose name contains one of them. |
When an effect gives more than one of these, a stage has to pass all of them: one
of the tags (when given), one of the match words (when given), and none of the
exclusions. Every one of the game’s stages is tagged; see
Stage tags for the list and for tagging
your own stages.
-- Embers over every burning stage, and fireflies among trees, but not in the snow.sf2.fx.particles { id = "embers", tags = { "fire" }, placement = "front", blend = "additive", shape = "glow", color = "#FFB050", count = 60 }sf2.fx.particles { id = "fireflies", tags = { "foliage" }, exclude_tags = { "snow" }, placement = "front", blend = "additive", shape = "glow", color = "#D8FF8AE0", count = 30 }Colours are #RRGGBB or #RRGGBBAA strings. blend = "additive" makes an
effect glow (it adds light); the default "alpha" paints over what is behind.
Distances and speeds are in location units, where a fighter is roughly 300
units tall; speeds are units per second.
Fighter nodes. Trails and node particles name points on a fighter. Useful names, taken from the game’s own move data:
| Node | Where |
|---|---|
NPivot |
Centre of the body |
NTop, NNeck, NChest, NStomach |
Head top, neck, chest, stomach |
NWrist_1, NWrist_2, NKnuckles_1, NKnuckles_2 |
Wrists and fists (_1 main side, _2 other side) |
NKnee_1, NKnee_2, NHeel_1, NHeel_2, NToe_2 |
Legs and feet |
Weapon-Node2_1, Weapon-Node2_2 |
Near the main-hand and off-hand weapon |
Magic-Node2_1, Ranged-Node2_1 |
Magic and ranged attachment points |
A node a fighter does not have is skipped for that fighter.
sf2.fx.particles
Section titled “sf2.fx.particles”Signature: sf2.fx.particles { id = "...", placement = "behind", color = "#RRGGBBAA", count = 100, ... }
Returns: The effect’s name, <mod-id>.<id>.
When: During loading.
Requires: presentation.visuals. A sprite must be a handle from
sf2.assets.sprite.
Adds a particle emitter. With placement = "background", "behind" or
"front" the particles fill the location; with placement = "node" (or just
node = "...") they stream from one node of each selected fighter, and keep
flowing behind it as it moves. With placement = "hit" nothing plays until a
fighter is struck: then count particles burst from the contact point, fly
outward at speed_min–speed_max, fall with gravity and fade out. For hit
particles, fighters selects the fighter being struck. With
placement = "contact" the particles burst where a fighter meets the floor or
a wall; see contact particles.
| Field | Default | Meaning |
|---|---|---|
placement |
"behind" ("node" when node is set) |
background: on the background layer nearest depth. behind: just behind the fighters. front: in front of the fighters. node: at a fighter node. hit: a burst at each matching hit. contact: a burst at each matching landing, knockdown, skid or wall impact. |
node |
— | Required for node placement. See the node table. |
fighters |
"both" |
Node placement only: both, player or opponent. In menu previews the fighter counts as the player. |
scenes |
"fights" |
Node placement only: everywhere also runs on menu previews. |
sprite |
Soft dot | Your particle image. |
color, end_color |
White, nil |
Start colour, and the colour each particle fades to by the end of its life. |
blend |
"alpha" |
alpha or additive. |
count |
100 |
1–1000 particles alive at once. |
lifetime_min, lifetime_max |
6, 10 |
0.05–60 seconds; min must not exceed max. |
size_min, size_max |
3, 6 |
0.1–400 units. |
velocity_x_min, velocity_x_max |
-10, 10 |
Horizontal speed range, −2000 to 2000; positive is right. |
velocity_y_min, velocity_y_max |
-10, 10 |
Vertical speed range; positive is up. |
noise |
5 |
0–500 random wandering. |
spin |
0 |
0–1: random start rotation, 1 is a full turn. |
depth |
0.3 |
0–1 for background placement: 0 nearest the fighters, 1 the farthest layer. |
area_width, area_height |
1.1, 1.1 |
0–2: emission area as a fraction of the location size. |
radius |
20 |
0–500: emission radius around a node, hit or contact point. |
x, y |
0, 0 |
Location placements: centre of the emission area, as an offset from the location centre in location units; up is positive. A stage is about 2300 × 512 units, with the floor about 80 units above its bottom edge. |
trigger |
"hit" for hit placement, "land" for contact placement |
Hit placement: hit, critical, block or ko. Contact placement: land, knockdown, slide or wall. Other placements run continuously and do not accept a trigger. |
speed_min, speed_max |
0, 0 |
0–5000: outward burst speed of hit and contact particles; min must not exceed max. The velocity_* ranges are added on top. |
gravity |
0 |
−5000 to 5000: downward pull on hit and contact particles in units per second squared; negative values rise. |
shape |
"dot" |
Built-in art when there is no sprite: dot (a soft dot), glow, ring, star (a four-pointed glint), smoke (an uneven puff) or shard (a thin splinter). A particle effect takes a sprite or a shape, not both. |
stretch |
0 |
0–4: draws each particle stretched along its velocity, for sparks and rain. |
grow |
1 |
0–8: each particle’s size at the end of its life as a multiple of its start size. 3 makes smoke billow and rings expand into shockwaves; below 1 shrinks. |
interval |
0 |
0–30 seconds, location and node particles only: instead of a steady stream, count particles burst out every interval seconds (breath, puffs). Each emitter starts at its own point in the cycle. |
facing |
0 |
Node particles only. 1 makes velocity_x point the way the fighter faces (positive is forward), so breath leaves the mouth whichever way the fighter turns. 0 keeps it pointing right on the screen. |
Location particles also appear in the dojo and the shop’s selected-dojo backdrop.
Node particles appear on menu previews with scenes = "everywhere"; hit/contact
particles require fight events. Every particle fades in and out.
sf2.fx.particles { id = "blade_sparks", node = "Weapon-Node2_1", scenes = "everywhere", blend = "additive", color = "#FFC060", end_color = "#FF400000", count = 40, lifetime_min = 0.3, lifetime_max = 0.7, velocity_y_min = 10, velocity_y_max = 60, radius = 6,}
-- Sparks thrown from the contact point of every unblocked hit.sf2.fx.particles { id = "hit_sparks", placement = "hit", trigger = "hit", blend = "additive", color = "#FFD27A", end_color = "#FF5A1E00", count = 10, lifetime_min = 0.18, lifetime_max = 0.35, size_min = 2, size_max = 5, speed_min = 250, speed_max = 700, gravity = 900, radius = 4,}
-- A shockwave: one ring at the contact point of each critical hit, expanding-- to six times its size as it fades.sf2.fx.particles { id = "critical_ring", placement = "hit", trigger = "critical", blend = "additive", shape = "ring", color = "#FFE8C8B0", end_color = "#FFE8C800", count = 1, radius = 0, lifetime_min = 0.35, lifetime_max = 0.35, size_min = 44, size_max = 44, grow = 6,}
-- Breath: a small puff from each fighter's neck every 2.2 seconds.sf2.fx.particles { id = "breath", node = "NNeck", match = { "snowy", "ice" }, shape = "smoke", color = "#EEF4FA55", end_color = "#EEF4FA00", count = 4, radius = 5, lifetime_min = 0.9, lifetime_max = 1.5, size_min = 5, size_max = 10, grow = 3.2, velocity_y_min = 12, velocity_y_max = 35, interval = 2.2,}Contact particles
Section titled “Contact particles”Contact particles (placement = "contact") are bursts that play where a fighter
meets the arena: dust from the floor, or grit knocked off a wall. Nothing plays
until the fighter’s movement matches the trigger, then count particles burst
at the contact point, just in front of the fighter, and behave like hit
particles (speed_*, gravity, velocity_*, radius, fading out).
| Trigger | Fires when | Where |
|---|---|---|
land (default) |
A fall from more than about 45 units above the floor ends with the feet on the floor. Small hops do not count. | On the floor under the lowest part of the body. |
knockdown |
The body’s centre drops below about 60 units above the floor while falling fast. At most once every 0.6 seconds per fighter. | On the floor under the body’s centre. |
slide |
The feet are on the floor while the body moves along it faster than about 420 units per second: skids, pushback and dashes. Repeats about every 0.07 seconds, so keep count small. |
On the floor under the feet. |
wall |
The fighter starts a wall-hit recoil: the WallHit or WallHitFall move the game plays when a fighter is knocked into the arena wall. Walking or being pushed against a wall does not count. |
At the edge of the body facing the nearer wall. |
A standing fighter is about 300 units tall, for scale. fighters selects the
fighter that is moving. Contact particles run in fights; with
scenes = "everywhere" landings, knockdowns and skids also play on menu
previews, but wall needs a fight’s arena walls.
The triggers are read from the fighter’s animated pose, not from the fight’s rules, so they only add presentation: they never change the fight. They use game time, so nothing fires while the game is paused. A fighter placed at a new position (such as at the start of a round) does not count as sliding.
-- Dust from heavy landings, and debris from wall impacts.sf2.fx.particles { id = "landing_dust", placement = "contact", trigger = "land", color = "#C9B89A66", end_color = "#C9B89A00", count = 12, radius = 16, lifetime_min = 0.45, lifetime_max = 0.9, size_min = 16, size_max = 34, speed_min = 10, speed_max = 40, velocity_x_min = -140, velocity_x_max = 140, velocity_y_min = 15, velocity_y_max = 60, noise = 15,}
sf2.fx.particles { id = "wall_debris", placement = "contact", trigger = "wall", color = "#5E4E3EFF", end_color = "#5E4E3E00", count = 16, radius = 20, spin = 1, lifetime_min = 0.5, lifetime_max = 0.9, size_min = 3, size_max = 8, speed_min = 200, speed_max = 480, gravity = 900,}sf2.fx.overlay
Section titled “sf2.fx.overlay”Signature: sf2.fx.overlay { id = "...", placement = "background", depth = 0.3, sprite = handle, color = "#RRGGBB", alpha = 1, ... }
Returns: The effect’s name, <mod-id>.<id>.
When: During loading.
Requires: presentation.visuals.
Places an image or a solid colour in the location. In the background it moves
with the background layer nearest depth, so it shares that layer’s parallax;
in front it moves with the fighters’ layer and sits in front of them. With
placement = "screen" it sits on the screen itself, above the fight and below
the HUD, and can appear on a trigger; see screen overlays.
| Field | Default | Meaning |
|---|---|---|
placement |
"background" |
background, front or screen. |
depth |
0.3 |
0–1 for background placement: 0 nearest the fighters, 1 the farthest layer. |
sprite |
Solid colour | Your image. Without one the overlay uses shape. |
shape |
"rect" |
Built-in art used when there is no sprite: rect is a flat colour, shaft a soft vertical light beam that is brightest at the top, glow a soft round light and band a soft horizontal strip (fog banks, light pooled on the floor). A shaft or glow without a size is a quarter of the stage wide (a shaft is also the full stage height); a band without a size is 1.4 stage widths by a quarter of the stage height. Screen overlays add slash and wash. |
color |
White | Multiplies the image, or is the flat colour. |
alpha |
1 |
0–1 extra opacity. |
blend |
"alpha" |
alpha or additive. |
x, y |
0, 0 |
Offset from the location centre; up is positive. |
width, height |
0 |
Size in location units. 0 for either covers the whole view (see shape for beams and glows). |
angle |
0 |
−180 to 180 degrees; positive turns counter-clockwise. Tilt a shaft to slant the light. |
flicker |
0 |
0–1: how far the opacity wavers, like a flame. |
flicker_speed |
6 |
0.1–30: how fast the flicker moves. |
sway, sway_speed |
0, 0.1 |
0–2000 units the overlay drifts to either side, and 0.01–10 sways per second. |
pulse, pulse_speed |
0, 0.5 |
0–1 slow breathing of its size (1 is ±25%), and 0.01–10 breaths per second. |
trigger, hold, duration, reveal, slide |
— | Screen overlays only; see below. |
sf2.fx.overlay { id = "dojo_glow", match = { "dojo" }, placement = "background", depth = 0.8, color = "#FFB060", blend = "additive", alpha = 0.18,}
-- A fog bank drifting slowly in front of the fighters' feet.sf2.fx.overlay { id = "fog_bank", match = { "swamp" }, placement = "front", shape = "band", color = "#D8E2EA", y = -215, width = 3600, height = 150, alpha = 0.16, sway = 160, sway_speed = 0.025, pulse = 0.2, pulse_speed = 0.04,}Screen overlays
Section titled “Screen overlays”A screen overlay (placement = "screen") is drawn on its own canvas above the
fight and below the HUD. Its position and size are in HUD units: the screen is
always 1536 units tall, and its width follows the aspect ratio (2731 at 16:9).
x and y offset it from the centre of the screen, angle turns it, and a
width or height of 0 fits the screen; the built-in slash (a long dry-brush
ink stroke) is 1.1 screens wide and 0.24 tall by default, and wash (a
screen-filling ink wash with a ragged edge on its right) is 1.6 screens by 1.25.
Screen overlays are not graded by sf2.fx.screen, so they keep their colours
through a black-and-white knockout.
With a trigger (hit, critical, ko, land, knockdown, wall or
round) the overlay is hidden until the trigger fires, then shows at full
strength for hold seconds and leaves over duration seconds, in real time.
| Field | Default | Meaning |
|---|---|---|
trigger |
"always" |
When it appears. |
hold |
0 |
0–10 seconds at full strength. |
duration |
0.6 |
0.02–10 seconds to leave. |
reveal |
0 |
0–5 seconds to draw it on from its left end. |
slide |
0 |
0–1: 1 makes it sweep off to the left instead of fading; between 0 and 1 mixes the two. |
-- An ink stroke tearing across the screen on the knockout.sf2.fx.overlay { id = "knockout_slash", placement = "screen", trigger = "ko", shape = "slash", color = "#0A0606", alpha = 0.92, angle = -10, y = 40, reveal = 0.12, hold = 0.55, duration = 0.9,}
-- An ink wash that covers each round's reset, then sweeps away.sf2.fx.overlay { id = "round_wipe", placement = "screen", trigger = "round", shape = "wash", color = "#0A0707", x = 120, hold = 0.15, duration = 0.55, slide = 1,}sf2.fx.trail
Section titled “sf2.fx.trail”Signature: sf2.fx.trail { id = "...", weapon = true, color = "#RRGGBB", ... } or sf2.fx.trail { id = "...", nodes = { "NKnee_2", "NHeel_2" }, ... }
Returns: The effect’s name, <mod-id>.<id>.
When: During loading.
Requires: presentation.visuals. Give exactly one of weapon = true or
two nodes.
Draws a fading ribbon swept by a line on the fighter. weapon = true follows
the weapon’s blade (each hand separately). nodes gives the inner end first
and the moving end second. The ribbon only appears while the moving end is
faster than min_speed, at full strength from full_speed.
Every trail, including the weapon trail preset, behaves the same way:
- Fight time.
lifetimeand speeds are measured in fight time. In slow motion a trail lasts longer and still appears on slowed swings. It holds still during hit-stop and pause, then continues. Menu previews use real time. - Shape. Between frames the line is swept as an arc around its inner end, so fast swings look round at any frame rate. Older parts of the ribbon narrow toward the moving end, giving a crescent, and fade out smoothly.
- Overlaps. Where a swing reverses and the ribbon folds over itself, the
overlap is drawn once, so it does not turn darker or brighter. The same holds
between trails on one fighter with the same
blend: where they overlap, the trail registered first is drawn and the others are hidden. See layering trails. - Jumps. A fighter that teleports, resets for a new round or snaps to a new facing starts a fresh ribbon instead of drawing a streak across the screen.
| Field | Default | Meaning |
|---|---|---|
weapon |
false |
Follow the weapon blade. |
nodes |
— | Two node names. See the node table. |
weapons |
— | Up to 32 lowercase words. Needs weapon = true. The trail then runs only while the fighter has a weapon equipped whose item name or subtype contains one of the words, for example { "staff", "blunt" }. |
fighters |
"both" |
both, player or opponent. |
scenes |
"fights" |
everywhere also runs on menu previews. |
color |
Fighter colour | Ribbon colour. Without it the trail follows the fighter’s colour, including perk tints. |
end_color |
— | #RRGGBB or #RRGGBBAA. The trail’s colour shifts from color to this colour as it ages. |
blend |
"alpha" |
alpha or additive. |
lifetime |
0.11 |
0.02–1 seconds. |
min_speed, full_speed |
900, 2600 |
Speeds of the moving end; full_speed must be greater. |
alpha |
0.55 |
0–1 at the moving end. |
start_alpha |
0.35 |
0–1, fraction of alpha at the inner end. |
Trail looks
Section titled “Trail looks”The fields below are optional. At their defaults the trail looks exactly like the plain ribbon described above, so you can add only the ones you need.
| Field | Default | Range | Meaning |
|---|---|---|---|
falloff |
1 |
0.25–4 | How the ribbon’s opacity rises from the inner end (start_alpha) to the moving end. 1 is a straight ramp. Higher values keep the ribbon faint until close to the moving end, so a swing reads as a cut instead of a smoky fan. |
ribbon |
1 |
0–1 | Multiplies the ribbon’s opacity. Use 0 for a trail that draws only an edge or streaks. |
edge |
0 |
0–1 | Opacity of a crisp line along the path of the moving end. 0 turns it off. |
edge_width |
0.08 |
0.01–0.5 | Depth of the edge line, as a fraction of the line’s length. |
edge_color |
Trail colour | — | #RRGGBB or #RRGGBBAA colour of the edge line. |
thrust |
0 |
0–200 | Width of a soft streak drawn where the moving end travels along the line, as in a stab or lunge. A plain ribbon has no width there. Its opacity follows alpha. 0 turns it off. |
hit_flash |
0 |
0–2 | Opacity added to the whole trail when this fighter lands an unblocked hit with its own body or weapon (not a projectile). It fades over about a tenth of a second. It also works with alpha = 0, for a trail that appears only on hits. |
hit_linger |
0 |
0–1 | Extra seconds the newest part of the trail (the last 0.1 seconds) stays when such a hit lands. |
The edges, ribbons and streaks of all trails on a fighter are drawn in that order. Where they overlap, the earlier one wins, so an edge line stays crisp on top of a ribbon.
All of these fields are numbers, except the colours and weapons. You can
change the numbers while a fight runs with sf2.fx.set.
sf2.fx.trail { id = "kick_trail", nodes = { "NKnee_2", "NHeel_2" }, fighters = "player", color = "#6FB8FF", blend = "additive", lifetime = 0.15, min_speed = 700,}
-- A blade trail that gathers toward the tip, cools to red and leaves a crisp edge.sf2.fx.trail { id = "katana_cut", weapon = true, weapons = { "katana" }, falloff = 2.2, end_color = "#6A1A10", edge = 0.85, edge_width = 0.07,}Layering trails
Section titled “Layering trails”Because each trail can have its own setting, you can build a look from
several trails and give each part its own switch under Options > Mod
settings. Set ribbon = 0 on the layers that should add only an edge or
streaks:
local sf2 = require("sf2")
local trails = sf2.settings.toggle { id = "trails", label = "Weapon trails", default = true }local edges = sf2.settings.toggle { id = "edges", label = "Blade edges", default = true }
sf2.fx.trail { id = "blade", setting = trails, weapon = true, falloff = 2 }sf2.fx.trail { id = "blade_edge", setting = edges, weapon = true, ribbon = 0, edge = 0.85, edge_width = 0.07 }sf2.fx.screen
Section titled “sf2.fx.screen”Signature: sf2.fx.screen { id = "...", trigger = "always", saturation = 1, contrast = 1, brightness = 0, tint = "#RRGGBB", tint_strength = 0, vignette = 0, ... }
Returns: The effect’s name, <mod-id>.<id>.
When: During loading.
Requires: presentation.visuals.
Grades the whole fight picture, in the locations its match and exclude
allow. When several grades are active they combine: saturation and contrast
multiply, brightness adds, tints layer in load order, halation adds, and the
strongest vignette, grain, accent, chromatic aberration, radial blur, cinema
bars, sharpening and camera shake win. For split toning, god rays and
distortion the strongest grade wins together with its colours and settings.
With a trigger other than always, the grade is off until a matching hit or,
for land, knockdown and wall, a matching movement by either fighter (see
contact particles for when they fire). round fires as each
round begins. outro fires as the knockout close-up
begins and outro_land as its zoom arrives; both end when the next round starts, so a
long hold keeps an outro grade on for the whole close-up. With trigger = "interval" the grade fires by itself every
interval seconds, varied by up to interval_jitter of that either way; the first
firing comes one interval into the fight, and paused games and title-screen
sparring wait. With trigger = "script", it waits for your Lua to call
sf2.fx.play.
It then applies fully for hold seconds and eases smoothly back to nothing
over duration seconds. Each new matching hit or movement restarts it. Hold and duration are
measured in real time, so they are not stretched by slow motion.
A triggered screen grade responds to gameplay fights. Background title-screen sparring does not trigger screen grades, their sounds, slow motion, or audio muffle; menu music and UI sounds continue normally. After eight seconds without input on the Home screen, the menu fades away to showcase the CPU fight and native fight sounds fade in at the player’s sound volume. Keyboard, mouse, or controller input restores the menu and silences those fight sounds; the waking input does not activate a menu choice. This does not enable mod screen-grade triggers, their sound effects, or combat callbacks in title-screen sparring.
A triggered grade can also carry sound. sound plays once each time the
trigger fires, at sound_volume times the player’s sound volume (silent when
sound is muted). Give it an array to pick one sound at random each time, and
give each choice its own volume to even out sounds of different loudness.
Sounds start on the frame the trigger fires, since every effect sound is loaded
when a fight starts. They play in real time, so slow motion does not stretch them.
Trim silence from the start of your files, because it delays the sound.
muffle puts a low-pass filter over every other game sound while the grade is
active. The filter follows the grade’s strength, so the fight sounds muffled
at full strength and clears again as the grade fades. Effect sounds skip the
filter, which keeps a knockout sting clear while the fight goes dull
underneath. When several grades muffle at once, the strongest wins.
| Field | Default | Meaning |
|---|---|---|
saturation |
1 |
0–2. 0 is black and white. |
contrast |
1 |
0–2. |
brightness |
0 |
−1 to 1. |
tint, tint_strength |
nil, 0 |
A colour multiplied into the picture, and how strongly (0–1). |
vignette |
0 |
0–1 darkening toward the edges. |
vignette_x, vignette_y |
0, 0 |
−1 to 1: moves the vignette’s centre; -0.35, 0.25 centres it up and to the left, so the lower right is darkest. |
trigger |
"always" |
always, hit, critical, ko, land, knockdown, wall, round, outro, outro_land, interval or script. slide is not accepted because it repeats while a fighter skids. script fires only from sf2.fx.play. Only screen grades accept script and interval. |
interval, interval_jitter |
8, 0.5 |
trigger = "interval" only: 0.5–120 seconds between firings, and 0–1 random variation of that interval either way. |
duration |
0.25 |
0.02–10 seconds for a triggered grade to fade out. |
hold |
0 |
0–10 seconds a triggered grade stays at full strength first. |
time_scale |
1 |
0.05–1, triggered grades only: game speed while the grade is at full strength. Speed returns to normal as the grade fades, so slow motion lasts exactly as long as the grade. The slowest active grade wins. It never overrides a pause or another speed change already in effect. |
grain |
0 |
0–1 fine moving film grain. |
halation, halation_threshold |
0, 0.75 |
0–2 glow strength around bright areas, and the brightness (0–2) where the glow starts. |
halation_color |
"#FF9E6B" |
Colour of the halation glow. |
flicker, flicker_speed |
0, 6 |
0–1 irregular brightness wobble like firelight, and its speed (0.1–30). |
accent, accent_strength, accent_width |
nil, 0, 0.08 |
A colour whose hue keeps its saturation when the grade removes colour, how strongly (0–1), and how close a hue must be (0.01–0.5 of the colour wheel). |
sound |
nil |
Triggered grades only. A native sound name such as "snd_time_shift" (letters, digits, _, - and /), an audio handle from sf2.assets.audio for a sound your mod ships, a table { sound = <name or handle>, volume = 0..1 }, or an array of 1–16 of these to pick one from at random. A choice’s volume (default 1) multiplies sound_volume. A native name that a mod has replaced plays the replacement. A missing sound is skipped with one warning in the log. |
sound_volume |
1 |
0–1 volume of sound, multiplied by the player’s sound volume. |
muffle |
0 |
0–1 low-pass filter over the other game sounds while the grade is active. 1 leaves only a dull thud. Works on always-on grades too, for example an underwater location. |
zoom |
1 |
1–3, triggered grades only: the camera pushes in by this factor while the grade is active. See camera push-in. |
zoom_offset_y |
0 |
−400 to 400, triggered grades only: camera pan during the push-in, like the camera API’s offset_y. Positive values raise the framing so heads stay in view; negative values lower it. |
shake, shake_speed |
0, 22 |
Triggered grades only: 0–1 camera shake (1 is about 26 units), and 1–60 for how fast it moves. The shake stays inside the arena art. |
letterbox |
0 |
0–0.3: height of each cinema bar as a fraction of the screen. |
chromatic |
0 |
0–1 colour fringing that grows toward the edges of the frame. |
radial_blur |
0 |
0–1 zoom blur toward the centre (it shares the impact preset’s blur; the stronger wins). |
split_tone, shadow_tint, highlight_tint |
0, cool teal, warm amber |
0–1 split toning: shadows take shadow_tint and highlights highlight_tint, as overlay colours (mid grey changes nothing). |
god_rays |
0 |
0–2: bright areas stream light toward a point on the screen. |
god_rays_x, god_rays_y |
-0.6, 0.8 |
−1 to 1: where the light comes from, from the left/bottom (−1) to the right/top (1). |
god_rays_follow |
0 |
0–1: how far the rays turn toward the stage’s own light, read from its art (the sun, the moon, a glowing window). At 1 they stream from that light, and take its colour unless god_rays_color is given. god_rays_x and god_rays_y are used where a stage’s light cannot be read. |
god_rays_threshold, god_rays_length, god_rays_color |
0.7, 0.6, warm white |
0–2 brightness at which areas start to stream, 0.1–1 how far the rays reach, and their colour. |
distortion, distortion_speed, distortion_height |
0, 3, 1 |
0–1 heat-haze or underwater wobble, 0.1–20 its speed, and 0.05–1 how much of the screen from the bottom it covers: 1 is the whole frame evenly, 0.45 hot air rising off the floor. |
sharpen |
0 |
0–1: crisps edges, useful on upscaled art. |
monochrome |
0 |
0–1: turns the picture black and white after every other colour change (tints, split toning, kept accent colours), so no other grade can colour it again. With several grades the strongest wins. |
light_wrap, light_wrap_width |
0, 8 |
0–1: bright picture around a fighter spills over the edge of the silhouette, as it does through a real lens, so a fighter against a bright sky or a fire looks lit by it rather than cut out. The inside of the body stays dark. light_wrap_width (1–32) is how far it reaches into the silhouette, in pixels of a 1080-line picture. With several grades the strongest wins. It costs one copy of the screen each frame while it is on. |
sf2.fx.screen { id = "cinema_grade", saturation = 0.9, contrast = 1.08, tint = "#E8F0FF", tint_strength = 0.3, vignette = 0.35,}
-- On the knockout hit, slow down and drain every colour but red, hold, then return.sf2.fx.screen { id = "knockout_fade", trigger = "ko", saturation = 0, contrast = 1.15, accent = "#B01010", accent_strength = 1, accent_width = 0.06, hold = 1.2, duration = 1.6, time_scale = 0.3, -- slow motion while grey sound = "snd_time_shift", sound_volume = 0.8, -- a clear swell over... muffle = 0.75, -- ...a muffled fight}
-- A kill cam: on the knockout hit the camera slams in on the fallen fighter-- while time nearly stops, then drifts back out as the speed returns.sf2.fx.screen { id = "kill_cam", trigger = "ko", zoom = 1.65, zoom_offset_y = -70, hold = 0.7, duration = 2.2, time_scale = 0.12, vignette = 0.5,}
-- A film look: teal shadows, warm highlights, a little halation and grain.sf2.fx.screen { id = "film", split_tone = 0.35, shadow_tint = "#3C6E8C", highlight_tint = "#E0A070", halation = 0.15, halation_threshold = 0.8, grain = 0.18, vignette = 0.3, sharpen = 0.15,}
-- God rays streaming from each daylit stage's own light, and hot air over lava.sf2.fx.screen { id = "sun_rays", tags = { "day" }, god_rays = 0.3, god_rays_follow = 1 }sf2.fx.screen { id = "heat_haze", tags = { "fire" }, distortion = 0.35, distortion_speed = 4, distortion_height = 0.45 }
-- Lightning with thunder, every nine seconds or so over rainy stages.sf2.fx.screen { id = "lightning", match = { "flooded", "swamp" }, trigger = "interval", interval = 9, interval_jitter = 0.6, brightness = 0.55, contrast = 1.3, saturation = 0.6, hold = 0.04, duration = 0.45, sound = { "snd_hermit_lightning", "snd_hermit_lightning2" },}
-- A sound the mod ships, on every critical hit, with no change to the picture.local sting = sf2.assets.audio("audio/crit_sting")sf2.fx.screen { id = "crit_sting", trigger = "critical", sound = sting, sound_volume = 0.6 }
-- One of three impacts at random on the knockout hit. The heavy one is much-- louder than the others, so its own volume brings it down to match.sf2.fx.screen { id = "knockout_impact", trigger = "ko", sound = { { sound = sf2.assets.audio("audio/heavy_impact"), volume = 0.3 }, sf2.assets.audio("audio/impact"), "snd_super_hit1", },}Camera push-in
Section titled “Camera push-in”A triggered grade with zoom above 1 or a nonzero zoom_offset_y moves the
fight camera while it is active. The push follows the grade’s strength: full
during hold, then easing back to the normal view over duration. The view
also moves toward the fighter involved: the struck fighter for hit,
critical and ko, the moving fighter for land, knockdown and wall, and
the x passed to sf2.fx.play for script (without x the
centre stays where it is). When several grades push at once, the strongest
wins; zooms do not multiply each other.
The push always keeps both main fighters in view. If they stand far apart (after a knockback, say) the zoom stops at whatever still fits them both, with room for a body on either side, and the centre only moves toward the focus as far as it can without dropping either fighter. Independent actors are not part of this framing.
The push multiplies whatever zoom the camera already has, including a mod that
owns the camera through camera control. The arena’s edge limits
still apply, so the view never shows past the art, and a native rule that forces
the minimum zoom turns the push off. Strong zoom crops the top of the picture,
and a high jump can still leave the frame vertically;
zoom_offset_y around −40 to −70 keeps heads in view at 1.3–1.7. An always-on
grade cannot push the camera.
sf2.fx.play
Section titled “sf2.fx.play”Signature: sf2.fx.play(effect, options?)
Returns: true when the grade started, or false when it cannot show:
its setting switch is off, its match/exclude words rule out the current
location, or no game presentation is running (as in a headless check).
When: Any time after loading, normally inside a combat callback such as
on_tick or on_animation_start. Calling it again restarts the grade, its
sound and its slow motion from full strength. Hold and duration use real time.
Requires: presentation.visuals. effect is the name a screen grade with
trigger = "script" registered by this mod returned, or its local id. Another
mod’s effect, or a grade with any other trigger, raises an error.
| Option | Default | Meaning |
|---|---|---|
x |
nil |
Arena X (−10000 to 10000) that the grade’s camera push-in frames, for example a fighter’s snapshot().self.position.x. |
This is how Lua makes a moment happen: a boss changing phase, a summon, a bolt of lightning. The grade carries everything else: colour, slow motion, sound, muffle and zoom. It is presentation only and never changes the fight.
-- During loading:local stall = sf2.fx.screen { id = "stall", trigger = "script", saturation = 0.2, contrast = 1.4, vignette = 0.75, hold = 0.3, duration = 1.1, time_scale = 0.3, zoom = 1.3, zoom_offset_y = -40, sound = "snd_time_shift",}-- Inside a combat callback, when your ability fires:local view = fighter:snapshot()if view then sf2.fx.play(stall, { x = view.self.position.x }) endsf2.fx.shadow
Section titled “sf2.fx.shadow”Signature: sf2.fx.shadow { id = "...", alpha = 0.45, width = 150, height = 28, fade_height = 350, min_scale = 0.35, color = "#000000", fighters = "both" }
Returns: The effect’s name, <mod-id>.<id>.
When: During loading.
Requires: presentation.visuals.
Draws a soft oval on the floor under each selected fighter, just behind the
fighter’s body. As the fighter’s lowest point rises above the floor, the shadow
shrinks toward min_scale and fades out, reaching nothing at fade_height.
The floor is the lowest point any fighter’s skeleton has reached in the fight,
so it settles as soon as the fighters stand. Shadows run in fights only.
| Field | Default | Meaning |
|---|---|---|
alpha |
0.45 |
0–1 opacity on the ground. |
width, height |
150, 28 |
Oval size in fighter units (1–2000 and 1–1000). A standing fighter is about 300 units tall. |
fade_height |
350 |
1–5000: height above the floor at which the shadow has faded out. |
min_scale |
0.35 |
0–1: shadow size at fade_height, as a fraction of full size. |
color |
Black | Shadow colour; its alpha multiplies alpha. |
fighters |
"both" |
both, player or opponent. |
sf2.fx.shadow { id = "contact_shadow", alpha = 0.42, width = 150, height = 26, fade_height = 320 }sf2.fx.glint
Section titled “sf2.fx.glint”Signature: sf2.fx.glint { id = "...", interval = 3, duration = 0.35, size = 22, alpha = 0.9, max_speed = 250, color = "#FFF7E6", fighters = "both", scenes = "fights" }
Returns: The effect’s name, <mod-id>.<id>.
When: During loading.
Requires: presentation.visuals.
Every interval seconds or so, a small four-pointed star of light runs along
one of the fighter’s weapon blades, from near the grip to the tip. It swells
and fades over duration. A glint starts only while the blade’s tip is moving
slower than max_speed, so it appears on a held weapon, not during swings. It
uses the same blades as weapon trails; bare hands have none. It draws in front
of the fighter with additive light.
| Field | Default | Meaning |
|---|---|---|
interval |
3 |
0.2–60 seconds between glints (varied by ±30%). |
duration |
0.35 |
0.05–3 seconds for one glint to travel and fade. |
size |
22 |
1–400: length of the star’s arms in fighter units. |
alpha |
0.9 |
0–1 peak brightness. |
max_speed |
250 |
0–20000: fastest tip speed at which a glint may start. |
color |
Warm white | Glint colour. |
fighters |
"both" |
both, player or opponent. |
scenes |
"fights" |
everywhere also runs on menu previews. |
sf2.fx.glint { id = "blade_glint", scenes = "everywhere", interval = 4, duration = 0.4, size = 26 }sf2.fx.light
Section titled “sf2.fx.light”Signature: sf2.fx.light { id = "...", source = "weapon", weapons = { "fire" }, color = "#FFB061", radius = 320, intensity = 1, fighter_light = 0.8, glow = 0.3, glow_size = 380, flicker = 0.15, flicker_speed = 8, fighters = "both", scenes = "fights", tags = { ... } }
Returns: The effect’s name, <mod-id>.<id>.
When: During loading.
Requires: presentation.visuals.
Makes something carry a light. There are five sources: weapons, magic, a fixed
point of the stage, the stage’s own light, and the hits themselves. With source = "weapon", every fighter whose
equipped weapon matches one of the weapons words carries a light at each weapon
hand (Weapon-Node2_1 and Weapon-Node2_2). A word matches when it appears
anywhere in the weapon’s item name or subtype, ignoring case: "fire" matches
WEAPON_FIRE_BATONS and the FireBatons subtype. With source = "magic", a
fighter casting magic (any move with the native MagicPlayer template) carries
a light at its magic hand, and every magic projectile in flight carries one at
its centre. A projectile’s light goes out the moment it strikes something
(hit or blocked) or starts its end animation, and fighters it was lighting
lose that light at once. With source = "location", the light sits at x, y
in the stage (offsets from its centre, up positive), like the sun, the moon or a
fire below the floor. With source = "stage", the light is the stage’s own key
light, read from its background art when the fight loads: it sits where the
brightest light in the picture is (the sun, the moon, a glowing gate, a lantern-lit
lake), takes that light’s colour unless you give color, and is as strong as that
light stands out from the rest of the picture (from about a third to full
intensity). One light then suits every stage, including other mods’ stages. With
source = "sky", the light falls from above: it shades the fighters’ bodies only
(set body_light), from nothing at the floor to full strength height units above
it, so heads and shoulders lift out of the black while the feet stay dark. It takes
the stage’s light colour unless you give color. With
source = "hit", a light flashes at the contact point
of every matching hit (trigger = hit, critical, block or ko, default
hit) and fades out over duration seconds of real time.
Each light does two things:
- It lights the stage. A soft additive glow of
glow_sizeis drawn just behind the fighter at the light. - It lights the fighters. Every fighter within
radius, including the one carrying it, gets a lit edge on the side facing the light, in the light’s colour. The strength falls off with the square of the distance. Several lights blend by strength. This uses the rim-light edge, so it works with or withoutsf2.visuals.rim_light; with the preset, the lit edge turns from its usual side toward the light.
Fighters are lit by the lights from the previous rendered frame, so every fighter sees every light regardless of update order.
With body_light above 0, a light also shades the fighters’ whole bodies, not
just their edge: each pixel of a silhouette brightens toward the light’s colour
with distance falloff, so a fire baton warms the arm and chest nearest to it.
Every part of a fighter is lit the same way, so overlapping limbs and clothing
stay one silhouette. Up to eight such lights count at once, the strongest first.
With aim above 0, a light also turns the fighters’ lit edge to face it, however
bright it is: at 1 the edge sits fully on the side toward the light and swings
round smoothly as a fighter moves past it. A light with intensity = 0 and
aim = 1 steers the rim light without lighting anything
itself. The edge only shows where the rim light or another light colours it.
| Field | Default | Meaning |
|---|---|---|
source |
"weapon" |
weapon, magic, location, stage, sky or hit. |
weapons |
— | Required for weapon lights, not allowed for other sources: up to 32 lowercase words found in glowing weapons’ names. |
x, y |
0, 0 |
Location lights only: position in the stage, as an offset from its centre in location units; up is positive. |
trigger, duration |
"hit", 0.12 |
Hit lights only: which hits flash (hit, critical, block or ko), and 0.02–2 seconds to fade. |
body_light |
0 |
0–1: how strongly it shades the fighters’ whole bodies. 0 lights only their edge. A sky light needs it above 0 to show. |
aim |
0 |
0–1: how far fighters within radius turn their lit edge to face this light, whatever its brightness. |
height |
260 |
Sky lights only: 10–2000, how far above the floor the light reaches full strength, in fighter units. |
color |
Warm orange | Light colour. Stage lights default to the stage’s own light colour. |
radius |
320 |
10–5000: how far the light reaches fighters, in fighter units (a standing fighter is about 300 tall). |
intensity |
1 |
0–2 overall strength. |
fighter_light |
0.8 |
0–1: how strongly it lights fighters. 0 only lights the stage. |
glow, glow_size |
0.3, 380 |
0–1 opacity and 1–5000 size of the stage glow. glow = 0 draws none. |
flicker, flicker_speed |
0.15, 8 |
0–1 irregular waver of the strength, and its speed (0.1–30). |
fighters |
"both" |
Whose weapons or magic carry the light. A projectile counts as the fighter who cast it. |
scenes |
"fights" |
Weapon lights only: everywhere also lights menu previews. |
A light without glow lights only the fighters; with glow, location and hit
lights also draw a glow on the stage at their position.
sf2.fx.light { id = "fire_weapons", source = "weapon", scenes = "everywhere", weapons = { "fire", "flame", "lava" }, color = "#FF8A3A", radius = 340, glow = 0.3, flicker = 0.25, flicker_speed = 9,}sf2.fx.light { id = "magic_light", source = "magic", color = "#C8B8FF", radius = 420, intensity = 1.2 }
-- The moon lighting the fighters from the upper right on night stages.sf2.fx.light { id = "moonlight", source = "location", tags = { "night" }, x = 900, y = 600, color = "#9DB8FF", radius = 3200, intensity = 0.8, fighter_light = 0.55, body_light = 0.08, glow = 0,}
-- Every stage lights the fighters from wherever its own light is, in its colour.sf2.fx.light { id = "stage_light", source = "stage", exclude_tags = { "fire" }, radius = 3600, intensity = 0.85, fighter_light = 0.55, body_light = 0.08, glow = 0, flicker = 0,}
-- The rim light faces each stage's own light. This light adds no light of its own.sf2.fx.light { id = "rim_follows_light", source = "stage", radius = 5000, intensity = 0, glow = 0, flicker = 0, aim = 1 }
-- A faint light from above in the stage's colour: heads catch it, feet stay in shadow.sf2.fx.light { id = "top_light", source = "sky", body_light = 0.14, height = 280, glow = 0, flicker = 0 }
-- A warm flash at every clean hit, lighting both fighters for an instant.sf2.fx.light { id = "hit_light", source = "hit", trigger = "hit", color = "#FFD7A0", radius = 520, intensity = 1.2, fighter_light = 0.9, body_light = 0.35, glow = 0.3, glow_size = 240, flicker = 0, duration = 0.12,}sf2.fx.stain
Section titled “sf2.fx.stain”Signature: sf2.fx.stain { id = "...", trigger = "hit", color = "#590808", alpha = 0.8, count = 3, size_min = 10, size_max = 26, spread = 40, flatten = 0.35, limit = 60, speed_min = 90, speed_max = 210, gravity = 900, lift = 80, merge_radius = 16, max_pool_size = 120, sprite = handle, blend = "alpha", fighters = "both" }
Returns: The effect’s name, <mod-id>.<id>.
When: During loading.
Requires: presentation.visuals. A sprite must be a handle from
sf2.assets.sprite.
Leaves splats on the floor under matching hits. With the default speed_max = 0,
count splats appear immediately below the contact point, scattered up to spread
to either side. Set a positive speed_max to launch visible droplets instead.
Droplets follow the strike’s impulse direction with a small random angular spread,
an upward lift, and downward gravity. Critical hits multiply launch speed by
1.25; knockouts by 1.5 (these multipliers do not stack). No damage or physics changes.
For droplets, spread adds random horizontal speed in arena units per second;
the splat appears where the droplet crosses the floor, without another random offset.
A zero impulse launches upward. Airborne drops use 75% of their eventual splat
width, with height equal to 80% of that airborne width, so the spray stays visible
at normal fight zoom. Flight follows game time, including slow motion,
and freezes while the fight is paused.
Landed splats are flattened by flatten and stay in arena coordinates until the
next round begins. They draw over foreground floor artwork, with fighter silhouettes
masked out so blood does not paint over feet. The built-in renderer needs a stencil
buffer for this mask. Blocked hits never stain. Without a sprite, three built-in
splat shapes are used at random. A custom sprite is used for both drops and splats.
The floor is sampled from fighter skeletons as for sf2.fx.shadow;
this is a flat arena plane, not collision against the floor sprite’s pixels.
Stains run in fights only.
Set merge_radius above zero to enable accumulation. A landing merges into the
nearest unsaturated pool of the same effect ID within that horizontal distance.
Its area and opacity increase, keeping its original position and irregular shape.
Width stops at max_pool_size; subsequent landings create new pools once nearby
pools are full. Different effect IDs never merge, even within the same mod.
max_pool_size must be at least size_max when merging is enabled.
limit counts landed pools, removing the oldest when necessary. The arena also
caps all stain effects together at 128 airborne droplets and 512 landed pools,
recycling the oldest at capacity. Droplets still airborne after eight game seconds
are discarded. Renderers are reused, with at most 128 inactive renderers cached.
Turning off an effect’s setting removes its droplets and pools; round reset and
arena teardown clear them too. Existing definitions keep instant, separate stains
unless they opt into the new fields.
| Field | Default | Meaning |
|---|---|---|
trigger |
"hit" |
hit, critical or ko. |
fighters |
"both" |
Which struck fighter leaves stains. |
color |
Dark red | Stain colour; its alpha multiplies alpha. |
alpha |
0.8 |
0–1 opacity (each splat varies slightly). |
count |
3 |
1–12 splats per hit. |
size_min, size_max |
10, 26 |
1–400 splat width in fighter units; min must not exceed max. |
spread |
40 |
0–400: instant splat scatter in arena units, or extra droplet horizontal speed in arena units/s. |
flatten |
0.35 |
0.1–1: splat height as a fraction of its width. |
limit |
60 |
1–200 landed pools of this effect kept at once. |
sprite, blend |
Built-in, "alpha" |
Your own splat image, and alpha or additive. |
speed_min, speed_max |
0, 0 |
Launch speed, 0-2000 arena units/s before critical/KO scaling; minimum must not exceed maximum. Zero maximum keeps instant stains. |
gravity |
900 |
Downward acceleration, 50-5000 arena units/s squared. |
lift |
80 |
Upward launch velocity added to the spray, 0-1000 arena units/s. |
merge_radius |
0 |
Horizontal centre distance for accumulation, 0-400 arena units. Zero disables merging. |
max_pool_size |
120 |
Maximum accumulated width, 1-1600 arena units; at least size_max when merging. |
detail |
0 |
0–1: above 0 uses the built-in detailed art instead of the simple splats: flat ink splashes with spikes, flung drops and speckle, and teardrops in flight. Ignored with a sprite. |
stretch |
0 |
0–4, detailed art: a drop still moving sideways when it lands becomes a directional splash, this much longer, with its head toward the throw. |
spread_time |
0 |
0–10 seconds for a landed pool to spread from a third of its size to full size. |
age_time, age_color |
0, dried brown |
0–300 seconds over which pools darken toward age_color. Fresh blood landing on an old pool brings back some of its colour. |
sheen |
0 |
0–1 wet highlight in the stage’s light colour that dulls as the pool dries. |
sf2.fx.stain { id = "hit_stains", color = "#4A0606", count = 3, size_min = 8, size_max = 20, limit = 50, speed_min = 90, speed_max = 210, gravity = 900, lift = 90, merge_radius = 16, max_pool_size = 70,}sf2.fx.stain { id = "knockout_stain", trigger = "ko", count = 5, size_min = 20, size_max = 42 }sf2.fx.on_frame
Section titled “sf2.fx.on_frame”Signature: sf2.fx.on_frame(handler, options?)
Returns: Nothing.
When: During loading, once per mod. The engine then calls handler(view)
during gameplay fights, options.rate times a second (1–30, default 10). It is not
called while the game is paused or during title-screen sparring.
Requires: presentation.visuals.
This is how a visuals mod reacts to the fight: read the copied view and change
your own effects with sf2.fx.set, or fire a script grade with
sf2.fx.play. The view is a fresh table each call; changing it
changes nothing.
| Field | Meaning |
|---|---|
round |
The round number. |
time |
Seconds of fight time so far. |
round_active |
true while the round is being fought. |
outro |
How far the knockout close-up has come: 0 when there is none, rising to 1 as it frames the winner. Use it to change effects with sf2.fx.set as the camera arrives. |
location |
The location’s name. |
player, opponent |
health, max_health, health_fraction (0–1) and arena x of each main fighter. |
distance |
Horizontal distance between the two fighters. |
The handler has the same instruction budget as a combat callback. If it raises an error or runs out of budget it is switched off for the session, with a warning in the log.
-- A vignette that darkens and pulses like a heartbeat below a third of health.local danger = sf2.fx.screen { id = "danger", vignette = 0, accent = "#B01010", accent_width = 0.06 }
sf2.fx.on_frame(function(view) if not view.player then return end local peril = math.max(0, 1 - view.player.health_fraction / 0.33) local beat = math.max(0, math.sin(view.time * (1.2 + peril * 1.4) * math.pi * 2)) ^ 6 sf2.fx.set(danger, { vignette = math.min(1, peril * (0.45 + beat * 0.25)), accent_strength = peril })end, { rate = 10 })sf2.fx.set
Section titled “sf2.fx.set”Signature: sf2.fx.set(effect, values)
Returns: Nothing.
When: Any time after the effect was registered, usually inside an
sf2.fx.on_frame handler or a combat callback.
Requires: presentation.visuals. effect is the name an sf2.fx function
returned, or its local id, and must belong to this mod.
Changes an effect’s numbers while the game runs, or switches it off and on.
values holds any numeric fields of the effect, checked against the same ranges
as at registration, plus enabled (true or false). Colours, shapes, triggers
and sprites cannot change. Changed values last until sf2.fx.reset
or the start of the next fight. Screen grades, lights and overlays pick up new
numbers at once, and so do trails; particle emitters and stains read most of
theirs only when they are built, but enabled works on every effect. A grade that is always
on still cannot be given zoom, shake or a time_scale below 1.
-- Fragment: "fog_bank" and "rain_near" are effects this mod registered earlier.sf2.fx.set("fog_bank", { alpha = 0.3 }) -- thicker fogsf2.fx.set("rain_near", { enabled = false }) -- stop the rainsf2.fx.reset
Section titled “sf2.fx.reset”Signature: sf2.fx.reset(effect)
Returns: Nothing.
When: Any time after the effect was registered.
Requires: presentation.visuals. The effect must belong to this mod.
Puts back every value sf2.fx.set changed and switches the effect
on again. Every fight also starts from the registered values.
sf2.fx.reset("fog_bank")Presets
Section titled “Presets”sf2.settings.toggle
Section titled “sf2.settings.toggle”Signature: sf2.settings.toggle { id = "...", label = "...", description = "...", default = false, group = "...", excludes = other }
Returns: A setting handle. Pass it as setting to a visuals function or to
sf2.settings.get.
When: During loading. Register switches before the effects that use them.
Requires: ui.settings.
| Field | Required/default | Meaning |
|---|---|---|
id |
Required | 1–64 lowercase ASCII letters, digits, _ or -, unique within your mod. It keeps the player’s choice stable across versions, so do not rename it. |
label |
Required | 1–48 characters shown in the Options row, under your mod’s name header. |
description |
nil |
Up to 160 characters describing the switch. Shown in the note beside Back while the switch has focus. |
default |
false |
Value used until the player changes it. |
group |
nil |
1–32 characters: the heading this switch is listed under on your mod’s settings page, such as "Lighting". Groups appear in the order their first switch is registered; switches without a group come first, without a heading. |
excludes |
nil |
A setting handle, or a list of up to 8, for switches of your mod registered earlier that can never be on together with this one. Turning either on turns the other off. Two switches that exclude each other cannot both default to true. If a player’s saved choices have both on (say, from before you added excludes), the earlier switch stays on and this one counts as off. |
A mod may register up to 32 switches. Options > Mod settings first lists every enabled mod that has switches, with how many of them are on. Choosing a mod opens its own page: its switches under their group headings (in two columns on desktop, one scrolling list on phones), and an All switches row that turns every switch on, then off, then back to its defaults. Back returns to the list of mods. Give a mod with many switches a few groups so players can find them.
local sf2 = require("sf2")local trails = sf2.settings.toggle { id = "weapon_trails", label = "Weapon trails", description = "Short trails behind fast weapon swings.", default = true,}
-- Listed under a "Lighting" heading on the mod's settings page.local rays = sf2.settings.toggle { id = "god_rays", label = "God rays", group = "Lighting", default = true }
-- Two looks that should never stack: turning one on turns the other off.local rim = sf2.settings.toggle { id = "rim_light", label = "Rim light", default = true }local wrap = sf2.settings.toggle { id = "light_wrap", label = "Light wrap", excludes = rim }sf2.settings.get
Section titled “sf2.settings.get”Signature: sf2.settings.get(setting)
Returns: true or false, the switch’s current value.
When: Any time after the switch was registered, including from callbacks. The value can change while the game runs.
Requires: A handle from sf2.settings.toggle created by the same script
context. No extra capability.
if sf2.settings.get(trails) then -- The player has trails on.endThe presets below are quicker to set up but fixed in shape. Use them as-is or as a starting point, and build anything else from the blocks above.
sf2.visuals.background_depth
Section titled “sf2.visuals.background_depth”Signature: sf2.visuals.background_depth { strength = 0.6, setting = handle }
Returns: Nothing.
When: During loading.
Requires: presentation.visuals.
Background layers already move at their own parallax factor from the location
file (for example 0.1 for far layers and 1 for the floor). This effect uses
factor ^ (1 + strength) instead, so far layers move proportionally less and
the scene reads deeper. A layer never moves further than its original factor
allows, so the art never runs out at the edges. Layers with factor 0 or 1
and above, the fighters’ own layer and everything in front of it keep their
original movement.
| Field | Default | Range |
|---|---|---|
strength |
0.6 |
0–2 |
setting |
nil |
A setting handle; without one the effect is always on. |
sf2.visuals.background_depth { strength = 0.6, setting = depth }sf2.visuals.weapon_trails
Section titled “sf2.visuals.weapon_trails”Signature: sf2.visuals.weapon_trails { lifetime = 0.11, min_speed = 900, full_speed = 2600, alpha = 0.55, color = "#RRGGBBAA", setting = handle }
Returns: Nothing.
When: During loading.
Requires: presentation.visuals.
Draws a short ribbon from the main-hand weapon’s grip to its tip. Each part of
the ribbon fades out over lifetime seconds, and only appears when the tip is
moving faster than min_speed (model units per second), at full strength from
full_speed. Fists and weapons shorter than a small blade length draw no trail.
The ribbon runs on fight time, sweeps round arcs and tapers.
See sf2.fx.trail for details.
| Field | Default | Range |
|---|---|---|
lifetime |
0.11 |
0.02–0.5 seconds |
min_speed |
900 |
0–20000 |
full_speed |
2600 |
1–40000, must be greater than min_speed |
alpha |
0.55 |
0–1 |
color |
Fighter colour | #RRGGBB or #RRGGBBAA. Without it the trail follows the fighter’s colour, including perk tints. |
setting |
nil |
A setting handle. |
sf2.visuals.weapon_trails { lifetime = 0.12, alpha = 0.5, setting = trails }sf2.visuals.depth_haze
Section titled “sf2.visuals.depth_haze”Signature: sf2.visuals.depth_haze { strength = 0.4, setting = handle }
Returns: Nothing.
When: During loading.
Requires: presentation.visuals.
Fades background layers toward the colour of the location’s farthest layer.
The farther a layer is, the stronger its haze (strength × (1 − factor)). The
fighters’ layer and the foreground are never hazed.
| Field | Default | Range |
|---|---|---|
strength |
0.4 |
0–1 |
setting |
nil |
A setting handle. |
sf2.visuals.depth_haze { strength = 0.35, setting = haze }sf2.visuals.rim_light
Section titled “sf2.visuals.rim_light”Signature: sf2.visuals.rim_light { offset = 2.5, alpha = 0.85, lighten = 0.35, warmth = 0, softness = 0, key_light = 0, ink = 0, ink_color = "#12081C", setting = handle }
Returns: Nothing.
When: During loading.
Requires: presentation.visuals.
Draws a thin lit edge on each fighter’s upper-left side. In fights its colour
is the location’s background colour (the average colour of its farthest layer);
with key_light it turns toward the stage’s own key light instead, the colour of
the brightest light in the stage’s art, so fighters are rimmed orange in a burning
town and pink by lanterns. In menu previews such as the shop and profile it is a
warm neutral light. Either way it is brightened toward white
by lighten. With ink above 0, a fighter’s rim eases toward ink_color
while that fighter casts magic (any move with the native MagicPlayer
template), so the lit edge becomes a dark outline, then eases back.
warmth blends the background-derived light toward warm ivory while preserving
its peak RGB value and configured alpha. Nearby weapon/magic lights and casting
ink still apply their own colors afterward. softness adds a smooth outer fringe
in screen pixels, leaving the solid rim’s color and intensity intact. Zero keeps
the original hard edge. The fringe follows mesh boundary edges, including in menu
previews; it does not blur the fighter or its internal triangle edges. These
options default to zero for existing mods. Native visual validation is pending.
| Field | Default | Range |
|---|---|---|
offset |
2.5 |
0–12 screen pixels |
alpha |
0.85 |
0–1 |
lighten |
0.35 |
0–1 |
warmth |
0 |
0–1: blend toward warm ivory, preserving peak brightness. |
softness |
0 |
0–3 screen pixels of outer feathering. |
key_light |
0 |
0–1: how far the fight colour turns from the background’s average colour to the stage’s key light. 0 keeps the average colour. |
ink |
0 |
0–1: how far the rim turns to ink_color during magic casts. |
ink_color |
"#12081C" |
#RRGGBB or #RRGGBBAA ink colour. |
setting |
nil |
A setting handle. |
sf2.visuals.rim_light { offset = 2.5, alpha = 0.85, lighten = 0.35, warmth = 0.65, softness = 1.25, ink = 0.85, ink_color = "#1A0C26", setting = rim }
-- Rim each fighter in the colour of the stage's own light.sf2.visuals.rim_light { offset = 2.75, alpha = 0.9, lighten = 0.4, warmth = 0.25, key_light = 1, setting = rim }sf2.visuals.bloom
Section titled “sf2.visuals.bloom”Signature: sf2.visuals.bloom { threshold = 0.82, knee = 0.12, intensity = 0.7, setting = handle }
Returns: Nothing.
When: During loading.
Requires: presentation.visuals.
Makes bright pixels glow. Pixels brighter than threshold bloom, with knee
softening the cut-off. This works on the whole picture, so very bright
background art also glows; raise threshold to limit it to effects.
| Field | Default | Range |
|---|---|---|
threshold |
0.82 |
0–2 |
knee |
0.12 |
0–1 |
intensity |
0.7 |
0–4 |
setting |
nil |
A setting handle. |
sf2.visuals.bloom { threshold = 0.85, intensity = 0.6, setting = bloom }sf2.visuals.ambient_particles
Section titled “sf2.visuals.ambient_particles”Signature: sf2.visuals.ambient_particles { density = 1, default_style = "dust", locations = { { tags = { "snow" }, match = { "word", ... }, style = "snow" } }, setting = handle }
Returns: Nothing.
When: During loading.
Requires: presentation.visuals.
Adds drifting particles behind the fighters. The first rule that fits the stage
picks the style: a rule fits when the stage has one of its
stage tags, or when one of its match
words is in the stage’s name. Names are lowercased and split on _, -, :,
/, spaces and digits, so new_year_24_china_dojo gives the words new, year,
china and dojo. Locations that fit no rule use default_style.
| Field | Default | Meaning |
|---|---|---|
density |
1 |
0–4, multiplies the number of particles. 0 draws none. |
default_style |
"dust" |
none, dust, snow, embers or petals. |
locations |
{} |
Up to 32 rules. Each has style and at least one of tags (stage tags) and match (lowercase name words using a-z, 0-9, _ or -). |
setting |
nil |
A setting handle. |
sf2.visuals.ambient_particles { default_style = "dust", locations = { { tags = { "snow" }, style = "snow" }, { tags = { "fire" }, style = "embers" }, -- One particular stage, by name. { match = { "autumn" }, style = "petals" }, }, setting = particles,}sf2.visuals.impact
Section titled “sf2.visuals.impact”Signature: sf2.visuals.impact { critical = 1, head = 0.6, shock = 0.4, duration = 0.3, setting = handle }
Returns: Nothing.
When: During loading.
Requires: presentation.visuals.
Adds a short radial blur and colour split when a native hit effect plays:
critical for critical hits (also scaled by the player’s Critical hit shake
slider), head for head hits and shock for shocks (both scaled by the
Hit shake slider). It fades out over
duration seconds.
| Field | Default | Range |
|---|---|---|
critical |
1 |
0–1 |
head |
0.6 |
0–1 |
shock |
0.4 |
0–1 |
duration |
0.3 |
0.05–2 seconds |
setting |
nil |
A setting handle. |
sf2.visuals.impact { critical = 1, head = 0, shock = 0, setting = impact }sf2.visuals.ink_hud
Section titled “sf2.visuals.ink_hud”Signature: sf2.visuals.ink_hud { quiet = 0.45, quiet_delay = 3, low_health = 0.3, timer_warning = 10, knockout_wash = 0.85, setting = handle }
Returns: Nothing.
When: During loading.
Requires: presentation.visuals.
Restyles the fight HUD in black lacquer and gold leaf, in place, while it is active:
- Health bars become gold-framed lacquer plates with a glossy vermilion fill; lost health lingers as warm ivory, raid bosses are indigo, stacked bars keep their native grey bar below, and in local versus the recoverable stretch is jade.
- The style meter is the same plate with a fill that deepens from ivory through amber, orange, vermilion and crimson to gold.
- Round markers become gold-rimmed beads; a won round is vermilion and thumps in.
- Names and the timer turn warm ivory with a crisp outline; the timer sits on a
soft shade and pulses red in its last
timer_warningseconds. - Style and hit texts stamp in.
- A fighter on their last bar below
low_healthgets a frame that pulses red. - After
quiet_delayseconds without hits, the style meter and perk icons settle toquietopacity (1 keeps them at full). - During the knockout the whole HUD and the on-screen controls fade by
knockout_wash, then return.
Portraits, control buttons and perk icon art are never replaced, and the bars keep the native fill timing, drain direction and smoothing. Switching the preset off (or its setting) restores the native HUD at once, mid-fight included.
| Field | Default | Range |
|---|---|---|
quiet |
0.45 |
0–1 |
quiet_delay |
3 |
0.5–30 seconds |
low_health |
0.3 |
0–0.9 of the last bar |
timer_warning |
10 |
0–60 seconds |
knockout_wash |
0.85 |
0–1 |
setting |
nil |
A setting handle. |
local hud = sf2.settings.toggle { id = "ink_hud", label = "Lacquer HUD", default = true }sf2.visuals.ink_hud { quiet = 0.55, setting = hud }sf2.visuals.knockout_outro
Section titled “sf2.visuals.knockout_outro”Signature: sf2.visuals.knockout_outro { hold = 0.5, lead = 0.3, zoom = 1.7, zoom_time = 0.8, offset_y = -60, snap = 0, setting = handle }
Returns: Nothing.
When: During loading.
Requires: presentation.visuals.
Turns the end of a knockout round into a short cinematic. Normally the winner starts its win pose as soon as its finishing move can be interrupted. With this preset:
- The winner finishes its move and returns to its idle stance. If it has not settled after 3 seconds, the outro carries on anyway.
- The winner holds the idle stance for
holdseconds, then starts its win pose. leadseconds into the win pose, the camera starts closing in on the winner, so the pose is what draws it in. It arrives overzoom_timeseconds and frames the winner alone, so the knocked-out fighter may leave the picture.zoomis how far the camera closes in, as a multiple of its normal scale.offset_ypans the view at the same time; positive values raise the framing (more above the winner’s head shows), negative values lower it. A small change goes a long way: tens of units, not hundreds.snapsets how the camera moves. At0the zoom and pan ease evenly in and out. At1the camera whips onto the winner (most of the pan lands in the first tenth ofzoom_time) and the zoom punches about 5% pastzoombefore settling back, for a hard cinematic cut. Values in between blend the two. Screen effects withtrigger = "outro"start with the close-up and ones with"outro_land"as its zoom arrives (at the snap’s peak, or at the end of an even ease), and the frame view’soutrovalue follows it. The whole stage comes closer with the camera. Background layers, which normally keep their size while the fight camera zooms, grow by the same amount during the close-up, andoffset_ymoves every layer alike, so layers drawn to line up with each other still do. Parallax stays sideways only, as in a normal fight. The camera stays close until the next round starts, then every layer returns to its normal size. When the knockout ends the fight, the result screen opens over the close-up: the picture freezes where the camera framed it, with both fighters still in place, instead of the empty stage a normal ending leaves behind.
A short pose such as a bow can end before the camera arrives; lower lead or
zoom_time if the close-up misses it.
Only rounds won by knockout get the outro. Rounds that end on time, by ring-out or by a fight rule keep the normal ending. The round timer is stopped throughout, and the result is decided before the outro starts, so it never changes who wins. All times are fight time, so the outro stretches with slow motion, for example from a knockout screen grade. Local and online versus matches keep the normal ending.
| Field | Default | Range |
|---|---|---|
hold |
0.5 |
0–3 seconds |
zoom |
1.7 |
1–3 |
zoom_time |
0.8 |
0.05–3 seconds |
lead |
0.3 |
0–3 seconds of win pose before the camera moves |
focus_time |
0.25 |
0–3; legacy, accepted for older mods and has no effect |
offset_y |
-60 |
-400 to 400 arena units |
snap |
0 |
0–1: 0 eases evenly; 1 whips onto the winner and punches the zoom past its target before settling. |
hide_hud |
0 |
1 fades the fight HUD and the on-screen controls out as the close-up begins, and back in when the next round starts. |
setting |
nil |
A setting handle. |
local outro = sf2.settings.toggle { id = "outro", label = "Knockout close-up", default = true }sf2.visuals.knockout_outro { hold = 0.6, zoom = 1.8, setting = outro }
-- A snappier close-up that whips onto the winner, as a clean finishing shot.sf2.visuals.knockout_outro { hold = 0.1, zoom = 1.5, zoom_time = 0.6, snap = 1, hide_hud = 1, setting = outro }
-- Cinema bars for the whole close-up, and a flash and jolt as the camera lands.sf2.fx.screen { id = "outro_bars", setting = outro, trigger = "outro", letterbox = 0.11, vignette = 0.6, hold = 10, duration = 0.6 }sf2.fx.screen { id = "outro_hit", setting = outro, trigger = "outro_land", brightness = 0.45, shake = 0.55, duration = 0.28 }Verification limits
Section titled “Verification limits”The API contract (fields, defaults, ranges, triggers, shapes, capabilities,
conflicts, sf2.fx.play, sf2.fx.on_frame, sf2.fx.set and the shipped
Chiaroscuro, Chiaroscuro 2, Final Blow, Shadow Clones and Umbra packages) is
checked headlessly. The
rendering itself, including hit timing, floor detection for shadows and glint
placement, and when the motion triggers (land, knockdown, slide, wall)
fire, the camera push-in’s framing, the film effects, body lighting, screen
overlays, the lacquer HUD and the knockout outro’s timing and framing, is only verified by playing the game. The land, knockdown and slide
thresholds may be tuned.