You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Problem: physics was translation-only, box/sphere only, no shapecast, and a concrete class. Done in #1693: packages/core/src/physics/physicsBackend.ts (the PhysicsBackend interface), physicsWorldBackend.ts (PhysicsWorld adapter), physicsBackendConformance.ts (runPhysicsBackendConformance(create, { test, expect })), and packages/core/src/movement/characterController.ts (createCharacterController). Read those four files before starting; the interface is the contract.
Tasks, one PR each, in order:
@jgengine/rapier package. Create packages/rapier/ mirroring packages/sql/ layout (package.json with "./*" wildcard export, tsconfig, tsconfig.build.json, build script tsgo -p tsconfig.build.json && bun ../../scripts/fix-extensions.ts dist). Dependencies: @jgengine/core (workspace) and @dimforge/rapier3d-compat (pin latest 0.x). src/createRapierBackend.ts: export async function createRapierBackend(options: PhysicsBackendConfig & { warn?: boolean }): Promise<PhysicsBackend> that awaits RAPIER.init(), then implements every method: shapes map to ColliderDesc.cuboid/ball/capsule/convexHull/trimesh/heightfield; kind maps to RigidBodyDesc.dynamic/kinematicPositionBased/fixed; layers and mask to collisionGroups; ccd to setCcdEnabled; joints to JointData.fixed/revolute/spherical/rope/spring with limits on revolute; raycast via world.castRay with solid=true and normal from castRayAndGetNormal; shapecast via world.castShape; overlap via intersectionsWithShape; contacts from EventQueue.drainCollisionEvents plus contactPair for the normal; setKinematicTarget via setNextKinematicTranslation; snapshot = world.takeSnapshot() bytes plus the handle map; restore = World.restoreSnapshot. Handles are your own counters mapped to Rapier handles, as the PhysicsWorld adapter does. capabilities: rotation true, all shapes, ccd true, all joints. Test file src/createRapierBackend.test.ts runs runPhysicsBackendConformance and the character controller test scenarios (copy characterController.test.ts with the backend swapped). Register the package: root package.json build chain, scripts/export-manifest.json via bun run gen, README package table and lockstep version line, scripts/tarballInstall.test.ts list, CHANGELOG Added. Update packages/core/src/physics/README.md "Update 2026-09" to say the adapter exists.
Controller adoption in the shell. In packages/core/src/movement/playerMovement.ts add an optional controller?: { backend: PhysicsBackend; capsule: CharacterControllerConfig } on PlayerMovementTuning; when present, stepPlayerMovement computes the horizontal motion from input as today, then calls createCharacterController(...).move(backend, { motion, dt, gravity, jumpVelocity, crouch }) and writes the resulting position through setPose, instead of the terrain-height and AABB path. Store the controller per user in the same per-context map the function already uses for motion state. Expose physics?: { backend: PhysicsBackend } on the game definition so resolvePlayerMovementTuning can pick it up, and register backend.step(dt) as ctx.sim.addStage({ id: "physics", phase: "afterMovement", run: (dt) => backend.step(dt) }) in packages/core/src/game/defineGame.ts when a backend is configured. Headless test: a createHeadlessRunner game with a PhysicsWorld backend, a floor and a step box, walks and steps up. CHANGELOG Added.
Static world into the backend. New packages/core/src/physics/worldColliders.ts: syncWorldColliders(backend, ctx) that creates static bodies for every scene object collider (ctx.scene.object colliders and packages/core/src/scene/colliders.ts), and for terrain a heightfield from ctx.world.ground samples; keeps a map from object id to handle and updates on spawn/despawn via the entity/object subscribe hooks. Test: a spawned object becomes a raycast hit. This makes [FEATURE] World features generate no collision — buildings, city volumes, and walls are solid to nothing #1627 (world features generate no collision) fixable through the backend later.
Probe and clip. In apps/dev or examples/, a scene with the Rapier backend: a crate tumbling down a slope, a ragdoll (use packages/core/src/physics/ragdoll.ts shapes through addBody/addJoint with ball joints), and a box vehicle rolling. Record with bun run pr-video (video or stills, never a GIF) and embed. Closes #1682.
Rules: wait for bun run agent:bootstrap --check; branch claude/<slug> off origin/main; claim comment first; one task per PR with Refs #1682; bun run gen after export changes (JSDoc on every export; create* factories need snapshot/restore); CHANGELOG bullet; check-types and test on every touched package plus check-stateful-ratchet, check-doc-symbols, check-orphan-ratchet; merge origin/main before pushing; squash auto-merge; fix CI on the same branch. Rapier's WASM must not be imported by @jgengine/core; only the new package depends on it.
Problem: physics was translation-only, box/sphere only, no shapecast, and a concrete class. Done in #1693:
packages/core/src/physics/physicsBackend.ts(thePhysicsBackendinterface),physicsWorldBackend.ts(PhysicsWorld adapter),physicsBackendConformance.ts(runPhysicsBackendConformance(create, { test, expect })), andpackages/core/src/movement/characterController.ts(createCharacterController). Read those four files before starting; the interface is the contract.Tasks, one PR each, in order:
@jgengine/rapierpackage. Createpackages/rapier/mirroringpackages/sql/layout (package.json with"./*"wildcard export, tsconfig, tsconfig.build.json, build scripttsgo -p tsconfig.build.json && bun ../../scripts/fix-extensions.ts dist). Dependencies:@jgengine/core(workspace) and@dimforge/rapier3d-compat(pin latest 0.x).src/createRapierBackend.ts:export async function createRapierBackend(options: PhysicsBackendConfig & { warn?: boolean }): Promise<PhysicsBackend>that awaitsRAPIER.init(), then implements every method: shapes map toColliderDesc.cuboid/ball/capsule/convexHull/trimesh/heightfield;kindmaps toRigidBodyDesc.dynamic/kinematicPositionBased/fixed; layers and mask tocollisionGroups;ccdtosetCcdEnabled; joints toJointData.fixed/revolute/spherical/rope/springwithlimitson revolute;raycastviaworld.castRaywithsolid=trueand normal fromcastRayAndGetNormal;shapecastviaworld.castShape;overlapviaintersectionsWithShape; contacts fromEventQueue.drainCollisionEventspluscontactPairfor the normal;setKinematicTargetviasetNextKinematicTranslation;snapshot=world.takeSnapshot()bytes plus the handle map;restore=World.restoreSnapshot. Handles are your own counters mapped to Rapier handles, as the PhysicsWorld adapter does.capabilities: rotation true, all shapes, ccd true, all joints. Test filesrc/createRapierBackend.test.tsrunsrunPhysicsBackendConformanceand the character controller test scenarios (copycharacterController.test.tswith the backend swapped). Register the package: rootpackage.jsonbuild chain,scripts/export-manifest.jsonviabun run gen, README package table and lockstep version line,scripts/tarballInstall.test.tslist, CHANGELOG Added. Updatepackages/core/src/physics/README.md"Update 2026-09" to say the adapter exists.Controller adoption in the shell. In
packages/core/src/movement/playerMovement.tsadd an optionalcontroller?: { backend: PhysicsBackend; capsule: CharacterControllerConfig }onPlayerMovementTuning; when present,stepPlayerMovementcomputes the horizontal motion from input as today, then callscreateCharacterController(...).move(backend, { motion, dt, gravity, jumpVelocity, crouch })and writes the resulting position throughsetPose, instead of the terrain-height and AABB path. Store the controller per user in the same per-context map the function already uses for motion state. Exposephysics?: { backend: PhysicsBackend }on the game definition soresolvePlayerMovementTuningcan pick it up, and registerbackend.step(dt)asctx.sim.addStage({ id: "physics", phase: "afterMovement", run: (dt) => backend.step(dt) })inpackages/core/src/game/defineGame.tswhen a backend is configured. Headless test: acreateHeadlessRunnergame with a PhysicsWorld backend, a floor and a step box, walks and steps up. CHANGELOG Added.Static world into the backend. New
packages/core/src/physics/worldColliders.ts:syncWorldColliders(backend, ctx)that creates static bodies for every scene object collider (ctx.scene.objectcolliders andpackages/core/src/scene/colliders.ts), and for terrain aheightfieldfromctx.world.groundsamples; keeps a map from object id to handle and updates on spawn/despawn via the entity/object subscribe hooks. Test: a spawned object becomes a raycast hit. This makes [FEATURE] World features generate no collision — buildings, city volumes, and walls are solid to nothing #1627 (world features generate no collision) fixable through the backend later.Probe and clip. In
apps/devorexamples/, a scene with the Rapier backend: a crate tumbling down a slope, a ragdoll (usepackages/core/src/physics/ragdoll.tsshapes throughaddBody/addJointwith ball joints), and a box vehicle rolling. Record withbun run pr-video(video or stills, never a GIF) and embed.Closes #1682.Rules: wait for
bun run agent:bootstrap --check; branchclaude/<slug>offorigin/main; claim comment first; one task per PR withRefs #1682;bun run genafter export changes (JSDoc on every export;create*factories need snapshot/restore); CHANGELOG bullet;check-typesandteston every touched package pluscheck-stateful-ratchet,check-doc-symbols,check-orphan-ratchet; mergeorigin/mainbefore pushing; squash auto-merge; fix CI on the same branch. Rapier's WASM must not be imported by@jgengine/core; only the new package depends on it.