# Color Operations

> Where it lives

- Skill: `vchelaru/color-operations` (Agent Skill)
- Install (CLI): `npx skillmds@latest add vchelaru/color-operations`
- Raw SKILL.md: https://api.skillmd.com/api/skills/vchelaru/color-operations/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: vchelaru (https://skillmd.com/u/vchelaru)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/vchelaru/color-operations

---


## Where it lives

`ColorOperation` enum: `Engines/FlatRedBallXNA/FlatRedBall/Graphics/GraphicalEmumerations.cs`.

It's not Sprite-specific — consumers include:

| Type | File | Notes |
|---|---|---|
| `Sprite` | `Engines/FlatRedBallXNA/FlatRedBall/Sprite.cs` | Full support, all enum values; setter calls `UpdateVertexColorsAccordingToAlpha` |
| `AnimationFrame` | `Engines/FlatRedBallXNA/FlatRedBall/Graphics/Animation/AnimationFrame.cs` | Nullable per-frame override, applied in `Sprite.UpdateToAnimationFrame` |
| `RenderableSkiaObject` (Gum/SkiaSharp text, SVG) | `Engines/SkiaGum/Renderables/RenderableSkiaObject.cs` | **Hardcoded to `Modulate`, read-only** — does not respect Add/Subtract/etc. |
| `Renderer` global state | `Engines/FlatRedBallXNA/FlatRedBall/Graphics/Renderer.cs` | Static `ColorOperation` property drives shader technique selection via `_effectManager.GetVertexColorTechniqueFromColorOperation` |

## Gotchas

- **Skia-rendered objects (Text/Gum via `RenderableSkiaObject`) silently ignore non-Modulate operations** — the `IRenderableIpso.ColorOperation` getter always returns `Modulate`, there's no setter. Don't expect `Add`/`Subtract`/etc. to work outside the XNA `Sprite` render path.
- **Debug-only platform restriction**: on iOS/Android/WinRT, setting `Sprite.ColorOperation` to `Add`, `Subtract`, `InterpolateColor`, `InverseTexture`, `Modulate2X`, or `Modulate4X` throws in DEBUG builds (`Sprite.cs` ~line 410) — these ops aren't guaranteed cross-platform.
- **`"AddSigned"` is not `ColorOperation.AddSubtract`.** It's a separate legacy string op-code handled only in `GraphicalEnumerations.SetColors` (`GraphicalEmumerations.cs` ~line 293), which biases `desiredRed/Green/Blue` by `-127.5` and remaps to plain `"Add"` — no enum value, no `VertexColorPacker` involvement.
- `ColorOperation.Texture` with a null `Texture` behaves like `ColorOperation.Color` (see `Renderer.cs` ~line 3349, `Sprite.cs` ~line 256) — a common "why does my untextured sprite still show color" trip-up.
- **`Sprite.Red/Green/Blue` clamp to `[-1, 1]`, not `[0, 1]`** (`Sprite.cs` ~line 301-304), and nothing downstream re-clamps before GPU upload. Packing (now `VertexColorPacker.Pack`, called from `SpriteManager.cs` ~line 2146) casts the float channel to `uint` via `(uint)(255 * value)`. **A negative float→uint cast here is undefined behavior per ECMA-335** (out-of-range float-to-unsigned conversion is unspecified) and is genuinely runtime-dependent, not just "clamps to 0": on .NET 8/x64 (this repo's actual `net8.0` target) it wraps to byte `129`; on .NET 10 the same code gives `0`. Either way it is NOT a clean subtraction — never rely on negative `Red`/`Green`/`Blue` under `ColorOperation.Add` to produce predictable output; use `ColorOperation.AddSubtract` instead (bias/scale-encodes the signed value so it survives the UNORM byte round-trip predictably).

