Repository navigation
fix(text): load font atlases from explicit metrics and image URLs - #713
Merged
Merged
Conversation
FontAtlasCache.getOrLoad now takes { metricsUrl, imageUrl } instead of
resolving the image named in the JSON's atlasImage field next to the JSON,
which broke under bundlers that hash asset names. Concurrent loads of the
same atlas are shared, a conflicting image URL for the same metrics
rejects, and the image's size is checked against atlasSize.
atlasImage is removed from the data, the validator and the generator. The
default font is exported as @forge-game-engine/forge/fonts/default/*.json
and *.png so bundlers can import it, and the docs-site demos now import it
that way instead of keeping a static copy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BnSddjDV7weKQBwDYkmgNA
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
stormmuller
enabled auto-merge (squash)
October 6, 2026 18:03
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BnSddjDV7weKQBwDYkmgNA
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>
This was referenced Oct 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements
design/font-atlas-loading.md.FontAtlasCache.getOrLoad(jsonUrl)used to find the atlas image by readingatlasImagefrom the JSON and resolving it next to the JSON URL. That breaks under any bundler that hashes asset names, and the default font had noexportspath a bundler could import.FontAtlasCache:getOrLoad({ metricsUrl, imageUrl }),get(metricsUrl).metricsUrl. Concurrent calls share one load. A call with the same metrics and a different image URL rejects, whether the first load is still running or finished. A failed load can be retried.atlasSize; on a mismatch it rejects, naming both URLs.load/assetsare private; it no longer implementsAssetCache.resolveRelativeToKeyis deleted.atlasImageis removed fromFontAtlasData,FontAtlasFileData, the validator, the generator andassets/fonts/default/default.json. Old files that still have it load as before, because the validator ignores unknown fields.formatVersionstays2../fonts/default/default.jsonand./fonts/default/default.pngsubpaths.check-exportsexcludes those two entrypoints, since attw checks TypeScript resolution and a PNG can't have types.static/fonts/default, which is deleted.new URL('@forge-game-engine/forge/fonts/default/default.json', import.meta.url).file-loaderalso processesnew URLimage requests and emits JavaScript as the.png. Found while testing; the webpack section of the guide now mentions it.*.pngmodule declaration for the import.text-effects-overlap.ts(formatVersion: 1, missingcapHeight).check-types:e2enow passes.text/index.md,text/loading-a-font-atlas.md(Vite and webpack examples, plus gotchas),text/rendering-text.md,text/generating-a-font-atlas.md,ui/labels-and-text.md,asset-loading/index.md. The "copy the files out ofnode_modules" instructions are gone.Plan review (solution-reviewer): REVISE, three items:
check-exportspasses: excluded the two asset entrypoints.ImageCache.getOrLoaddoesn't share concurrent loads either. Left as a follow-up, since it's outside this design.Its optional "delete
get" suggestion was not taken, because the design keepsgetas public API.Definition of done, verified:
@forge-game-engine/forge/fonts/default/default.json?urland.pngloads the atlas from hashed URLs (95 glyphs, 512px image).docusaurus serveanddocusaurus start.Related issue(s)
Design:
design/font-atlas-loading.md. The Galactic Journey follow-up is in a separate PR inForge-Game-Engine/demoand needs the release that ships this.Verification checklist
npm run check-typespasses with 0 errors (andcheck-types:e2e)npm testpasses (1797 tests)npm run lintpasses with 0 errorsnpm run cspellpasses with 0 errorsnpm run check-exportspassesindex.ts(FontAtlasUrlsviafont-atlas-cache.ts)/documentation-site/docs/docsis updatednpm run buildnpm run typecheckandnpm run buildChangelog
## [Unreleased]inCHANGELOG.md:#### Added: the default font's exports subpath#### Changed: the newgetOrLoadsignature, with migration notes🤖 Generated with Claude Code
https://claude.ai/code/session_01BnSddjDV7weKQBwDYkmgNA
Generated by Claude Code