Collision implementation and validation
Status: implemented and validated. Base: collision design review bd5c4834fa827d8ca62ded82a563772024165d0d. Work branch: codex/working-collision. Main, Sprite, Tilemap and design-review branches are not write targets. This report, code, tests and measurements are recoverable from GitHub without the original conversation.
Outcome and public boundary
Actor ContactEnter/Stay/Exit and Contact.self/other/selfActor/otherActor remain unchanged. TileContactEnter/Stay/Exit expose direct map/tileX/tileY data and observed image ID/frozen policy. There is no public contact-target union or required migration for Actor-only games. The previous review union proposal is historical and explicitly superseded.
A public union would impose a branch on an Actor-only handler merely because the engine supports terrain. Dedicated tokens require two subscriptions only when a game actually has two different rules. Actor-only, terrain-only and mixed usage is compiled in tests/types/collision-usage.ts; negative options/fields and preserved Actor payloads are compiled in tests/types/tile-collision.ts. No significant duplicate game-code framework was needed. Native TypeScript completion checks use the real exported APIs, not the historical proposal declarations.
Current API and lifecycle and full Solver measurements are the supported feature documents. Historical prototypes under tests/prototypes remain isolated evidence; the formal World does not call the naive seam-producing probe.
Responsibilities and implementation
| Part | Responsibility |
|---|---|
| Collider | Shape, immutable Trigger/Solid policy and layer/mask, enable state, typed contact delivery |
| CollisionBody | Static/dynamic inverse-mass correction weight, not movement integration or velocity |
| Motion / Transform | Movement intent/integration and world position; correction preserves intent |
| Tilemap / tilemap-collision | Copied image Grid, optional copied/frozen ID rules, sparse cell overrides, revision/notifications and placement gates |
| CollisionWorld | Scene discovery, Actor sweep + local cell candidates, one pair registry, bounded response and ordered observation delivery |
| collision-policy / collision-contact | Shared validation/filter/pair response and observation/phase helpers |
| collision-geometry / collision-response | Existing Circle/OBB narrow phase and inverse-mass translation, reused for terrain data |
| tile-collision-geometry | Closed local candidate bounds and equal-policy exposed boundary features |
| tile-contact | Dedicated public payload/token boundary, not a second solver or World |
| Web renderer | Existing image readiness/sharing/culling only; no collision ownership or scheduling |
The Actor refactor checkpoint passed all 391 original unit tests and strict types before adding terrain response. Each map keeps its existing single internal Actor/passive TilemapLayer. Geometry records are plain data only for observed cells, shared by contacting Actors and evicted with the last contact pair. No per-cell Actor/Collider Component, Scene ownership collection, chunk/rectangle index, dependency or ControlGuard is added.
Actor and terrain pairs share policy/filter evaluation, exact narrow phase, one correction loop/iteration budget, observation state, transition/retirement, error handling and owner checks. Separate broad-phase candidate providers are necessary for Actor bounds versus a grid; they feed the same orchestration. Actor-only diagnostics retain their schema. Terrain scan/recompute/work counters are separate; responseDiagnostics describes the entire shared solver.
Collision-only edits do not advance image revision or recreate Sprites. Local lookup needs no map-wide geometry rebuild. Rules/defaults, overrides and image values are sampled during the collision pass; callback edits apply next pass, while disable/disposal suppresses later invalid Enter/Stay immediately. Matched invalid exits follow the same pair-processing semantics as Actor contacts. Scene/World destruction clears silently.
Boundary response acceptance
Four neighbor probes suppress internal response faces/vertices only for equal complete normalized policies. Exact manifolds remain at exposed faces and convex corners. An internal feature is replaced only by a real exposed finite face intersection (circle projection / OBB segment/slab test), not arbitrary normal rounding. Different response/layer/mask boundaries remain separate.
The formal World compares continuous-wall trajectories with a single ordinary static Actor wall, using circles and boxes rotated +0.37/-0.64 radians, horizontal/vertical walls, both sides and tangent directions, straight walls and exposed convex corners, and 32×32/48×20 cells. 96 measured scenarios have maximum difference 1.5631940186722204e-13 world units, below the unchanged 1e-6 threshold, with no unresolved contacts. Separate unit cases exercise concave corners. The original circle counterexample now ends at (54,136), rather than the naive probe's tangent drift of about +3.43.
General deep embedding is deliberately not claimed solved. Fully internal Solid cells can have no exterior response feature; an explicit unit case reports unresolved rather than promising escape. Impossible gaps, dense crowds, low budgets, extreme numeric scales, discrete tunneling and moved-platform velocity transfer remain limitations.
Validation results
| Validation | Result |
|---|---|
| npm ci | Pinned lockfile installed; no manifest/lock/dependency changes |
| npm test | 509/509, 38 files; includes all 391 pre-existing tests |
| npm run check | Passed strict TypeScript, DOM-free Core, Biome, boundaries/files and licenses; existing 12 warnings/11 infos, no errors; 116 locked licenses |
| Native completions | grass/wall/water in constructor rule and setter; direct TileContact fields and unchanged Actor Contact fields; existing Sprite get/play/map set/fill completions pass |
| Full browser suite | 178/178, retries disabled, 4.0 minutes |
| Post-layout actual sample tests | 4/4, WebGL/Canvas2D × desktop/narrow |
| New terrain browser coverage | Real stopped pixels, visibility/Camera independence, pending-image collision/removal, zero renderer cell visits for policy-only edits, gaps/regeneration, water Trigger, WASD/sliding, collision toggle and Scene recreation |
| Default production game build/smoke | Passed /Simpludify/, 1440×900 and 390×844 |
| Official site build/check | Both /Simpludify/ and /: 51 HTML documents, all local references/assets, 50 standalone TypeScript examples |
| Production site browser smoke | Both bases, both sizes: themes/layout, eight samples, actual wall stop x=434, live terrain contacts/candidates and zero unresolved, real pixels/one-cell edit, gallery cleanup/restart, startup failure and Canvas fallback |
| Formal Solver benchmark | 72 plain + 72 instrumented runs, 2880 raw recorded passes, six raw files plus summaries and 96 trajectory results |
| Measurement integrity | Every stored sample/percentile/release and matched plain/profiled behavior verified by a unit test |
Chromium/Chrome for Testing 156.0.8078.4, Playwright 1.64.1, Linux, WebGL SwiftShader and forced Canvas2D. CHROMIUM_PATH selected the matching headless shell downloaded from Google's testing archive. Physical GPU/mobile/other browsers are not verified. Browser tests report no unhandled page/console errors in the new terrain cases. The historical narrow Signal Run failure did not reproduce; no historical root-cause fix is claimed.
Sample screenshots were inspected. Sample-local CSS is imported by its module so standalone and fixture/gallery paths share the compact control grid. No engine UI/Flex/Pointer API was changed. Rendering/shared-image tests retain their source/crop/lease and asynchronous cancellation contracts.
Performance and resources
Measured clean runtime source: 12255c346f3ce0da5f4313c7ca2206e1041543a2, Node v24.22.1 / AMD EPYC 9V74 80-Core Processor / Linux. No other local tests/builds ran during recording. Later commits do not change Core runtime code.
At 256² with 128 radius-10 dynamic Actors, plain step p50 medians were about 0.33–0.35 ms across the controlled dense/scattered/static/eight-edit workloads. Initial map generation/inventory/copy/validation was about 2.4–3.4 ms; disposal about 0.33–0.38 ms for those 128-Actor cases. These are shared-host Node CPU observations, not FPS or universal speed claims. Map/Actor construction, first-step registration, edits, plain step, separately instrumented capture/query/response/delivery, counts and disposal are retained in the detailed report.
The first recorded static pass reads six candidate cells for one Actor or 1218 for 128 Actors, independently of map dimension. Counts include repeated solver/final scans. Terrain has no relation to the number of displayed Sprites; browser tests still collide with zero rendered map Sprites. All controlled measurement disposals leave zero Scene Actors, reconciled maps, contact pairs, retained cell geometry and input sources. This is logical ownership evidence, not a native/managed memory leak proof or long soak.
Actor/Actor narrow response is masked out in this terrain benchmark, while its real sweep cost remains; mixed contacts/mass behavior is covered by regression tests. Pose resets and instrumentation are explicit limitations. Large bodies/Triggers, many overlapping maps, broad-phase worst cases and long sessions remain profiling targets. No measured need for a new chunk/merge framework was established here.
Reproduction
npm ci
npm run check
npm test
node scripts/check-collision-completions.mjs
node scripts/check-sprite-completions.mjs
# Install compatible Chromium or set CHROMIUM_PATH.
npm run test:browser
npm run test:build
npm run site:build -- --base /Simpludify/
npm run site:check
npm run test:site
npm run site:build -- --base /
npm run site:check
npm run test:site
# Commit/push first; do not run other tests/builds during recorded measurement.
node scripts/benchmark-tile-collision.mjs
The initial usability sketches were compiled before implementation and switched to real APIs. Early test-fixture mistakes (an undefined argument activating a JS default Body and noncontacting mixed-test starting poses) were corrected before the formal checkpoint; they were not Solver defects. The only post-full-suite UI change imported the same sample CSS through the module, then reran the four affected browser cases. All new runtime source is saved and validated; no unfinished formal feature is hidden by those preparation checks.
Commits and recovery
Every commit is immediately attached to the dedicated remote branch with a non-force expected-head update. No local-only commit is a recovery checkpoint. Main/Sprite/Tilemap/design refs must remain unchanged. The file cannot embed its own final commit SHA without changing that SHA; use the work branch ref/history or final response for the final SHA.
| Commit | Saved scope |
|---|---|
0c670d123bc62b6d4f71b95bd8f6599135162b97 |
refactor(core): share collision policy and observation while preserving Actor contacts |
4e2fb1123618625a32ebd4c6afffbafbb7b94713 |
feat(core): add optional copied tile collision rules and sparse cell overrides |
dfa2fa43e0e41e3dd6211704d471332291382171 |
feat(core): exclude internal tile response features with continuous-wall probes |
1b6aca3d28deefdf0a4e5c5af7b874cd04e11154 |
feat(core): integrate dedicated tile contacts into shared collision World and solver |
cd249a47cb88839e68ce4ee8211d145f9f04e8e4 |
feat(examples): add real tile terrain collision editing and browser fixtures (validation pending) |
12255c346f3ce0da5f4313c7ca2206e1041543a2 |
docs: publish terrain collision contracts and save full Solver measurement harness |
49390966e6ac6799002187a900b2ca64925cf1df |
perf: record full tile collision Solver phases counts cleanup and 96 wall trajectories |
8e81f3137e857043d054576b5041846c797c5da7 |
test(site): verify production terrain collision and compact sample controls |
| Final validation commit (this file) | Completed validation, full file inventory and integration notes |
Changed files and integration notes
Relative to bd5c4834fa827d8ca62ded82a563772024165d0d, 53 files change: eight Core files plus tests/type fixtures, sample/CSS, benchmark harness/raw data and documentation/site validation. Package manifests/lockfiles and existing Signal Run/Prism Raid game logic are unchanged. Review the design-base comparison first: the full main comparison also contains all Sprite/Tilemap/review ancestry. No merge, main push, npm release, Pages visibility or DNS change is performed.
The former public union proposal is marked historical in all review documents; current guides use separate events. No Actor contact migration is needed. Before integration, run CI against any newer upstream changes and preserve the shared iteration/event semantics and exposed-feature acceptance matrix. CCD, slopes, friction/bounce/rigid bodies, streaming, general deep-embedding recovery and new UI/control ownership frameworks are outside scope.
Full file inventory:
README.mdbenchmarks/tile-collision/results.jsonbenchmarks/tile-collision/samples/100-dense.jsonbenchmarks/tile-collision/samples/100-scattered.jsonbenchmarks/tile-collision/samples/256-dense.jsonbenchmarks/tile-collision/samples/256-scattered.jsonbenchmarks/tile-collision/samples/32-dense.jsonbenchmarks/tile-collision/samples/32-scattered.jsondocs/api.mddocs/architecture.mddocs/collision-implementation.mddocs/collision-performance.mddocs/collision-review/README.mddocs/collision-review/implementation-plan.mddocs/collision-review/performance.mddocs/collision-review/tilemap-contract.mddocs/collision-review/validation.mddocs/roadmap.mddocs/tilemap-collision.mddocs/tilemap-design.mddocs/tilemap-validation.mddocs/tilemap.mdexamples/tilemap/README.mdexamples/tilemap/main.tsexamples/tilemap/style.csspackages/core/src/collider.tspackages/core/src/collision-contact.tspackages/core/src/collision-policy.tspackages/core/src/collision.tspackages/core/src/tile-collision-geometry.tspackages/core/src/tile-contact.tspackages/core/src/tilemap-collision.tspackages/core/src/tilemap.tsscripts/benchmark-tile-collision.mjsscripts/check-collision-completions.mjsscripts/site-smoke.mjssite/config.mjssite/ja/api.mdsite/ja/collision.mdsite/ja/examples.mdsite/ja/limitations.mdsite/ja/tilemap.mdtests/browser/tile-collision-fixture.tstests/browser/tile-collision.spec.tstests/helpers/tile-wall-scenario.tstests/prototypes/collision-api.tstests/types/collision-usage.tstests/types/tile-collision.tstests/unit/tile-collision-benchmark-data.test.tstests/unit/tile-collision-geometry.test.tstests/unit/tile-collision.test.tstests/unit/tile-wall.test.tstests/unit/tilemap-collision-data.test.ts