Skip to content

fix(rendering): let colors go brighter than white - #710

Merged
stormmuller merged 2 commits into
devfrom
claude/laughing-hamilton-bdd1bn
Oct 6, 2026
Merged

stormmuller merged 2 commits into
devfrom
claude/laughing-hamilton-bdd1bn

Conversation

@stormmuller

Copy link
Copy Markdown
Member

Summary

Implements design/hdr-colors.md.

Color's constructor clamped every channel to [0, 1]. Nothing after it in the pipeline does: the sprite shader multiplies the texture by the tint, instance tints are uploaded as floats, and hdr render targets keep values above 1 for bloom and tone mapping. So the clamp in Color was the only thing stopping a tint from brightening a sprite. Unity, Godot and Bevy all leave their color types unclamped for this.

Changes

  • Color
    • Red, green and blue now have no upper bound. They are still clamped to 0 at the bottom, because negative light has no meaning.
    • Alpha is still clamped to [0, 1], because premultiplied blend factors would go negative on a float render target.
    • toRGBAString writes channels above 1 as 255, since a CSS color can't be brighter than white.
    • fromHSLA now throws for a saturation or lightness outside 0–100. Before, out-of-range values were hidden by the output clamp.
  • UI transitions: no code change. An easing that overshoots (easeInOutBack, easeInOutElastic) now briefly passes a target color above 1 instead of being cut off at 1. Unity and Godot tweens behave the same way. A unit test now covers this.
  • Tests
    • New Color unit tests and a ui-transition-system overshoot test.
    • Two new e2e scenes, each comparing a sprite against an otherwise identical twin in the same frame:
      • tint-brighter-than-texture: on the 8-bit canvas, a gray sprite tinted 1.5 draws 1.5× brighter than one tinted white.
      • hdr-tint-bloom: on an hdr camera with bloom and tone mapping, a sprite tinted 3 blooms more than one tinted white (halo 147 vs 59).
    • Both new specs fail against the old Color.
    • The e2e helper createWhiteSquareImage is now createSquareImage(fillStyle), so a scene can generate a gray texture.
  • Docs: the bloom.md tip and emissive section, the opening of hdr-rendering.md, and a new "Hover and press colors" section in ui/buttons-and-interaction.md.

The solution-reviewer agent returned REVISE on the first plan. Everything it flagged is addressed above: the easing-overshoot reasoning and test, the 8-bit e2e check, a new HDR scene instead of changing bloom-over-background, and the fromHSLA decision.

Follow-up outside this repo: the Galactic Journey demo can rest its buttons at Color.white and drop the "Dimmed at rest" workaround once it depends on a Forge release that includes this change.

Related issue(s)

design/hdr-colors.md

Verification checklist

  • npm run check-types passes with 0 errors
  • npm test passes (1796 tests)
  • npm run lint passes with 0 errors
  • npm run cspell passes with 0 errors
  • npm run check-exports passes
  • Any new/changed public API is exported from the module's index.ts (no new exports)
  • Documentation under /documentation-site/docs/docs is updated
  • Demos verified: rebuilt /dist; ran the docs site's typecheck and build; loaded easing-functions, ui-button, space-shooter and ui-main-menu in Chromium, and they render with no engine errors. No demo code changes were needed.
  • Full e2e suite passes locally (40 tests). check-types:e2e gives the same two errors in text-effects-overlap.ts that it gives on dev.

Changelog

  • A bullet has been added under ## [Unreleased] in CHANGELOG.md (#### Changed)

🤖 Generated with Claude Code

https://claude.ai/code/session_01EBx4SStcu1rxjYyqgkEC1n


Generated by Claude Code

Color no longer clamps red, green and blue to 1, so a tint can brighten a
sprite past its texture and, on an hdr camera, bloom more than a
white-tinted one. Negative channels are still clamped to 0 and alpha to
[0, 1]; toRGBAString writes channels above 1 as 255, and fromHSLA throws
for a saturation or lightness outside 0-100.

Adds e2e coverage for both cases (8-bit canvas and hdr + bloom) and
updates the bloom, HDR and UI button guides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBx4SStcu1rxjYyqgkEC1n
@codecov

