castle-web-cli 0.4.128 → 0.4.130

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/dist/agent-prompts.js +4 -4
  2. package/dist/filesChanged.d.ts +2 -0
  3. package/dist/filesChanged.js +6 -2
  4. package/dist/ide.js +15 -4
  5. package/dist/importBrowse.js +1 -0
  6. package/dist/index.js +10 -4
  7. package/dist/init.js +63 -15
  8. package/dist/native/loop.js +1 -1
  9. package/dist/native/tools.js +1 -1
  10. package/dist/serve.d.ts +1 -0
  11. package/dist/serve.js +20 -1
  12. package/dist/shell/assets/index-BpOLUyVO.js +441 -0
  13. package/dist/shell/assets/{index-CAq6f9B5.css → index-D6K-0YDB.css} +1 -1
  14. package/dist/shell/index.html +2 -2
  15. package/kits/physics-2d/CLAUDE.md +40 -23
  16. package/kits/physics-2d/behaviors/Joints.jsx +4 -3
  17. package/kits/physics-2d/behaviors/SoundPlayer.jsx +266 -0
  18. package/kits/physics-2d/behaviors/Sprite.jsx +3 -3
  19. package/kits/physics-2d/behaviors/Video.jsx +4 -3
  20. package/kits/physics-2d/castle.json +1 -1
  21. package/kits/physics-2d/editors/PlayOnly.jsx +15 -6
  22. package/kits/physics-2d/editors/PxArtEditor.jsx +95 -31
  23. package/kits/physics-2d/editors/SceneEditor.jsx +5 -5
  24. package/kits/physics-2d/editors/SingleEditor.jsx +37 -8
  25. package/kits/physics-2d/editors/StyleEditor.jsx +7 -4
  26. package/kits/physics-2d/editors/pixelEditorChrome.jsx +7 -7
  27. package/kits/physics-2d/editors/pixelInspector.jsx +13 -7
  28. package/kits/physics-2d/editors/pxArtTimeline.jsx +29 -11
  29. package/kits/physics-2d/editors/pxArtTimeline.module.css +11 -0
  30. package/kits/physics-2d/engine/audioContext.js +41 -0
  31. package/kits/physics-2d/engine/autoInspector.jsx +50 -33
  32. package/kits/physics-2d/engine/blueprint.js +15 -3
  33. package/kits/physics-2d/engine/liveReload.js +33 -21
  34. package/kits/physics-2d/engine/media.js +29 -39
  35. package/kits/physics-2d/engine/sound.js +316 -0
  36. package/kits/physics-2d/engine/systemRegistry.js +1 -1
  37. package/kits/physics-2d/engine/ui.jsx +274 -5
  38. package/kits/physics-2d/engine/ui.module.css +137 -16
  39. package/kits/physics-2d/scripts/testsounds.mjs +84 -0
  40. package/kits/physics-2d/systems/media.js +31 -15
  41. package/kits/physics-3d/CLAUDE.md +109 -0
  42. package/kits/physics-3d/behaviors/Body.jsx +51 -0
  43. package/kits/physics-3d/behaviors/Door.jsx +31 -0
  44. package/kits/physics-3d/behaviors/Lookable.jsx +11 -0
  45. package/kits/physics-3d/behaviors/Model.jsx +14 -0
  46. package/kits/physics-3d/behaviors/Pickup.jsx +39 -0
  47. package/kits/physics-3d/behaviors/Player.jsx +172 -0
  48. package/kits/physics-3d/behaviors/Portal.jsx +19 -0
  49. package/kits/physics-3d/behaviors/Shape.jsx +75 -0
  50. package/kits/physics-3d/behaviors/Solid.jsx +11 -0
  51. package/kits/physics-3d/behaviors/Transform.jsx +24 -0
  52. package/kits/physics-3d/blueprints/barrel.scene +15 -0
  53. package/kits/physics-3d/blueprints/crate.scene +29 -0
  54. package/kits/physics-3d/blueprints/door.scene +35 -0
  55. package/kits/physics-3d/blueprints/gem.scene +26 -0
  56. package/kits/physics-3d/blueprints/pillar.scene +25 -0
  57. package/kits/physics-3d/blueprints/platform.scene +23 -0
  58. package/kits/physics-3d/blueprints/player.scene +29 -0
  59. package/kits/physics-3d/blueprints/portal.scene +21 -0
  60. package/kits/physics-3d/blueprints/rock.scene +14 -0
  61. package/kits/physics-3d/blueprints/statue.scene +26 -0
  62. package/kits/physics-3d/blueprints/tree.scene +14 -0
  63. package/kits/physics-3d/blueprints/wall.scene +26 -0
  64. package/kits/{basic-2d → physics-3d}/castle.json +23 -5
  65. package/kits/physics-3d/docs/pxmodel-format.md +111 -0
  66. package/kits/physics-3d/drawings/crate.pxart +26 -0
  67. package/kits/physics-3d/drawings/door-edge.pxart +26 -0
  68. package/kits/physics-3d/drawings/door.pxart +27 -0
  69. package/kits/physics-3d/drawings/face.pxart +26 -0
  70. package/kits/physics-3d/drawings/floor.pxart +27 -0
  71. package/kits/physics-3d/drawings/platform.pxart +27 -0
  72. package/kits/physics-3d/drawings/statue.pxart +27 -0
  73. package/kits/physics-3d/drawings/wall.pxart +27 -0
  74. package/kits/physics-3d/engine3d/PlayOnly3D.jsx +32 -0
  75. package/kits/physics-3d/engine3d/PxModelEditor.jsx +619 -0
  76. package/kits/physics-3d/engine3d/Scene3DEditor.jsx +618 -0
  77. package/kits/physics-3d/engine3d/Scene3DPlayer.jsx +309 -0
  78. package/kits/physics-3d/engine3d/editor3dData.js +114 -0
  79. package/kits/physics-3d/engine3d/editor3dInspector.jsx +404 -0
  80. package/kits/physics-3d/engine3d/editorChrome.jsx +218 -0
  81. package/kits/physics-3d/engine3d/editorWorld.js +249 -0
  82. package/kits/physics-3d/engine3d/materials.js +174 -0
  83. package/kits/physics-3d/engine3d/meshops.js +123 -0
  84. package/kits/physics-3d/engine3d/modelEditorWorld.js +439 -0
  85. package/kits/physics-3d/engine3d/modelFiles.js +139 -0
  86. package/kits/physics-3d/engine3d/pxmodel.js +265 -0
  87. package/kits/physics-3d/engine3d/thumbnails.js +137 -0
  88. package/kits/physics-3d/engine3d/world3d.js +216 -0
  89. package/kits/{basic-2d → physics-3d}/eslint.config.js +10 -25
  90. package/kits/physics-3d/index.html +20 -0
  91. package/kits/physics-3d/main.jsx +30 -0
  92. package/kits/physics-3d/models/barrel.pxmodel +10 -0
  93. package/kits/physics-3d/models/player.pxmodel +222 -0
  94. package/kits/physics-3d/models/rock.pxmodel +30 -0
  95. package/kits/physics-3d/models/tree.pxmodel +10 -0
  96. package/kits/{basic-2d → physics-3d}/package-lock.json +140 -119
  97. package/kits/{basic-2d → physics-3d}/package.json +11 -9
  98. package/kits/{basic-2d → physics-3d}/pnpm-lock.yaml +162 -137
  99. package/kits/physics-3d/scenes/main.scene +414 -0
  100. package/kits/physics-3d/scenes/model-lab.scene +127 -0
  101. package/kits/physics-3d/systems/physics3d.js +356 -0
  102. package/package.json +5 -2
  103. package/dist/shell/assets/index-DKu9ejyh.js +0 -436
  104. package/kits/basic-2d/CLAUDE.md +0 -221
  105. package/kits/basic-2d/behaviors/Camera.jsx +0 -43
  106. package/kits/basic-2d/behaviors/Collider.jsx +0 -213
  107. package/kits/basic-2d/behaviors/Layout.jsx +0 -53
  108. package/kits/basic-2d/behaviors/Sprite.jsx +0 -357
  109. package/kits/basic-2d/behaviors/tint.js +0 -47
  110. package/kits/basic-2d/blueprints/cauldron.scene +0 -20
  111. package/kits/basic-2d/docs/pxart-format.md +0 -377
  112. package/kits/basic-2d/drawings/cauldron.pxart +0 -113
  113. package/kits/basic-2d/editors/BlueprintLibrary.jsx +0 -270
  114. package/kits/basic-2d/editors/ErrorBoundary.jsx +0 -59
  115. package/kits/basic-2d/editors/PlayOnly.jsx +0 -31
  116. package/kits/basic-2d/editors/PxArtEditor.jsx +0 -1092
  117. package/kits/basic-2d/editors/SceneEditor.jsx +0 -1780
  118. package/kits/basic-2d/editors/SelectionOverlay.jsx +0 -909
  119. package/kits/basic-2d/editors/SingleEditor.jsx +0 -122
  120. package/kits/basic-2d/editors/behaviorRegistry.js +0 -34
  121. package/kits/basic-2d/editors/editorHistory.js +0 -157
  122. package/kits/basic-2d/editors/inspectorSheet.js +0 -13
  123. package/kits/basic-2d/editors/pixelCanvas.js +0 -11
  124. package/kits/basic-2d/editors/pixelEditorChrome.jsx +0 -74
  125. package/kits/basic-2d/editors/pixelGeometry.js +0 -140
  126. package/kits/basic-2d/editors/pixelInspector.jsx +0 -633
  127. package/kits/basic-2d/editors/pxArtEditorModel.js +0 -732
  128. package/kits/basic-2d/editors/pxArtPlayback.js +0 -92
  129. package/kits/basic-2d/editors/pxArtTimeline.jsx +0 -752
  130. package/kits/basic-2d/editors/pxArtTimeline.module.css +0 -506
  131. package/kits/basic-2d/editors/pxArtTools.js +0 -232
  132. package/kits/basic-2d/editors/useArtboardFit.js +0 -105
  133. package/kits/basic-2d/engine/ScenePlayer.jsx +0 -209
  134. package/kits/basic-2d/engine/SceneUI.jsx +0 -59
  135. package/kits/basic-2d/engine/assets.js +0 -15
  136. package/kits/basic-2d/engine/autoInspector.jsx +0 -70
  137. package/kits/basic-2d/engine/behaviorExtensions.js +0 -32
  138. package/kits/basic-2d/engine/blueprint.js +0 -557
  139. package/kits/basic-2d/engine/collider.js +0 -200
  140. package/kits/basic-2d/engine/files.js +0 -141
  141. package/kits/basic-2d/engine/liveReload.js +0 -88
  142. package/kits/basic-2d/engine/pxart.js +0 -1032
  143. package/kits/basic-2d/engine/pxartSmooth.js +0 -222
  144. package/kits/basic-2d/engine/scene.js +0 -696
  145. package/kits/basic-2d/engine/spriteGeometry.js +0 -32
  146. package/kits/basic-2d/engine/systemRegistry.js +0 -16
  147. package/kits/basic-2d/engine/ui.jsx +0 -695
  148. package/kits/basic-2d/engine/ui.module.css +0 -2287
  149. package/kits/basic-2d/index.html +0 -24
  150. package/kits/basic-2d/main.jsx +0 -24
  151. package/kits/basic-2d/scenes/main.scene +0 -16
  152. package/kits/basic-2d/scripts/draw.mjs +0 -121
  153. package/kits/physics-2d/behaviors/Sound.jsx +0 -163
  154. package/kits/physics-2d/behaviors/Tone.jsx +0 -95
  155. package/kits/physics-2d/engine/tone.js +0 -112
  156. /package/kits/{basic-2d → physics-3d}/.prettierrc +0 -0
  157. /package/kits/{basic-2d → physics-3d}/vite.config.js +0 -0
