Level: one declared option contract instead of a copied list (#1649) - #1682
Merged
Merged
Conversation
Purely internal; no public API or behaviour change. The set a `Trigger` forwards is byte-identical to before, `level.load` takes the same options it always did, `index.ts` is untouched, and the new declarations are `@internal` so they are stripped from the published types. #1654 fixed the six glTF options a Trigger was dropping by adding them to its hand-written allowlist. That fixes the instance, not the class: every option a level format gains has to be remembered in a second place or it silently vanishes, which is exactly how those six went missing for three releases. #1649 said as much — "the second is preferable on maintenance grounds" — and the PR took the first without arguing for it. So each format now declares the options it reads next to the code that reads them (`GLTFScene.loadOptions`, `TMXTileMap.loadOptions`), `level` publishes the union as `LEVEL_LOAD_OPTIONS`, and `Trigger` forwards by that minus `async`. Naming an option once, beside its consumer, is now enough. Same shape as the loader's `normalizeSrc`/`needsBaseURL` contract in #1648. Derived on FIRST USE rather than at module scope: `level` reaches the formats, which reach the renderables, which reach `trigger`, so reading the export while that cycle unwinds throws on the temporal dead zone — two specs caught it. Tests: the "every option survives" assertion is derived from the contract, so it fails the day a declared option stops travelling rather than only catching the six it was written for. Verified by declaring a new option on `GLTFScene` and restoring the old hand-written forwarder: it fails, naming the option. Replaces a test that asserted the six keys were ABSENT and so passed identically with the fix reverted — split into the two halves that do pin something (the explicit-undefined guard, and `async` on both paths). Docs: two statements I wrote alongside #1654 were wrong. The tilemaps skill said a Trigger "passes its own settings through", which is the exact belief that produced #1649; and the `async` rationale held only on the transition path, since a trigger with no fade never sets it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGvtaUNATVCVxD2qcbiY4t
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.
Follow-up to #1654. Purely internal — no public API change, no behaviour change.
Why
#1654 fixed the six glTF options a
Triggerwas silently dropping by adding them to its hand-written allowlist. That fixes the instance, not the class: every option a level format gains has to be remembered in a second place or it vanishes without a warning, which is exactly how those six went missing for three releases.#1649 said so itself — "The second is preferable on maintenance grounds" — and the PR took the first option with a one-line note and no argument for it. Nobody asked why at review time. This is that follow-up, and it is the same move #1648 made for the loader with
normalizeSrc/needsBaseURL.What
Each level format declares the options it reads, next to the code that reads them:
GLTFScene.loadOptions— besideaddTo, which consumes themTMXTileMap.loadOptionslevelpublishes the union internally asLEVEL_LOAD_OPTIONS, andTriggerforwards by that minusasync. Naming an option once, beside its consumer, is now enough for it to travel everywhere.Derived on first use, not at module scope:
levelreaches the formats, which reach the renderables, which reachtrigger, so reading the export while that cycle unwinds throws on the temporal dead zone. Two specs caught it.Backward compatibility
level.loadaccepts exactly the same options, with the same typedoc.index.tsuntouched; all three declarations are@ignore @internal, sostrip-internalkeeps them out of the published.d.ts(verified on the built output).Tests
The "every option survives" assertion is derived from the contract, so it fails the day a declared option stops travelling rather than only catching the six it was written for. Verified by declaring a new option on
GLTFScenewhile restoring the old hand-written forwarder — it fails and names the option; under the contract the same option travels with no change toTrigger.It also replaces a test from #1654 that asserted the six keys were absent and therefore passed identically with the fix reverted. Split into the two halves that do pin something: the explicit-
undefinedguard, andasyncexclusion on both paths.Docs
Two statements I added alongside #1654 were wrong and are corrected here: the tilemaps skill said a Trigger "passes its own settings through" — the exact belief that produced #1649 — and the
asyncrationale held only on the transition path, since a trigger with no fade never sets it.7007 tests, lint clean. No changelog entry: the user-facing fix is already documented under #1649, and this is an internal refactor.
🤖 Generated with Claude Code
https://claude.ai/code/session_01NGvtaUNATVCVxD2qcbiY4t