codecov Bot commented Oct 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBx4SStcu1rxjYyqgkEC1n
@stormmuller
stormmuller enabled auto-merge (squash) October 6, 2026 17:59
@stormmuller
stormmuller merged commit 5e04736 into dev Oct 6, 2026
13 checks passed
@stormmuller
stormmuller deleted the claude/laughing-hamilton-bdd1bn branch October 6, 2026 18:01
stormmuller pushed a commit that referenced this pull request Oct 6, 2026
…j6xxo

Brings in explicit font atlas URLs (#713) and HDR colors (#710). The game
states demo loads the default font atlas from the package, as the other
demos now do, since static/fonts is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAAGHGoEyhYQhFcyTw37jE
stormmuller added a commit that referenced this pull request Oct 6, 2026
…es (#711)

* feat(states): add game states, run conditions and state-scoped entities

Implements design/game-states.md.

- ecs: `runIf` on `addSystem` and `addSystemGroup`, checked just before
  the system or group runs; a gated system isn't queried.
- ecs: a built-in `firstSystemGroup` that runs before every other group.
  Groups ordered after it run at the start of the tick, before every other
  group, so a state's exit and enter groups run before input and gameplay.
- states: new module with `createGameState`, `inState`/`onEnter`/`onExit`
  and `addStateScopedComponent`.
- Docs: run conditions and the first group in the ECS guides, a new Game
  States guide and a game states demo.
- design/game-states.md: records the start-of-tick placement the exit and
  enter groups need, and the scoped-removal group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAAGHGoEyhYQhFcyTw37jE

* Merge remote-tracking branch 'origin/dev' into claude/serene-mendel-1j6xxo

Brings in explicit font atlas URLs (#713) and HDR colors (#710). The game
states demo loads the default font atlas from the package, as the other
demos now do, since static/fonts is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAAGHGoEyhYQhFcyTw37jE

* docs(states): link the demo where the guide uses it, not in the intro

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAAGHGoEyhYQhFcyTw37jE

* docs(states): name the example state type GameStateName, not Screen

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAAGHGoEyhYQhFcyTw37jE

* docs(states): say what happens without runIf, drop the dependency-injection aside

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAAGHGoEyhYQhFcyTw37jE

* docs(states): describe run conditions plainly instead of as gates

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAAGHGoEyhYQhFcyTw37jE

* docs(states): describe paused systems without implying they hold state, drop round-specific wording

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAAGHGoEyhYQhFcyTw37jE

* docs(states): drop the Time aside from the run conditions section

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAAGHGoEyhYQhFcyTw37jE

* docs(states): drop the input section from the game states guide

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAAGHGoEyhYQhFcyTw37jE

* docs(states): describe enter and exit systems as reacting to transitions, not setting up a state

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAAGHGoEyhYQhFcyTw37jE

* docs(states): drop the several-worlds section from the game states guide

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAAGHGoEyhYQhFcyTw37jE

* docs(skills): make document-feature produce isolated, literal technical documentation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAAGHGoEyhYQhFcyTw37jE

* docs(states): rewrite the game states docs as isolated technical reference

Applies the document-feature skill to the game states guide, the
state-scoped entities guide, the run conditions section of the System
guide, the first group section of the World guide, and the GameState and
firstSystemGroup JSDoc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAAGHGoEyhYQhFcyTw37jE

* docs(skills): document-feature writes feature guides, leaving per-member detail to the API reference

The skill now separates guides (the user manual) from the generated API
reference, requires an outline of headings named after the feature's
concepts and tasks, and bans generic headings such as "Worked example",
"Gotchas" and "Common mistakes". The game states guide is rewritten to it
as a single page; the separate state-scoped entities page is folded in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAAGHGoEyhYQhFcyTw37jE

* docs(states): leave entity removal behavior to the World guide

The game states guide no longer describes what happens to a removed
entity's children; that's removal and parenting behavior, documented
where those features are. The document-feature skill gains the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAAGHGoEyhYQhFcyTw37jE

---------

Co-authored-by: Claude <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.

2 participants