Skip to content

Fx reboot, WIP - #299

Draft
trethaller wants to merge 206 commits into
masterfrom
fx-reboot
Draft

trethaller wants to merge 206 commits into
masterfrom
fx-reboot

Conversation

@trethaller

Copy link
Copy Markdown
Contributor

No description provided.

ncannasse and others added 30 commits October 9, 2026 17:49
vLerp folds into the existing flat values when it matches one of them
(the usual cases evaluate exactly as before), and falls back to VLerp,
which getFast evaluates, instead of a non flat VAdd/VMult.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Params are stored under their plain name (scale, startSpeed...), one mode
per component: Constant, Range, Curve, RandomCurve, CurveRange or unset,
the shapes being a curve referenced by path or an easing (EmitterParam).
Particle shaders under an emitter get the same model (Shader.particleParams),
colors keep their curves.

Old files are upgraded on load into equivalent modes (EmitterUpgrade) and
saved in the new format (props.paramsVersion). Upgraded emitters keep the
random slot layout of the old code (skippedRandomSlots), so they draw the
same randoms. The animation curves of the spawn and multiplier params, and
of the emitter settings, keep multiplying the value by name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nes on load

An animation curve (over the FX time) is now the value of the property: the
object transforms (position, scale, rotation in degrees) of FX and FX2D, the
local rotation offset (degrees), the shader params out of the emitters, the
emitter settings, spawn params (value, min, max) and multipliers. The life
time can be animated too.

The emitter params reference their animation curves by path (Emitter /
Emitter2D.animCurves, saved in props.animCurves), curve names carry no meaning
there. The FX objects and shaders still find theirs by name.

Curves are saved with a version; the ones without it are upgraded on load
(AnimUpgrade, old format only), on the raw data from FX.load / FX2D.load and
the emitter load: the old constants are baked into the keys (exact for the
linear cases, a Reference curve takes them in its scale / offset, a Blend
curve bakes its children, the curves referencing a baked one are
compensated), the old emitter curves are bound by path where they are (a
range gets a second curve referencing the first). The rotation / scale of an
object composed with a base rotation is folded exactly when the base
rotation allows it, re-baked from samples otherwise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An animation curve holds the value of the property: a new track starts with a
key at the current value of the property (transform, shader param, light,
renderer FX field, emitter setting or spawn / multiplier bound). The tracks
of the emitter params are bound to them by path (animCurves), a range
component gets a min and a max track.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Overrides saved from now on carry `#anim` (Diff.apply skips the `#` fields
of a diff root). Old overrides set old curve values and the properties they
were composed with: Reference.applyOverrides marks the curves they set as old
and, when the referenced .fx is already upgraded, puts the curves of the
prefabs they touch back in the old format (AnimUpgrade.legacyOverride, the
inverse of the exact bakes), so the upgrade on load bakes them with the
overridden values. A spawn range min that references its max follows the
re-baked bounds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he transform, tiny base angles count as none

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
trethaller and others added 30 commits November 3, 2026 12:23
…the prefab type

getMode / setMode / getParams / getModeParams are EmitterHelper statics taking the prefab (Emitter, Emitter2D, or a Shader under an emitter for the modes), getSettings renamed getParams.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	hrt/prefab/Curve.hx
scaleOverTime and orbitSpeedOverTime are @:animate settings like the
other emitter settings (an animation curve over the emitter time),
sampled once per tick instead of once per particle.
ParamKind.Multiplier is removed; the upgrade writes their constant as
an emitter field and renames their curves to the setting names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Games have emitters in .prefab files, out of any FX (Spacecraft 3 files,
Farever 6): they are the root of their FX data, versioned and upgraded on
load and by the migration tool, as before 3bd8469. Only the outermost one:
FX.inFXData is true under an FX or under an emitter, so a sub-emitter is
upgraded once, with its parent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Random Curve is its curve plus a signed random spread, `spreadMin` where
the curve is at its lowest value, `spreadMax` at its highest, linear in the
value in between: `v + (spread + slope * v) * (2u - 1)`, `spread` and `slope`
folded when the values are built (EmitterParam.spreadLine), from the exact
range of the curve (Curve.getValueRange: the keys and the extrema of the
bezier of each segment), only for Random Curves, once per loaded resource.

The old `(k + a * random) * curve` maps to it exactly (the half width is
affine in the curve as its middle is). The migration converts it with the
range of the baked curve; a width following a curve around a constant
value or along a flat curve becomes a Curve Range (its two bounds as its
datas). The time-based amount (`lerp(from, to, t)`) lost the random of
every curve not linear in time (Farever: 334 FX differed in game, 11 now,
the non deterministic ones and a locally saved file).

Editor: Rand min / Rand max sliders, the band follows the value of the
curve; dragging an edge sets the half width there, the curve stays; flip
in value swaps the spreads, flip in time keeps them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No name remap: the particle rotation param is `particleRotation` on both
emitters (`rotation` is the one of the Object2D).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…urves

