mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-23 10:02:29 +00:00
fix(render): standardize gradient colorSize to game tiles (#4633)
## Summary Effect cosmetic attributes are meant to be standardized on **game tiles** for spatial values and **per-second** for rates. An audit of every effect attribute against its renderer consumer found one violation: the gradient trail's `colorSize`. - The trail shader computed the palette cycle as `colorSize * 4.0 * count`, so `colorSize: 1` produced ~4-tile bands — while `CosmeticSchemas.ts` already documented `colorSize` as "band width in tiles". - This PR removes the hidden ×4 in `trail.frag.glsl` and the mirrored factor in the structures gradient pace in `structure.frag.glsl`, so `colorSize` is now exactly the band width in tiles, and updates the pace-formula doc comment in `CosmeticSchemas.ts`. All other attributes were verified already standard: gradient `movementSpeed` (tiles/s), transition `frequency` (colors/s), spiral `radius` (tiles) / `rotationSpeed` (rad/s), explosion `size`/`thickness` (tiles), `speed` (tiles/s), `transitionSpeed` (colors/s). Every animated path runs off a `uTime` uniform that is genuinely in seconds. ## Catalog impact - Production `cosmetics.json` ships **zero** effects today, so there is no live migration. - Dev catalog gradient entries need `colorSize` ×4 to preserve their current look (`rainbow_trail` 0.2→0.8, `red_black_trail` 1→4, `tiel_red_gradient_nuke_trail` 0.5→2, `rwb_structure_gradient` 5→20). ## Test plan - `npx vitest tests/CosmeticSchemas.test.ts --run` — 51 passed (schema shape unchanged; this is a shader-semantics fix). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -68,12 +68,10 @@ void main() {
|
|||||||
} else {
|
} else {
|
||||||
// gradient — cyclic gradient banded across the map (world-space diagonal),
|
// gradient — cyclic gradient banded across the map (world-space diagonal),
|
||||||
// scrolling over time so a moving trail shifts hue along it. colorSize
|
// scrolling over time so a moving trail shifts hue along it. colorSize
|
||||||
// scales the band width (colorSize = 1 ≈ 4 tiles per band); movementSpeed
|
// = band width in tiles; movementSpeed = tiles/sec the bands travel.
|
||||||
// = tiles/sec the bands travel.
|
|
||||||
float colorSize = max(texelFetch(uEffect, ivec2(o, rowBase + 2), 0).a, 0.001);
|
float colorSize = max(texelFetch(uEffect, ivec2(o, rowBase + 2), 0).a, 0.001);
|
||||||
float movementSpeed = texelFetch(uEffect, ivec2(o, rowBase + 3), 0).a;
|
float movementSpeed = texelFetch(uEffect, ivec2(o, rowBase + 3), 0).a;
|
||||||
// 4.0 = tiles per band at colorSize 1; tune for default band thickness.
|
float cycle = colorSize * float(count);
|
||||||
float cycle = colorSize * 4.0 * float(count);
|
|
||||||
float phase =
|
float phase =
|
||||||
fract((vWorldPos.x + vWorldPos.y - uTime * movementSpeed) / cycle);
|
fract((vWorldPos.x + vWorldPos.y - uTime * movementSpeed) / cycle);
|
||||||
float f = phase * float(count);
|
float f = phase * float(count);
|
||||||
|
|||||||
@@ -84,13 +84,13 @@ bool structuresEffectColor(int owner, out vec3 color) {
|
|||||||
// gradient is visible across the shape (world-space banding like the
|
// gradient is visible across the shape (world-space banding like the
|
||||||
// trail's would put the entire icon inside one band and read as a flat
|
// trail's would put the entire icon inside one band and read as a flat
|
||||||
// color). It scrolls along the diagonal at the trail-equivalent pace:
|
// color). It scrolls along the diagonal at the trail-equivalent pace:
|
||||||
// one full slide every colorSize · 4 · count / movementSpeed seconds,
|
// one full slide every colorSize · count / movementSpeed seconds,
|
||||||
// so both knobs keep their trail timing semantics.
|
// so both knobs keep their trail timing semantics.
|
||||||
float colorSize = max(texelFetch(uEffect, ivec2(owner, rowBase + 2), 0).a, 0.001);
|
float colorSize = max(texelFetch(uEffect, ivec2(owner, rowBase + 2), 0).a, 0.001);
|
||||||
float movementSpeed = texelFetch(uEffect, ivec2(owner, rowBase + 3), 0).a;
|
float movementSpeed = texelFetch(uEffect, ivec2(owner, rowBase + 3), 0).a;
|
||||||
float dn = (vLocalPos.x + vLocalPos.y) * 0.5; // icon diagonal, -0.5..0.5
|
float dn = (vLocalPos.x + vLocalPos.y) * 0.5; // icon diagonal, -0.5..0.5
|
||||||
float phase =
|
float phase =
|
||||||
fract(dn - uTime * movementSpeed / (colorSize * 4.0 * float(count)));
|
fract(dn - uTime * movementSpeed / (colorSize * float(count)));
|
||||||
float f = phase * float(count);
|
float f = phase * float(count);
|
||||||
int i = int(f) % count;
|
int i = int(f) % count;
|
||||||
int j = (i + 1) % count;
|
int j = (i + 1) % count;
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ const NukeExplosionEffectSchema = CosmeticSchema.extend({
|
|||||||
// separate schema, and the spatial semantics differ:
|
// separate schema, and the spatial semantics differ:
|
||||||
// - "gradient": the palette spans each structure icon's diagonal once (a
|
// - "gradient": the palette spans each structure icon's diagonal once (a
|
||||||
// visible gradient across the shape), sliding one full cycle every
|
// visible gradient across the shape), sliding one full cycle every
|
||||||
// colorSize · 4 · count / movementSpeed seconds (the trail-equivalent pace).
|
// colorSize · count / movementSpeed seconds (the trail-equivalent pace).
|
||||||
// - "transition": the whole icon is one color at a time, cross-fading through
|
// - "transition": the whole icon is one color at a time, cross-fading through
|
||||||
// the list. `frequency` = color changes per second.
|
// the list. `frequency` = color changes per second.
|
||||||
// Colors are unvalidated strings; the renderer drops any it can't parse (and
|
// Colors are unvalidated strings; the renderer drops any it can't parse (and
|
||||||
|
|||||||
Reference in New Issue
Block a user