@@ -0,0 +1,172 @@
1
+ // The walkable player: a dynamic locked-rotation capsule driven by impulses
2
+ // (the vase floating-capsule controller), a faux-isometric follow camera, and
3
+ // the "look at things" reading of nearby Lookables into the hud.
4
+ //
5
+ // Controls: WASD / arrows move (camera-relative), Q / E orbit the camera.
6
+ // State lives on `actor.runtime` -- behavior instances are per-frame.
7
+
8
+ const DEG = Math.PI / 180;
9
+
10
+ function keyDown(keys, ...codes) {
11
+ return codes.some((code) => keys.has(code));
12
+ }
13
+
14
+ // Tank-style drive input: `forward` (-1..1) walks along the facing direction,
15
+ // `turn` (-1..1) steers it. W/S (and up/down arrows) drive, A/D (and
16
+ // left/right) turn. The pointer-drag stick maps the same way -- drag up walks,
17
+ // drag sideways turns, and the drag ANGLE blends the two, so a diagonal drag
18
+ // walks a curve and a pure sideways drag turns in place.
19
+ // Keys turn a bit harder than a full stick deflection -- discrete input wants
20
+ // the snappier response.
21
+ const KEY_TURN = 1.35;
22
+
23
+ function readDrive(keys, stick) {
24
+ let forward = 0;
25
+ let turn = 0;
26
+ if (keyDown(keys, 'KeyW', 'ArrowUp')) forward += 1;
27
+ if (keyDown(keys, 'KeyS', 'ArrowDown')) forward -= 1;
28
+ if (keyDown(keys, 'KeyD', 'ArrowRight')) turn += KEY_TURN;
29
+ if (keyDown(keys, 'KeyA', 'ArrowLeft')) turn -= KEY_TURN;
30
+ if (forward === 0 && turn === 0 && stick?.active && Math.hypot(stick.x, stick.y) >= 0.15) {
31
+ forward = -stick.y;
32
+ turn = stick.x;
33
+ }
34
+ return {
35
+ forward: Math.max(-1, Math.min(1, forward)),
36
+ turn: Math.max(-KEY_TURN, Math.min(KEY_TURN, turn)),
37
+ };
38
+ }
39
+
40
+ // Spring-hover the capsule a touch above the ground (smooths steps and edges);
41
+ // returns whether the player counts as grounded. The spring stays out of the
42
+ // way while the player is on the way up from a jump.
43
+ function applyHover(phys, actor, t, dims, dt) {
44
+ const hit = phys.castRay({ x: t.x, y: t.y, z: t.z }, { x: 0, y: -1, z: 0 }, dims.height, actor);
45
+ const hoverHeight = dims.height / 2 + 0.15;
46
+ const grounded = Boolean(hit && hit.distance < hoverHeight + 0.25);
47
+ const vel = phys.getVelocity(actor);
48
+ if (hit && hit.distance < hoverHeight + 0.4 && vel.y <= 1) {
49
+ const accel = 160 * (hoverHeight - hit.distance) - 18 * vel.y;
50
+ phys.applyImpulse(actor, { x: 0, y: accel * phys.mass(actor) * dt, z: 0 });
51
+ }
52
+ return grounded;
53
+ }
54
+
55
+ function applyMove(phys, actor, move, speed, grounded, dt) {
56
+ const mass = phys.mass(actor);
57
+ const vel = phys.getVelocity(actor);
58
+ if (move) {
59
+ const factor = Math.min(1, dt * 14) * (grounded ? 1 : 0.3);
60
+ phys.applyImpulse(actor, {
61
+ x: (move.x * speed - vel.x) * mass * factor,
62
+ y: 0,
63
+ z: (move.z * speed - vel.z) * mass * factor,
64
+ });
65
+ } else if (grounded) {
66
+ const factor = Math.min(1, dt * 12);
67
+ phys.applyImpulse(actor, { x: -vel.x * mass * factor, y: 0, z: -vel.z * mass * factor });
68
+ }
69
+ }
70
+
71
+ function updateCamera(scene, t, rt, props, dt) {
72
+ const cam = scene.three?.camera;
73
+ if (!cam) return;
74
+ const targetX = t.x - props.camDistance * Math.sin(rt.camAngle);
75
+ const targetY = t.y + props.camHeight;
76
+ const targetZ = t.z + props.camDistance * Math.cos(rt.camAngle);
77
+ // A scene switch teleports the player; snap instead of swooshing the
78
+ // camera across the whole map.
79
+ if (Math.hypot(targetX - cam.position.x, targetZ - cam.position.z) > 20) {
80
+ cam.position.set(targetX, targetY, targetZ);
81
+ } else {
82
+ const lerp = 1 - Math.exp(-12 * dt);
83
+ cam.position.x += (targetX - cam.position.x) * lerp;
84
+ cam.position.y += (targetY - cam.position.y) * lerp;
85
+ cam.position.z += (targetZ - cam.position.z) * lerp;
86
+ }
87
+ cam.lookAt(t.x, t.y + 0.5, t.z);
88
+ }
89
+
90
+ // The nearest Lookable the player is facing, within its range.
91
+ function findLookText(scene, actor, t, facing) {
92
+ const fx = Math.sin(facing);
93
+ const fz = Math.cos(facing);
94
+ let bestText = '';
95
+ let bestDist = Infinity;
96
+ for (const other of scene.actorsWith('Lookable')) {
97
+ if (other.id === actor.id) continue;
98
+ const ot = other.components.Transform;
99
+ if (!ot) continue;
100
+ const dx = ot.x - t.x;
101
+ const dz = ot.z - t.z;
102
+ const dist = Math.hypot(dx, dz);
103
+ const range = other.components.Lookable.range ?? 4;
104
+ if (dist < 0.01 || dist > range || dist >= bestDist) continue;
105
+ if ((dx * fx + dz * fz) / dist < 0.7) continue;
106
+ bestText = other.components.Lookable.text ?? '';
107
+ bestDist = dist;
108
+ }
109
+ return bestText;
110
+ }
111
+
112
+ export class Player {
113
+ static behaviorName = 'Player';
114
+ static defaultProps = { speed: 6, turnSpeed: 2.4, jumpSpeed: 8, camDistance: 9, camHeight: 7 };
115
+
116
+ constructor(props) {
117
+ this.props = props;
118
+ }
119
+
120
+ update(actor, scene, dt) {
121
+ const phys = scene.physics3d;
122
+ if (!phys?.ready()) return;
123
+ const t = actor.components.Transform;
124
+ // Cross-scene state (camera angle, score) lives on the runtime's
125
+ // persistent store so it survives `loadFromFile`; actor.runtime is
126
+ // rebuilt on every scene load.
127
+ const per = (scene.persistent ??= {});
128
+ const rt = actor.runtime;
129
+ if (per.camAngle === undefined) per.camAngle = Math.PI / 4;
130
+ if (per.score === undefined) per.score = 0;
131
+
132
+ const shape = actor.components.Shape;
133
+ const dims = { radius: shape?.radius ?? 0.35, height: shape?.height ?? 1.4 };
134
+ const grounded = applyHover(phys, actor, t, dims, dt);
135
+ const drive = readDrive(scene.keys, scene.moveInput);
136
+ per.camAngle += drive.turn * this.props.turnSpeed * dt;
137
+ rt.facing = Math.PI - per.camAngle;
138
+ const move =
139
+ drive.forward !== 0
140
+ ? {
141
+ x: Math.sin(per.camAngle) * drive.forward,
142
+ z: -Math.cos(per.camAngle) * drive.forward,
143
+ }
144
+ : null;
145
+ applyMove(phys, actor, move, this.props.speed, grounded, dt);
146
+ t.rotationY = rt.facing / DEG;
147
+
148
+ // Jump: Space on keyboard, or a short tap anywhere on the canvas (which
149
+ // works while another finger drives the drag stick). A tap is buffered
150
+ // briefly so one made just before touchdown still fires.
151
+ if (scene.moveInput?.consumeJump?.()) rt.jumpBuffer = 0.18;
152
+ else rt.jumpBuffer = Math.max(0, (rt.jumpBuffer ?? 0) - dt);
153
+ if ((keyDown(scene.keys, 'Space') || rt.jumpBuffer > 0) && grounded) {
154
+ rt.jumpBuffer = 0;
155
+ const vel = phys.getVelocity(actor);
156
+ phys.setVelocity(actor, { x: vel.x, y: this.props.jumpSpeed, z: vel.z });
157
+ }
158
+ // Fall faster than the rise: extra downward pull past the jump apex kills
159
+ // the floaty arc.
160
+ if (!grounded) {
161
+ const vel = phys.getVelocity(actor);
162
+ if (vel.y < 1) phys.applyImpulse(actor, { x: 0, y: -14 * phys.mass(actor) * dt, z: 0 });
163
+ }
164
+
165
+ updateCamera(scene, t, per, this.props, dt);
166
+ const remaining = scene.actorsWith('Pickup').length;
167
+ const total = per.score + remaining;
168
+ const score =
169
+ total === 0 ? '' : remaining === 0 ? '◆ all shards found' : `◆ ${per.score}/${total}`;
170
+ scene.hud = { score, look: findLookText(scene, actor, t, rt.facing) };
171
+ }
172
+ }
@@ -0,0 +1,19 @@
1
+ // Walk-through scene switching: a sensor actor (a glowing ring works well)
2
+ // that asks the runtime to load another scene when the player touches it.
3
+ // The request is applied by the player loop BETWEEN frames -- never mid
4
+ // physics dispatch -- and a short grace after each switch keeps a portal from
5
+ // instantly bouncing the player back where they came from.
6
+ export class Portal {
7
+ static behaviorName = 'Portal';
8
+ static defaultProps = { scene: '' };
9
+
10
+ constructor(props) {
11
+ this.props = props;
12
+ }
13
+
14
+ onCollisionEnter(actor, scene, other) {
15
+ if (!other.components.Player || !this.props.scene) return;
16
+ if (scene.time < (scene.persistent?.portalGraceUntil ?? 0)) return;
17
+ scene.requestedScene = this.props.scene;
18
+ }
19
+ }
@@ -0,0 +1,75 @@
1
+ // The actor's visual: a primitive mesh with a PBR material, and the source of
2
+ // its collider dimensions. `art` names a `.pxart` (or image) file used as the
3
+ // albedo map, nearest-filtered so pixels stay crisp; `color` tints (or is the
4
+ // whole surface when there is no art -- a flat colour is a fine look here).
5
+ // `solid: true` is one of the two ways to make the collider solid (the other is
6
+ // a `Solid` behavior on the actor); without either the collider is a sensor
7
+ // that reports overlaps but doesn't block.
8
+ import React from 'react';
9
+ import { SelectField, TextField } from '@imports/castle.physics-2d/engine/ui';
10
+ import { AutoFields, overrideProps } from '@imports/castle.physics-2d/engine/autoInspector';
11
+
12
+ const KIND_OPTIONS = [
13
+ { value: 'box', label: 'Box' },
14
+ { value: 'sphere', label: 'Sphere' },
15
+ { value: 'capsule', label: 'Capsule' },
16
+ { value: 'cylinder', label: 'Cylinder' },
17
+ ];
18
+
19
+ function ShapeInspector({ component, setComponent, override }) {
20
+ const art = component.art;
21
+ return (
22
+ <>
23
+ <SelectField
24
+ label="Kind"
25
+ value={component.kind}
26
+ options={KIND_OPTIONS}
27
+ onChange={(kind) => setComponent({ kind })}
28
+ {...overrideProps(override, 'kind')}
29
+ />
30
+ {typeof art === 'object' && art !== null ? (
31
+ <TextField label="Art" value="(per-face art -- edit as JSON)" onChange={() => {}} />
32
+ ) : (
33
+ <TextField
34
+ label="Art"
35
+ value={art ?? ''}
36
+ onChange={(next) => setComponent({ art: next })}
37
+ {...overrideProps(override, 'art')}
38
+ />
39
+ )}
40
+ <AutoFields
41
+ defaultProps={Shape.defaultProps}
42
+ component={component}
43
+ setComponent={setComponent}
44
+ exclude={['kind', 'art', 'artRepeat']}
45
+ override={override}
46
+ />
47
+ </>
48
+ );
49
+ }
50
+
51
+ export class Shape {
52
+ static Inspector = ShapeInspector;
53
+ static behaviorName = 'Shape';
54
+ static defaultProps = {
55
+ kind: 'box', // 'box' | 'sphere' | 'capsule' | 'cylinder'
56
+ width: 1,
57
+ height: 1,
58
+ depth: 1,
59
+ radius: 0.5,
60
+ color: '#c7cfdd',
61
+ art: '',
62
+ artRepeat: null,
63
+ tint: '',
64
+ roughness: 0.7,
65
+ metalness: 0,
66
+ clearcoat: 0,
67
+ emissive: '#000000',
68
+ emissiveIntensity: 1,
69
+ solid: false,
70
+ };
71
+
72
+ constructor(props) {
73
+ this.props = props;
74
+ }
75
+ }
@@ -0,0 +1,11 @@
1
+ // Marker: this actor's collider blocks things. Without it (and without
2
+ // `Shape.solid`), colliders are sensors -- they overlap and report it, which is
3
+ // what pickups and trigger zones want.
4
+ export class Solid {
5
+ static behaviorName = 'Solid';
6
+ static defaultProps = {};
7
+
8
+ constructor(props) {
9
+ this.props = props;
10
+ }
11
+ }
@@ -0,0 +1,24 @@
1
+ // Every 3d actor needs a Transform: position and rotation (degrees) in world
2
+ // units, 1 unit = 1 meter. The physics system keeps it in sync with the actor's
3
+ // rapier body -- dynamic bodies write simulated results back here, fixed and
4
+ // kinematic bodies follow edits made here. The renderer reads it every frame,
5
+ // so Transform is the source of truth for where an actor is.
6
+ export class Transform {
7
+ static behaviorName = 'Transform';
8
+ static defaultProps = { x: 0, y: 0, z: 0, rotationX: 0, rotationY: 0, rotationZ: 0 };
9
+ // Position and rotation are always instance-local (like the 2d kit's Layout
10
+ // x/y): they never read as "overriding" a blueprint value in the inspector,
11
+ // and blueprint edits never move placed instances.
12
+ static propertyMeta = {
13
+ x: { inherit: false },
14
+ y: { inherit: false },
15
+ z: { inherit: false },
16
+ rotationX: { inherit: false },
17
+ rotationY: { inherit: false },
18
+ rotationZ: { inherit: false },
19
+ };
20
+
21
+ constructor(props) {
22
+ this.props = props;
23
+ }
24
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "Barrel",
3
+ "actors": [
4
+ {
5
+ "components": {
6
+ "Transform": { "y": 0.45 },
7
+ "Shape": { "kind": "cylinder", "radius": 0.4, "height": 0.9 },
8
+ "Model": { "file": "models/barrel.pxmodel", "yOffset": -0.45 },
9
+ "Body": { "type": "dynamic", "friction": 0.7 },
10
+ "Solid": {},
11
+ "Lookable": { "text": "a barrel. push it around.", "range": 3 }
12
+ }
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "Crate",
3
+ "actors": [
4
+ {
5
+ "components": {
6
+ "Transform": {
7
+ "y": 0.5
8
+ },
9
+ "Shape": {
10
+ "kind": "box",
11
+ "width": 1,
12
+ "height": 1,
13
+ "depth": 1,
14
+ "art": "drawings/crate.pxart",
15
+ "roughness": 0.85
16
+ },
17
+ "Body": {
18
+ "type": "dynamic",
19
+ "friction": 0.8
20
+ },
21
+ "Solid": {},
22
+ "Lookable": {
23
+ "text": "a crate. push it around.",
24
+ "range": 3
25
+ }
26
+ }
27
+ }
28
+ ]
29
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "Door",
3
+ "actors": [
4
+ {
5
+ "components": {
6
+ "Transform": {
7
+ "y": 1.1
8
+ },
9
+ "Shape": {
10
+ "kind": "box",
11
+ "width": 2,
12
+ "height": 2.2,
13
+ "depth": 0.3,
14
+ "art": {
15
+ "front": "drawings/door.pxart",
16
+ "back": "drawings/door.pxart",
17
+ "sides": "drawings/door-edge.pxart",
18
+ "top": "drawings/door-edge.pxart"
19
+ },
20
+ "color": "#c9a17a",
21
+ "roughness": 0.8
22
+ },
23
+ "Body": {
24
+ "type": "kinematic"
25
+ },
26
+ "Solid": {},
27
+ "Door": {},
28
+ "Lookable": {
29
+ "text": "a door. it opens when someone comes close.",
30
+ "range": 5
31
+ }
32
+ }
33
+ }
34
+ ]
35
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "Gem",
3
+ "actors": [
4
+ {
5
+ "components": {
6
+ "Transform": {
7
+ "y": 0.7
8
+ },
9
+ "Shape": {
10
+ "kind": "sphere",
11
+ "radius": 0.28,
12
+ "color": "#0cf1ff",
13
+ "roughness": 0.25,
14
+ "metalness": 0.2,
15
+ "emissive": "#0cf1ff",
16
+ "emissiveIntensity": 0.5
17
+ },
18
+ "Pickup": {},
19
+ "Lookable": {
20
+ "text": "a shard. walk into it to collect it.",
21
+ "range": 3.5
22
+ }
23
+ }
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "Pillar",
3
+ "actors": [
4
+ {
5
+ "components": {
6
+ "Transform": {
7
+ "y": 1.5
8
+ },
9
+ "Shape": {
10
+ "kind": "cylinder",
11
+ "radius": 0.3,
12
+ "height": 3,
13
+ "art": "drawings/wall.pxart",
14
+ "artRepeat": [
15
+ 1,
16
+ 1.5
17
+ ],
18
+ "color": "#868188",
19
+ "roughness": 0.85
20
+ },
21
+ "Solid": {}
22
+ }
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "Platform",
3
+ "actors": [
4
+ {
5
+ "components": {
6
+ "Transform": {
7
+ "y": 0.2
8
+ },
9
+ "Shape": {
10
+ "kind": "box",
11
+ "width": 2,
12
+ "height": 0.4,
13
+ "depth": 2,
14
+ "art": "drawings/platform.pxart",
15
+ "color": "#657392",
16
+ "roughness": 0.8,
17
+ "metalness": 0.35
18
+ },
19
+ "Solid": {}
20
+ }
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "Player",
3
+ "actors": [
4
+ {
5
+ "components": {
6
+ "Transform": {
7
+ "y": 1
8
+ },
9
+ "Shape": {
10
+ "kind": "capsule",
11
+ "radius": 0.35,
12
+ "height": 1.4,
13
+ "color": "#cf8acb",
14
+ "roughness": 0.4,
15
+ "clearcoat": 0.5
16
+ },
17
+ "Body": {
18
+ "type": "dynamic",
19
+ "lockRotations": true,
20
+ "friction": 0,
21
+ "gravityScale": 2
22
+ },
23
+ "Solid": {},
24
+ "Model": { "file": "models/player.pxmodel", "yOffset": -0.7 },
25
+ "Player": {}
26
+ }
27
+ }
28
+ ]
29
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "Portal",
3
+ "actors": [
4
+ {
5
+ "components": {
6
+ "Transform": { "y": 1.05, "rotationX": 90 },
7
+ "Shape": {
8
+ "kind": "cylinder",
9
+ "radius": 0.9,
10
+ "height": 0.16,
11
+ "color": "#0cf1ff",
12
+ "emissive": "#0cf1ff",
13
+ "emissiveIntensity": 0.65,
14
+ "roughness": 0.3
15
+ },
16
+ "Portal": {},
17
+ "Lookable": { "text": "a portal.", "range": 4.5 }
18
+ }
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "Rock",
3
+ "actors": [
4
+ {
5
+ "components": {
6
+ "Transform": { "y": 0.4 },
7
+ "Shape": { "kind": "box", "width": 1, "height": 0.8, "depth": 1 },
8
+ "Model": { "file": "models/rock.pxmodel", "yOffset": -0.4 },
9
+ "Solid": {},
10
+ "Lookable": { "text": "a rock.", "range": 3 }
11
+ }
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "Statue",
3
+ "actors": [
4
+ {
5
+ "components": {
6
+ "Transform": {
7
+ "y": 0.8
8
+ },
9
+ "Shape": {
10
+ "kind": "cylinder",
11
+ "radius": 0.5,
12
+ "height": 1.6,
13
+ "art": "drawings/statue.pxart",
14
+ "color": "#b8b5b9",
15
+ "roughness": 0.55,
16
+ "metalness": 0.1
17
+ },
18
+ "Solid": {},
19
+ "Lookable": {
20
+ "text": "an old stone statue.",
21
+ "range": 4
22
+ }
23
+ }
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "Tree",
3
+ "actors": [
4
+ {
5
+ "components": {
6
+ "Transform": { "y": 0.9 },
7
+ "Shape": { "kind": "cylinder", "radius": 0.22, "height": 1.8 },
8
+ "Model": { "file": "models/tree.pxmodel", "yOffset": -0.9 },
9
+ "Solid": {},
10
+ "Lookable": { "text": "a tree.", "range": 3.5 }
11
+ }
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "Wall",
3
+ "actors": [
4
+ {
5
+ "components": {
6
+ "Transform": {
7
+ "y": 1
8
+ },
9
+ "Shape": {
10
+ "kind": "box",
11
+ "width": 4,
12
+ "height": 2,
13
+ "depth": 0.4,
14
+ "art": "drawings/wall.pxart",
15
+ "artRepeat": [
16
+ 2,
17
+ 1
18
+ ],
19
+ "color": "#45444f",
20
+ "roughness": 0.9
21
+ },
22
+ "Solid": {}
23
+ }
24
+ }
25
+ ]
26
+ }
@@ -1,4 +1,5 @@
1
1
  {
2
+ "title": "physics-3d",
2
3
  "editor": {
3
4
  "initialPanels": [
4
5
  {
@@ -20,9 +21,11 @@
20
21
  "hiddenPaths": [],
21
22
  "visiblePaths": [
22
23
  "drawings/**",
24
+ "models/**",
23
25
  "scenes/**",
24
26
  "blueprints/**",
25
- "behaviors/**"
27
+ "behaviors/**",
28
+ "assets/**"
26
29
  ],
27
30
  "fileTypes": [
28
31
  {
@@ -41,6 +44,14 @@
41
44
  "editor": "kit",
42
45
  "data": true
43
46
  },
47
+ {
48
+ "ext": ".pxmodel",
49
+ "label": "Model",
50
+ "new": "New model",
51
+ "icon": "clone",
52
+ "editor": "kit",
53
+ "data": true
54
+ },
44
55
  {
45
56
  "ext": ".jsx",
46
57
  "label": "Behavior",
@@ -50,8 +61,15 @@
50
61
  ],
51
62
  "defaultPlayFile": "scenes/main.scene"
52
63
  },
53
- "deckId": "5R39TxaRfLLQ",
54
- "cardId": "AZk0PWHTrFcT",
55
- "title": "basic-2d",
56
- "publishedVersion": "2026-08-06T19:58:54.294Z"
64
+ "imports": {
65
+ "castle.physics-2d": {
66
+ "deckId": "ckRZGFW4iPrx",
67
+ "version": "2026-08-14T21:20:31.152Z"
68
+ }
69
+ },
70
+ "main": "main.jsx",
71
+ "autoUpdateWhenImported": true,
72
+ "deckId": "JH0SclbPVP0y",
73
+ "cardId": "eXfAaUdbSU5A",
74
+ "publishedVersion": "2026-08-21T22:11:54.424Z"
57
75
  }