Leftovers of the curves multiplying the base value: a scalar shader param
or a renderer FX field without a curve of its exact name is not animated
(was set to its base value, or to 1 for a renderer FX field).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Value with a 4th component was always a color in the data (no `:w` curve in
the games, only `:a`): VVector is x, y, z (2D and 3D), VColor r, g, b, a next
to VHsl. getVectorValue no longer reads `:w` curves, getColorValue and the
particle shader params of 4 components build a VColor (same evaluation). The
emitter params have 3 components at most (EmitterParam.COMPONENTS).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A random between two curves is the Curve Range mode of the emitter params (a curve of two datas).
The migration turns the other random blends into what the old runtime read: a spawn param becomes a
range with its two child curves as bounds, a setting its first child curve (it read the random of the
first particle), any other curve its own keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…atas

CurveBlendMode is an enum saved by name: None, Blend (two datas blended by `param`), Range (two
datas, the Curve Range of the emitter params) and Reference (`refPath`, no keys saved). A blend has
no child curves anymore, the timeline edits each of its datas. The migration writes the new curve
format (it was converted by `Curve.load`), and drops the curves under a plain curve, never read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A param in a curve mode stores no curve: its curve is the child named after the param and
component (`partScale`, `partRotation:z`), the first enabled one, like the other curves of an
emitter. The shader particle params are the names of their curves (`particleParams`). Changing
the mode removes the curve, a curve mode whose curve was deleted shows and saves as the default.
Renames: the params `partX` (their runtime field stays `x`), `seedGroup` -> `seed`,
`scaleOverTime` / `orbitSpeedOverTime` -> `scaleMultiplier` / `orbitSpeedMultiplier`, and in the
code the plain fields are properties (`PROPERTIES`, `PropertyDef`), the fields with modes params
(`PARAMS`, `ParamDef`), `InstanceDef` -> `ParticleDef`. The migration renames the curves the old
runtime read, the unread ones keep their old name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…RandomColor + useRandomGradient

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only used by one unused Farever FX. The migration removes these curves, with a warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
timeScale, burstInterval, emitRateChangeTime, emitStartAngle/emitEndAngle (2D),
emitDomeHeight, animationCycles, partMaxSpeed; their curves renamed by the
migration. Labels: Dampen, Lock Axis, Stretch By Velocity, Kill Chance On
Collision, Particle Scaling, group Emission.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	hide/kit/Slider.hx
#	hrt/prefab/fx/Emitter.hx
#	hrt/prefab/fx/EmitterHelper.hx
The prefix tells the clock of a curve: the Particle params, read over the
particle life, are `partX`; the Spawn params and the properties, read over the
emitter time, are not (`lifeTime`, `startSpeed`, `startWorldSpeed`, renamed by
the migration). A param's name is also the field of its value in the emitter
object: ParticleDef is removed, the particle param values are fields of
EmitterObject / EmitterObject2D like the others, and the macro no longer
strips the prefix. The spec and comments say emitter time where the emitter
curves were said to be over the FX time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (no copy of the keys on FX play)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ut rotation

- A single `scale` curve puts the same Value on x, y and z: it is evaluated once
  (3 times since VectorAnimation, once with the scalar Value of master). About
  half of the animated objects of Farever have one.
- `m.rotate` is skipped when the object has no rotation curve and no base rotation.

Why VectorAnimation (a Value per axis, null for none) and not a Value all the
way (VVector) for the transforms:
- Since the curves hold the value they animate (794ca88), an axis without a
  curve keeps the value of the prefab: each axis is a field of its own (x,
  scaleY, rotationZ...), and partial animations are common (scale or position
  on one or two axes).
- A VVector has no missing component: the prefab value would be copied in as
  a VConst when the animations are built, so an edit of that field in the
  editor would not show until they are rebuilt (or the editor would have to
  rebuild them on every transform change). The null reads the field at each
  sync.
- Shader vectors and colors stay VVector / VColor: one field, replaced as a
  whole by its curves, a missing component a constant (0, 1 for colors), as
  before the migration (their curves were never multiplied, nothing re-baked).
- Plain curves instead of Values on the FX side would duplicate the parameter
  and blend evaluation of the Evaluator, for no runtime gain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… no copies

The emitter objects (3D and 2D) keep a `prefab` field, the clone made for this instance, and
read the plain properties from it when they use them: the ~40 copies of `updateInstance` and
their duplicate fields are removed (with their drifted defaults). The objects keep the `Value`s
built by `makeValues`, overridable at runtime (`emitRate = VConst(10)`), and their state
(`emitScale`, the rate state), grouped like the inspector. The random colors are blended from
the prefab arrays. Game code changes a property on `e.prefab` (spec: Runtime).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants