@vibes.diy/prompts 14.3.35 → 14.3.36

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.
@@ -0,0 +1,244 @@
1
+ # Voxel worlds — the first draft: a viewport that plays
2
+
3
+ A voxel app's first draft is a full-screen 3D viewport with a block world you
4
+ can fly through and look around, drawn the fast way, with the HUD floating over
5
+ it. Placing and breaking blocks, walking with collision, endless terrain,
6
+ saving edits and sharing the world are the next pass. What the draft owes that
7
+ pass is the right skeleton: the world lives in a flat `Map`, the terrain is
8
+ drawn as one `InstancedMesh`, and the engine is built once inside an effect —
9
+ so the next pass adds to this file rather than rewriting it.
10
+
11
+ Unlike the page-shaped draft above, a world does not flow down the page: the
12
+ viewport is `fixed inset-0` and the controls sit on top of it.
13
+
14
+ ### `App.jsx`
15
+ ```jsx
16
+ import React, { useEffect, useRef, useState } from "react";
17
+ import { useFireproof } from "use-fireproof";
18
+ import * as THREE from "three";
19
+ import { Button, Card, CardHeader, CardTitle, CardContent } from "@vibes.diy/look";
20
+
21
+ // The block-in-hand palette (shared by materials and HUD swatches).
22
+ const VOXEL_PALETTE = {
23
+ grass: "#6fcf5b", dirt: "#8a5a3b", stone: "#9b9b9b", wood: "#b5793a", glass: "#9fe3ff", candy: "#ff6fb1",
24
+ neon: "#39ff9e", water: "#2f8fe0", lava: "#ff5a2f", erase: "#ff4d6d", ink: "#1a1325", light: "#ffffff",
25
+ };
26
+ const HOTBAR = ["grass", "dirt", "stone", "wood"];
27
+ const GAME_KEYS = new Set(["KeyW", "KeyA", "KeyS", "KeyD", "KeyC", "Space", "ShiftLeft"]);
28
+ const SIZE = 24; // half-width of the starting island, in blocks
29
+ const SEED = 7;
30
+
31
+ const keyOf = (x, y, z) => `${x}|${y}|${z}`;
32
+ const heightAt = (x, z) => Math.round(Math.sin((x + SEED) * 0.18) * 2 + Math.cos((z - SEED) * 0.15) * 2 + Math.sin((x + z) * 0.06) * 3) + 5;
33
+
34
+ // The world model: every block, buried or not. Collision and picking read this, never the scene.
35
+ function buildBlocks() {
36
+ const blocks = new Map();
37
+ for (let x = -SIZE; x <= SIZE; x++)
38
+ for (let z = -SIZE; z <= SIZE; z++) {
39
+ const h = heightAt(x, z);
40
+ for (let y = 0; y <= h; y++) blocks.set(keyOf(x, y, z), y === h ? "grass" : y > h - 3 ? "dirt" : "stone");
41
+ }
42
+ return blocks;
43
+ }
44
+
45
+ const NB = [[1, 0, 0], [-1, 0, 0], [0, 1, 0], [0, -1, 0], [0, 0, 1], [0, 0, -1]];
46
+ const isHidden = (blocks, x, y, z) => NB.every(([dx, dy, dz]) => blocks.has(keyOf(x + dx, y + dy, z + dz)));
47
+
48
+ // One shared cube, one shared material, one draw call: the exposed blocks as instances with a color each.
49
+ const CUBE = new THREE.BoxGeometry(1, 1, 1);
50
+ const WORLD_MAT = new THREE.MeshLambertMaterial({ color: 0xffffff });
51
+ function buildWorldMesh(blocks) {
52
+ const visible = [];
53
+ for (const [k, type] of blocks) {
54
+ const [x, y, z] = k.split("|").map(Number);
55
+ if (!isHidden(blocks, x, y, z)) visible.push([x, y, z, type]);
56
+ }
57
+ const mesh = new THREE.InstancedMesh(CUBE, WORLD_MAT, visible.length);
58
+ const m = new THREE.Matrix4();
59
+ const c = new THREE.Color();
60
+ visible.forEach(([x, y, z, type], i) => {
61
+ mesh.setMatrixAt(i, m.makeTranslation(x + 0.5, y + 0.5, z + 0.5));
62
+ mesh.setColorAt(i, c.set(VOXEL_PALETTE[type]));
63
+ });
64
+ mesh.instanceMatrix.needsUpdate = true;
65
+ mesh.instanceColor.needsUpdate = true;
66
+ return mesh;
67
+ }
68
+
69
+ export default function App() {
70
+ const { useLiveQuery } = useFireproof("skyIsland");
71
+ const { docs: worlds } = useLiveQuery("type", { key: "world" });
72
+ const world = worlds[0];
73
+ const mountRef = useRef(null);
74
+ const engineRef = useRef(null);
75
+ const [locked, setLocked] = useState(false);
76
+ const [held, setHeld] = useState(HOTBAR[0]);
77
+
78
+ // The engine, built once: renderer, scene, world mesh, input, loop. React only draws the HUD over it.
79
+ useEffect(() => {
80
+ const mount = mountRef.current;
81
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
82
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
83
+ renderer.setSize(mount.clientWidth, mount.clientHeight);
84
+ mount.appendChild(renderer.domElement);
85
+
86
+ const scene = new THREE.Scene();
87
+ scene.background = new THREE.Color(0x8ecbff);
88
+ scene.fog = new THREE.Fog(0x8ecbff, 40, 140);
89
+ const camera = new THREE.PerspectiveCamera(75, mount.clientWidth / mount.clientHeight, 0.1, 500);
90
+ scene.add(new THREE.HemisphereLight(0xffffff, 0x8a5a3b, 0.9));
91
+ const sun = new THREE.DirectionalLight(0xffffff, 1);
92
+ sun.position.set(30, 60, 20);
93
+ scene.add(sun);
94
+
95
+ const blocks = buildBlocks();
96
+ const worldMesh = buildWorldMesh(blocks);
97
+ scene.add(worldMesh);
98
+
99
+ const state = { pos: new THREE.Vector3(0, heightAt(0, 0) + 3, 8), yaw: 0, pitch: -0.2, keys: {}, running: true };
100
+ const canvas = renderer.domElement;
101
+ const onLock = () => setLocked(document.pointerLockElement === canvas);
102
+ const onMove = (e) => {
103
+ if (document.pointerLockElement !== canvas) return;
104
+ state.yaw -= e.movementX * 0.002;
105
+ state.pitch = Math.max(-1.5, Math.min(1.5, state.pitch - e.movementY * 0.002));
106
+ };
107
+ const onKeyDown = (e) => {
108
+ if (document.pointerLockElement !== canvas) return; // only while the world holds the mouse
109
+ if (e.target.isContentEditable || e.target.closest("input, textarea")) return; // typing stays typing
110
+ if (!GAME_KEYS.has(e.code)) return; // browser shortcuts stay browser shortcuts
111
+ state.keys[e.code] = true;
112
+ e.preventDefault(); // Space flies up, never scrolls the page
113
+ };
114
+ const onKeyUp = (e) => {
115
+ state.keys[e.code] = false;
116
+ };
117
+ const onBlur = () => { state.keys = {}; };
118
+ const onResize = () => {
119
+ renderer.setSize(mount.clientWidth, mount.clientHeight);
120
+ camera.aspect = mount.clientWidth / mount.clientHeight;
121
+ camera.updateProjectionMatrix();
122
+ };
123
+ document.addEventListener("pointerlockchange", onLock);
124
+ document.addEventListener("mousemove", onMove);
125
+ window.addEventListener("keydown", onKeyDown);
126
+ window.addEventListener("keyup", onKeyUp);
127
+ window.addEventListener("blur", onBlur);
128
+ window.addEventListener("resize", onResize);
129
+ engineRef.current = { requestLock: () => canvas.requestPointerLock() };
130
+
131
+ const clock = new THREE.Clock();
132
+ const forward = new THREE.Vector3();
133
+ const right = new THREE.Vector3();
134
+ let raf;
135
+ const loop = () => {
136
+ if (!state.running) return;
137
+ const dt = Math.min(clock.getDelta(), 0.05);
138
+ const speed = (state.keys.ShiftLeft ? 18 : 9) * dt;
139
+ forward.set(-Math.sin(state.yaw), 0, -Math.cos(state.yaw));
140
+ right.set(forward.z, 0, -forward.x);
141
+ if (state.keys.KeyW) state.pos.addScaledVector(forward, speed);
142
+ if (state.keys.KeyS) state.pos.addScaledVector(forward, -speed);
143
+ if (state.keys.KeyD) state.pos.addScaledVector(right, speed);
144
+ if (state.keys.KeyA) state.pos.addScaledVector(right, -speed);
145
+ if (state.keys.Space) state.pos.y += speed;
146
+ if (state.keys.KeyC) state.pos.y -= speed;
147
+ camera.position.copy(state.pos);
148
+ camera.rotation.set(state.pitch, state.yaw, 0, "YXZ");
149
+ renderer.render(scene, camera);
150
+ raf = requestAnimationFrame(loop);
151
+ };
152
+ loop();
153
+
154
+ return () => {
155
+ state.running = false;
156
+ cancelAnimationFrame(raf);
157
+ document.removeEventListener("pointerlockchange", onLock);
158
+ document.removeEventListener("mousemove", onMove);
159
+ window.removeEventListener("keydown", onKeyDown);
160
+ window.removeEventListener("keyup", onKeyUp);
161
+ window.removeEventListener("blur", onBlur);
162
+ window.removeEventListener("resize", onResize);
163
+ worldMesh.dispose();
164
+ renderer.dispose();
165
+ mount.removeChild(canvas);
166
+ };
167
+ }, []);
168
+
169
+ return (
170
+ <div className="fixed inset-0 select-none touch-none bg-background text-foreground font-sans">
171
+ <div ref={mountRef} className="absolute inset-0" />
172
+
173
+ <div className="absolute left-4 top-4 rounded-lg bg-card/90 px-3 py-2 text-card-foreground shadow-md">
174
+ <p className="font-bold">{world?.name ?? "Sky Island"}</p>
175
+ <p className="text-muted-foreground">WASD fly · Space up · C down · Shift fast</p>
176
+ </div>
177
+
178
+ <div className="absolute bottom-4 left-1/2 flex -translate-x-1/2 gap-2 rounded-lg bg-card/90 p-2 shadow-md">
179
+ {HOTBAR.map((type) => (
180
+ <Button key={type} size="sm" variant={type === held ? "default" : "outline"} onClick={() => setHeld(type)}
181
+ style={{ backgroundColor: type === held ? VOXEL_PALETTE[type] : undefined }}>
182
+ {type}
183
+ </Button>
184
+ ))}
185
+ </div>
186
+ {/* TODO(next pass): place and break the held block with a voxel raycast, walk with gravity and collision, save edits as documents */}
187
+
188
+ {!locked && (
189
+ <button onClick={() => engineRef.current?.requestLock()} className="absolute inset-0 z-20 flex items-center justify-center bg-black/55">
190
+ <Card className="max-w-sm">
191
+ <CardHeader>
192
+ <CardTitle>{world?.name ?? "Sky Island"}</CardTitle>
193
+ </CardHeader>
194
+ <CardContent>
195
+ <p>Click to play</p>
196
+ <p className="mt-2 text-muted-foreground">Mouse looks around. Esc gives the mouse back.</p>
197
+ </CardContent>
198
+ </Card>
199
+ </button>
200
+ )}
201
+ </div>
202
+ );
203
+ }
204
+ ```
205
+
206
+ ### `seed.json`
207
+ ```json
208
+ {
209
+ "skyIsland": [
210
+ { "key": "island", "type": "world", "name": "Sky Island", "sky": "clear" }
211
+ ]
212
+ }
213
+ ```
214
+
215
+ Draft note: the next pass adds placing and breaking blocks, walking with gravity and collision, saved edits per player, and inviting friends into the world.
216
+
217
+ ## What stays the same in a voxel draft
218
+
219
+ - **The viewport fills the screen**: the outer element is `fixed inset-0
220
+ select-none touch-none`, the renderer mounts into an `absolute inset-0` div,
221
+ and every control is an `absolute` layer over it. Never the page-flow `main`
222
+ with padding from the shape above.
223
+ - **The engine is built once** in `useEffect(() => {...}, [])` with the
224
+ renderer, scene, world mesh, listeners and loop inside it, `engineRef.current` for the few calls
225
+ React makes in, and a cleanup that stops the loop, removes every listener
226
+ and disposes the mesh and renderer. Per-frame values live in the `state`
227
+ object, never in React state.
228
+ - **`setPixelRatio(Math.min(window.devicePixelRatio, 2))`**, a resize handler
229
+ that updates the renderer size and the camera aspect, and a `Fog` that
230
+ matches the sky color.
231
+ - **The world is a flat `Map` keyed `"x|y|z"`**, and it is drawn as ONE
232
+ `InstancedMesh` of the exposed blocks with one shared `BoxGeometry` and one
233
+ shared material, the color per instance. Never a `THREE.Mesh` per block, and
234
+ never a geometry or material created inside the block loop.
235
+ - **Pointer lock comes from a "Click to play" overlay** whose visibility follows
236
+ the real `pointerlockchange` state, so it returns when the player presses
237
+ Esc. Mouse look clamps pitch to ±1.5 rad. A keydown calls `preventDefault`
238
+ only when the canvas holds the lock, the target is not a text field, and the
239
+ key is in the game's own set; held keys clear on blur.
240
+ - **The palette table** is the Blocktopia voxel palette, in full, named once
241
+ for both the instance colors and the HUD swatches.
242
+ - **What waits for the next pass** is named in the `TODO(next pass)` comment
243
+ and the draft note: placing and breaking, collision and gravity, endless
244
+ terrain, saving edits, sharing. The draft draws none of it.
package/llms/voxel.js CHANGED
@@ -13,6 +13,7 @@ export const voxelConfig = {
13
13
  "minecraft, voxel, block world, sandbox, blocks, mining, building, first-person, 3d game, " +
14
14
  "procedural world, chunks, terrain, overworld, creative mode, survival, crafting, nether",
15
15
  cues: ["voxel", "voxels", "minecraft", "block world", "blocky", "sandbox game"],
16
+ draftVariant: true,
16
17
  importModule: "three",
17
18
  importName: "THREE",
18
19
  importType: "namespace",
package/llms/voxel.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"voxel.js","sourceRoot":"","sources":["../../jsr/llms/voxel.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,WAAW,GAAc;IACpC,IAAI,EAAE,OAAO;IACb,KAAK,EAAE,OAAO;IACd,WAAW,EACT,yFAAyF;QACzF,6FAA6F;QAC7F,4FAA4F;QAC5F,yFAAyF;QACzF,uFAAuF;QACvF,gGAAgG;QAChG,2FAA2F;QAC3F,yFAAyF;QACzF,6CAA6C;QAC7C,2FAA2F;QAC3F,yFAAyF;IAC3F,IAAI,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,CAAC;IAC/E,YAAY,EAAE,OAAO;IACrB,UAAU,EAAE,OAAO;IACnB,UAAU,EAAE,WAAW;CACxB,CAAC"}
1
+ {"version":3,"file":"voxel.js","sourceRoot":"","sources":["../../jsr/llms/voxel.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,WAAW,GAAc;IACpC,IAAI,EAAE,OAAO;IACb,KAAK,EAAE,OAAO;IACd,WAAW,EACT,yFAAyF;QACzF,6FAA6F;QAC7F,4FAA4F;QAC5F,yFAAyF;QACzF,uFAAuF;QACvF,gGAAgG;QAChG,2FAA2F;QAC3F,yFAAyF;QACzF,6CAA6C;QAC7C,2FAA2F;QAC3F,yFAAyF;IAC3F,IAAI,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,CAAC;IAG/E,YAAY,EAAE,IAAI;IAClB,YAAY,EAAE,OAAO;IACrB,UAAU,EAAE,OAAO;IACnB,UAAU,EAAE,WAAW;CACxB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibes.diy/prompts",
3
- "version": "14.3.35",
3
+ "version": "14.3.36",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "exports": {
@@ -37,9 +37,9 @@
37
37
  "license": "Apache-2.0",
38
38
  "dependencies": {
39
39
  "@adviser/cement": "~0.5.34",
40
- "@vibes.diy/call-ai-v2": "14.3.35",
41
- "@vibes.diy/identity": "14.3.35",
42
- "@vibes.diy/use-vibes-types": "14.3.35",
40
+ "@vibes.diy/call-ai-v2": "14.3.36",
41
+ "@vibes.diy/identity": "14.3.36",
42
+ "@vibes.diy/use-vibes-types": "14.3.36",
43
43
  "arktype": "~2.2.3",
44
44
  "json-schema-faker": "~0.6.3"
45
45
  },