feat: WP-07 site and asset pipeline - #119
Open
N1k4G wants to merge 3 commits into
Open
Conversation
WP-07 begins by making the separation structural rather than documented. The legacy descriptors mix collision, air, spawn and current data with features, visual zones, atmosphere and decoration rules in one object, so "change how a site looks" and "change where the diver can swim" were the same edit. scripts/generate-site-resources.mjs partitions the descriptors into two renderer-neutral documents and refuses to run if a descriptor grows a field that is in neither list. A new field must be deliberately classified, never silently dropped or silently promoted into collision. src/sites/site-resources.ts validates on import and throws rather than serving geometry that corrupts quietly: - lerpProfile walks points assuming ascending x. Unsorted input does not throw; it interpolates against the wrong segment and returns a plausible depth for the wrong place. - An inverted structure box can never be hit by solidAt, so a wall silently stops existing. tests/parity/site-geometry.test.ts replays floorAt, ceilingAt, solidAt, overheadAt and badAirAt against the real legacy source across a sampled grid of every site, including beyond the profile ends where clamping applies. That test earned its place immediately: the first implementation folded lerpProfile's division into its multiplication, which is algebraically identical and numerically is not, and it broke parity on two sites by one ULP. The operation order is now preserved deliberately, with a comment saying why.
The Pixi slice built its wreck from hand-written Graphics calls and read no
site data at all, which is the scaling limit WP-07 exists to remove. This adds
the data path without yet rewiring the renderer.
The manifest maps every authored feature kind to one asset, with an atlas, a
layer and a minimum quality tier. The mapping is hand-authored because it is
art direction, but completeness is enforced by test in both directions: a new
feature kind fails until someone assigns it an asset, and an orphaned entry
fails until someone removes it. Neither can rot quietly.
The layer factory turns presentation data into an ordered, culled placement
list and builds no Pixi objects, so it is testable without a GPU and a second
renderer could consume the same output. Draw order is sorted rather than
inherited from authoring order, so the same inputs always produce the same
scene. Quality tiers drop decoration before the things a diver navigates by.
Feature shapes are modelled as authored rather than as assumed: some are
points with a `d`, others span `dTop`/`dBottom`, and kinds carry their own
extra fields. The first version assumed a uniform `{kind, x, d}` and the
typechecker rejected it against the real data.
WP-07's acceptance criterion now has a test: re-pointing an asset at a
different atlas and frame leaves every gameplay value byte-identical, and the
two documents are asserted structurally disjoint so an art edit cannot reach
collision again.
The renderer added its children in a flat list, so draw order was an accident of call order and there was nowhere for authored content to go. It now owns named layer containers matching the manifest, and populates them from the layer factory. Placement markers are pooled. Which features are visible changes many times during a dive, and allocating a Graphics per feature per resync would churn the heap for a scene whose contents barely change. Resync is also skipped until the camera has actually travelled, rather than running every frame. Markers are deliberately plain shapes. Production atlases are BLOCKED_EXTERNAL, so this draws a placeholder rather than art guessed at here; the manifest already fixes the atlas and frame contract they will load through, so substituting real textures does not touch this file's structure. The hand-authored hull, seabed and engine stay as they are. They are bespoke art for one scene, not authored site content, and replacing them with markers would degrade the slice to prove a point that the decoration layer already proves.
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.
Moves dive sites to validated, renderer-neutral resources with an asset manifest, layer factories, culling, pooling and quality tiers, per
Codex_Review.mdWP-07.Both acceptance criteria have passing tests
"Invalid site data fails at build/test time."
src/sites/site-resources.tsvalidates on import and throws, targeting the failures that are silent rather than loud:lerpProfilewalks points assuming ascendingx. Unsorted input does not throw — it interpolates against the wrong segment and returns a plausible depth for the wrong place.solidAt, so a wall quietly stops existing."One asset can be replaced without changing simulation or collision data." Re-pointing an asset at a different atlas and frame leaves every gameplay value byte-identical, and the two documents are asserted structurally disjoint so an art edit cannot reach collision again.
The separation is structural, not documentary
src/sites.jsmixed collision, air, spawn and current data with features, visual zones, atmosphere and decoration rules in one object — so "change how a site looks" and "change where the diver can swim" were the same edit.The generator partitions them into two documents and refuses to run if a descriptor grows a field in neither list. A new field must be deliberately classified, never silently dropped or silently promoted into collision.
Parity is proven against the real legacy source loaded via
?raw, not a snapshot, replayingfloorAt,ceilingAt,solidAt,overheadAtandbadAirAtacross a sampled grid of every site including beyond the profile ends where clamping applies.The parity test earned its place immediately
The first implementation folded
lerpProfile's division into its multiplication:Algebraically identical, numerically not. It moved results by one ULP and broke parity on two of four sites. The operation order is now preserved deliberately, with a comment saying why.
The typechecker caught a second assumption:
SiteFeaturewas modelled as a uniform{kind, x, d}, but real features include depth spans (dTop/dBottom) and kind-specific extras.Renderer
The Pixi renderer added children in a flat list, so draw order was an accident of call order. It now owns named layer containers and populates them from the layer factory, with pooled markers and resync skipped until the camera has actually travelled.
Two deliberate limits:
BLOCKED_EXTERNAL, so this draws something obviously provisional rather than art guessed at here. The manifest fixes the atlas/frame contract, so substituting real textures will not touch the renderer's structure.Known gap, worth raising before WP-08
The visual risk in the renderer change is unguarded.
compare-rendering.mjsgates the legacy client only, so nothing here would catch a Pixi visual regression. This was verified structurally — build, e2e, layer assignment — but not visually.Extending the pixel comparison to the Pixi client is the natural next work, and I would want it before WP-08 starts moving real scenes.
Verification
npm ci,npm run lint,npm run typecheck,npm run build, andnpm testall pass. No change to the legacy client, the numerical model or the golden traces.