@vibes.diy/prompts 5.5.11 → 5.5.13
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.
- package/llms/voxel.js +4 -1
- package/llms/voxel.js.map +1 -1
- package/llms/voxel.md +145 -11
- package/package.json +4 -4
- package/system-prompt-initial-oneshot.md +1 -0
- package/system-prompt-initial.md +1 -0
- package/system-prompt.md +1 -0
package/llms/voxel.js
CHANGED
|
@@ -6,8 +6,11 @@ export const voxelConfig = {
|
|
|
6
6
|
"DDA voxel raycast, an AABB player controller with gravity, jump, sprint, auto step-up and " +
|
|
7
7
|
"un-stick, pointer-lock look with a click-to-play overlay and touch drag, endless chunk " +
|
|
8
8
|
"streaming, a day/night cycle, ambient mobs, and live multiplayer block edits over Fireproof. " +
|
|
9
|
+
"Covers the survival layer (table-driven crafting tiers, tools with durability, smelting, " +
|
|
10
|
+
"armor, mob combat, hunger, dimensions, per-player inventory, world reset) and the flat " +
|
|
11
|
+
"multi-file project split for bigger games. " +
|
|
9
12
|
"minecraft, voxel, block world, sandbox, blocks, mining, building, first-person, 3d game, " +
|
|
10
|
-
"procedural world, chunks, terrain, overworld, creative mode, survival",
|
|
13
|
+
"procedural world, chunks, terrain, overworld, creative mode, survival, crafting, nether",
|
|
11
14
|
importModule: "three",
|
|
12
15
|
importName: "THREE",
|
|
13
16
|
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,+FAA+F;QAC/F,2FAA2F;QAC3F,
|
|
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,+FAA+F;QAC/F,2FAA2F;QAC3F,yFAAyF;QACzF,6CAA6C;QAC7C,2FAA2F;QAC3F,yFAAyF;IAC3F,YAAY,EAAE,OAAO;IACrB,UAAU,EAAE,OAAO;IACnB,UAAU,EAAE,WAAW;CACxB,CAAC"}
|
package/llms/voxel.md
CHANGED
|
@@ -7,13 +7,15 @@ voxel-specific architecture that makes such a game actually playable, and it
|
|
|
7
7
|
calls out the handful of things that make the difference between "cool" and
|
|
8
8
|
"I keep getting stuck / I can't lock my mouse".
|
|
9
9
|
|
|
10
|
-
The worked
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
The worked examples throughout are real community apps by `isaac-subach`
|
|
11
|
+
(shared with permission): **Voxel World** — procedural biomes, caves, ore
|
|
12
|
+
veins, endless chunk streaming, day/night, live multiplayer edits, ambient
|
|
13
|
+
wildlife — and its bigger successor **Blockcraft World**, which adds the full
|
|
14
|
+
survival layer: crafting tiers, tool durability, smelting, armor, mob combat,
|
|
15
|
+
hunger, creative mode, nether/end dimensions, per-player inventory, and a
|
|
16
|
+
shared world reset. Every pattern below is pulled from them.
|
|
15
17
|
|
|
16
|
-
##
|
|
18
|
+
## Project shape: an engine in a React shell, split across a few flat files
|
|
17
19
|
|
|
18
20
|
A voxel game is one big imperative Three.js engine wrapped in a thin React
|
|
19
21
|
shell. Build the engine **once** inside a `useEffect(() => {...}, [])` that owns
|
|
@@ -23,6 +25,50 @@ renders only the HUD, the hotbar, and overlays as normal DOM on top of the
|
|
|
23
25
|
`<canvas>`. Never re-create the engine on re-render, and never drive
|
|
24
26
|
per-frame values through React state — read live values through refs.
|
|
25
27
|
|
|
28
|
+
A small game fits in one `App.jsx`; past ~1,000 lines, split it. The proven
|
|
29
|
+
layout (this is exactly how Blockcraft World is factored):
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
App.jsx entry — the app component; ONLY file with `export default`
|
|
33
|
+
core.js data tables (blocks/items/recipes/tools), worldgen noise,
|
|
34
|
+
texture/material builders, styles — no React, named exports
|
|
35
|
+
WorldCanvas.jsx the 3D engine component (+ error boundary), named exports
|
|
36
|
+
ui.jsx Hotbar, InventoryPanel, CraftingPanel, panels — named exports
|
|
37
|
+
access.js the access function (see multiplayer section)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Multi-file rules — each one is load-bearing, breaking any of them breaks the
|
|
41
|
+
deployed app:
|
|
42
|
+
|
|
43
|
+
- **All files flat at the root.** The CLI push / codegen path reads top-level
|
|
44
|
+
files only — a subdirectory (`components/…`) is silently not uploaded and
|
|
45
|
+
every import of it dangles.
|
|
46
|
+
- **Relative imports include the extension**: `./core.js`, `./ui.jsx` — the
|
|
47
|
+
client serves files by exact path, so an extensionless `./core` 404s in the
|
|
48
|
+
browser.
|
|
49
|
+
- **`App.jsx` is the only entry** and — among the client modules — the only
|
|
50
|
+
`export default`; the other client files use named exports and are reached
|
|
51
|
+
from App by imports. **`access.js` is exempt**: it is server-run, never
|
|
52
|
+
imported from `App.jsx`, and follows the access contract's own export rules
|
|
53
|
+
(`export default` is the catch-all access function; a named export gates
|
|
54
|
+
only the database with the matching name).
|
|
55
|
+
- **Never import from the entry** (`../App.jsx` / `./App.jsx`) — shared
|
|
56
|
+
constants live in a sibling module (`core.js`), imported by both sides.
|
|
57
|
+
- **Imported bindings are read-only.** Module-scope mutable state (a
|
|
58
|
+
`let peaceful` flag, a difficulty setting) cannot be assigned from another
|
|
59
|
+
file — `PEACEFUL = v` across modules throws a TypeError at runtime (message
|
|
60
|
+
varies by engine, e.g. "Assignment to constant variable"). Keep the `let` in
|
|
61
|
+
its home module and export a setter; reads stay live through ESM bindings:
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
// core.js
|
|
65
|
+
export let PEACEFUL = false
|
|
66
|
+
export function setPeaceful(v) { PEACEFUL = v }
|
|
67
|
+
// WorldCanvas.jsx
|
|
68
|
+
import { PEACEFUL, setPeaceful } from "./core.js"
|
|
69
|
+
setPeaceful(true) // ✓ — assignment to the import would crash
|
|
70
|
+
```
|
|
71
|
+
|
|
26
72
|
```jsx
|
|
27
73
|
function CanvasStage({ engineRef, onReady }) {
|
|
28
74
|
const canvasRef = React.useRef(null);
|
|
@@ -291,25 +337,112 @@ React.useEffect(() => {
|
|
|
291
337
|
Apply the local edit **before** the `put` so the world never waits on the
|
|
292
338
|
network, and read live handles/permissions through refs (a value captured in the
|
|
293
339
|
engine closure at load time goes stale — bind live state via `state.on*`
|
|
294
|
-
callbacks or refs, never the closure).
|
|
295
|
-
|
|
296
|
-
|
|
340
|
+
callbacks or refs, never the closure).
|
|
341
|
+
|
|
342
|
+
The `access.js` keeps the shared world **open** — block docs channel publicly
|
|
343
|
+
with anonymous writers allowed — while survival doc types each get a branch
|
|
344
|
+
with their natural owner. Every type the app writes needs its own branch
|
|
345
|
+
**before the final unknown-type throw** (an unhandled type fails the write):
|
|
297
346
|
|
|
298
347
|
```js
|
|
299
348
|
export default function (doc, oldDoc, user, ctx) {
|
|
300
349
|
const WORLD = "world:overworld";
|
|
350
|
+
// No branch may change a doc's type: without this, the open block branch
|
|
351
|
+
// could squat a reserved _id (inv:<handle>, world:reset) and lock out the
|
|
352
|
+
// legitimate writer.
|
|
353
|
+
if (oldDoc && oldDoc.type !== doc.type) throw { forbidden: "type change" };
|
|
301
354
|
if (doc.type === "block") {
|
|
355
|
+
// Block docs are PINNED to their own _id namespace — an open branch that
|
|
356
|
+
// accepted arbitrary _ids could occupy other types' reserved keys.
|
|
357
|
+
if (doc._id !== `block:${doc.key}`) throw { forbidden: "block docs live at block:<key>" };
|
|
302
358
|
if (user && doc.authorHandle && doc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
|
|
303
359
|
return { channels: [WORLD], grant: { public: [WORLD] }, allowAnonymous: true };
|
|
304
360
|
}
|
|
361
|
+
if (doc.type === "inventory") {
|
|
362
|
+
// Exactly one doc per player: the _id is BOUND to the caller's handle, so
|
|
363
|
+
// nobody can mint or overwrite someone else's pouch. Ownership is checked
|
|
364
|
+
// on create AND update — oldDoc is the doc being replaced.
|
|
365
|
+
if (!user) throw { forbidden: "sign in" };
|
|
366
|
+
if (doc._id !== `inv:${user.userHandle}`) throw { forbidden: "not your inventory" };
|
|
367
|
+
if (doc.authorHandle !== user.userHandle) throw { forbidden: "not your inventory" };
|
|
368
|
+
if (oldDoc && oldDoc.authorHandle !== user.userHandle) throw { forbidden: "not your inventory" };
|
|
369
|
+
return { channels: [WORLD], grant: { public: [WORLD] } };
|
|
370
|
+
}
|
|
371
|
+
if (doc.type === "presence") {
|
|
372
|
+
// Roster heartbeat — same self-only rule and _id binding, create AND update.
|
|
373
|
+
if (!user) throw { forbidden: "sign in" };
|
|
374
|
+
if (doc._id !== `presence:${user.userHandle}`) throw { forbidden: "not you" };
|
|
375
|
+
if (doc.authorHandle !== user.userHandle) throw { forbidden: "not you" };
|
|
376
|
+
if (oldDoc && oldDoc.authorHandle !== user.userHandle) throw { forbidden: "not you" };
|
|
377
|
+
return { channels: [WORLD], grant: { public: [WORLD] } };
|
|
378
|
+
}
|
|
379
|
+
if (doc.type === "worldReset") {
|
|
380
|
+
// Singleton at a FIXED _id, owner only — destructive for the whole group.
|
|
381
|
+
// Clients ignore voxel edits older than doc.at, so "reset" deletes
|
|
382
|
+
// nothing; enforce the watermark so it can only move forward.
|
|
383
|
+
if (!user) throw { forbidden: "sign in" };
|
|
384
|
+
ctx.requireRole("owner");
|
|
385
|
+
if (doc._id !== "world:reset") throw { forbidden: "singleton lives at world:reset" };
|
|
386
|
+
if (typeof doc.at !== "number" || (oldDoc && !(doc.at > oldDoc.at))) throw { forbidden: "watermark must move forward" };
|
|
387
|
+
return { channels: [WORLD], grant: { public: [WORLD] } };
|
|
388
|
+
}
|
|
305
389
|
throw { forbidden: "unknown document type" };
|
|
306
390
|
}
|
|
307
391
|
```
|
|
308
392
|
|
|
393
|
+
The pattern generalizes: **self-owned docs bind their `_id` to the writer's
|
|
394
|
+
handle and check ownership on both create and update**; group-destructive
|
|
395
|
+
singletons gate on the owner role.
|
|
396
|
+
|
|
309
397
|
Do the world's block editing on the **client** regardless of write
|
|
310
398
|
permission (`applyBlock` always runs locally); gate only the **sync** on
|
|
311
399
|
`can.create(...)` so a slow or stale rule binding can never freeze the game.
|
|
312
400
|
|
|
401
|
+
## The survival layer: data tables first, systems second
|
|
402
|
+
|
|
403
|
+
Everything Minecraft-ish about progression is **table-driven**. Define the
|
|
404
|
+
tables once in `core.js` and every system (mining, crafting, combat, UI) reads
|
|
405
|
+
them — adding content then means adding rows, not code:
|
|
406
|
+
|
|
407
|
+
```js
|
|
408
|
+
export const BLOCKS = [ { id: "grass", name: "Grass", top: 0x5fbf3f, side: 0x7a5230, drops: "dirt" }, /* … */ ]
|
|
409
|
+
export const ITEMS = { plank: { name: "Plank" }, stick: { name: "Stick" }, ironIngot: { name: "Iron Ingot" }, /* … */ }
|
|
410
|
+
export const RECIPES = [
|
|
411
|
+
{ id: "planks", out: { plank: 4 }, needs: { wood: 1 } },
|
|
412
|
+
{ id: "stonePick", out: { stonePick: 1 }, needs: { cobble: 3, stick: 2 }, station: "bench" },
|
|
413
|
+
{ id: "ironIngot", out: { ironIngot: 1 }, needs: { ironOre: 1, coal: 1 }, station: "kiln" },
|
|
414
|
+
]
|
|
415
|
+
export const TOOLS = { woodPick: { speed: 2, durability: 60 }, stonePick: { speed: 4, durability: 130 }, /* … */ }
|
|
416
|
+
export const HARDNESS = { dirt: 1, stone: 3, ore: 4, obsidian: 5 /* hits (or seconds) to break */ }
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
The systems that ride those tables:
|
|
420
|
+
|
|
421
|
+
- **Mining speed & drops** — breaking takes `HARDNESS[type]` effort divided by
|
|
422
|
+
the equipped tool's `speed`; wear the tool's `durability` per break and drop
|
|
423
|
+
the block's `drops` item into the inventory.
|
|
424
|
+
- **Crafting** — `craftableCount(recipe, inv)` = how many times the inventory
|
|
425
|
+
covers `needs`; stations (bench/kiln) gate recipe visibility. Tiers emerge
|
|
426
|
+
from the tables alone: wood → stone → iron (smelt ore at the kiln).
|
|
427
|
+
- **Combat** — clicking a mob applies weapon damage + knockback (impulse along
|
|
428
|
+
the camera ray), mobs carry `hp`, and a death spawns a particle poof and
|
|
429
|
+
drops. Hostiles pathfind toward the player at night; armor rows reduce
|
|
430
|
+
incoming damage.
|
|
431
|
+
- **Hunger / health** — a slow hunger drain, eating restores it, starvation
|
|
432
|
+
chips health; respawn resets position and bars.
|
|
433
|
+
- **Creative mode** — one boolean that bypasses inventory checks, hardness,
|
|
434
|
+
and durability; keep it a UI toggle, not a fork of the systems.
|
|
435
|
+
- **Dimensions** — nether/end are just different `baseBlock(x, y, z, dim)`
|
|
436
|
+
generators plus a portal block that flips `dim`; the engine, collision, and
|
|
437
|
+
streaming code don't change.
|
|
438
|
+
- **World depth** — keep a real vertical range (e.g. bedrock at `y = -128`)
|
|
439
|
+
so caves and ores have somewhere to live.
|
|
440
|
+
|
|
441
|
+
Persist the survival state as typed docs (see access function below): one
|
|
442
|
+
`inventory` doc per player (`inv:<handle>`), and a `worldReset` singleton the
|
|
443
|
+
owner writes — clients ignore voxel edits older than its timestamp, which
|
|
444
|
+
resets the shared world without deleting anything.
|
|
445
|
+
|
|
313
446
|
## Ambient life & atmosphere (cheap, high-impact)
|
|
314
447
|
|
|
315
448
|
None of this needs persistence — keep it purely visual and client-side:
|
|
@@ -339,5 +472,6 @@ None of this needs persistence — keep it purely visual and client-side:
|
|
|
339
472
|
draw distance.
|
|
340
473
|
|
|
341
474
|
That set — exposed-face meshing, per-axis collision with step-up and un-stick,
|
|
342
|
-
a click-to-play pointer-lock overlay, a DDA raycast,
|
|
343
|
-
|
|
475
|
+
a click-to-play pointer-lock overlay, a DDA raycast, per-voxel Fireproof docs,
|
|
476
|
+
table-driven survival systems, and a flat multi-file split once it grows — is
|
|
477
|
+
what turns a voxel tech demo into a game people actually play.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibes.diy/prompts",
|
|
3
|
-
"version": "5.5.
|
|
3
|
+
"version": "5.5.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"description": "",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"license": "Apache-2.0",
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@adviser/cement": "~0.5.34",
|
|
27
|
-
"@vibes.diy/call-ai-v2": "^5.5.
|
|
28
|
-
"@vibes.diy/identity": "^5.5.
|
|
29
|
-
"@vibes.diy/use-vibes-types": "^5.5.
|
|
27
|
+
"@vibes.diy/call-ai-v2": "^5.5.13",
|
|
28
|
+
"@vibes.diy/identity": "^5.5.13",
|
|
29
|
+
"@vibes.diy/use-vibes-types": "^5.5.13",
|
|
30
30
|
"arktype": "~2.2.3",
|
|
31
31
|
"json-schema-faker": "~0.6.2"
|
|
32
32
|
},
|
|
@@ -256,6 +256,7 @@ Rules for the items:
|
|
|
256
256
|
- Set `_id` explicitly **only** for a well-known singleton (a `config:setname` settings doc); the `key` is still required.
|
|
257
257
|
- Multi-db apps get one array per db; use the exact db names from `App.jsx`.
|
|
258
258
|
- **JSON only — no images or binary.** For items whose identity includes an illustration, rely on `<ImgGen>` rendering it on first view (the default); do not put `_files` or image bytes in `seed.json`.
|
|
259
|
+
- **If the app has an `access.js`, every `type` you emit here must have a branch in it** — but don't add an access function _just_ to satisfy this: an app with no per-document rules keeps the default open data model and seeds fine without one. When there **is** an `access.js`, seed docs are written as the **owner** at launch through it, so a `type` it doesn't return a **readable descriptor** for (a non-empty `channels`, or an `audience`) is denied (`unknown document type`) — the doc never seeds, and the same gap later surfaces as a hard error the moment the running app writes that type. Before finishing, if you emitted an `access.js`, confirm it returns a readable descriptor for every distinct `type` present in `seed.json`. (Deletes go through the same gate: a `db.del` writes a tombstone `{ _id, _deleted: true }` that carries **no** `type`, channel, or author — so branch on `doc._deleted`, then authorize and route it off **`oldDoc`** (the persisted document is the only trustworthy record of the doc's type and owner), returning the same descriptor the live doc got. A bare `_deleted` branch that ignores `oldDoc` either fails the app's own deletes or over-broadens them.)
|
|
259
260
|
|
|
260
261
|
## End every turn with one improvement question
|
|
261
262
|
|
package/system-prompt-initial.md
CHANGED
|
@@ -257,6 +257,7 @@ Rules for the items:
|
|
|
257
257
|
- Set `_id` explicitly **only** for a well-known singleton (a `config:setname` settings doc); the `key` is still required.
|
|
258
258
|
- Multi-db apps get one array per db; use the exact db names from `App.jsx`.
|
|
259
259
|
- **JSON only — no images or binary.** For items whose identity includes an illustration, rely on `<ImgGen>` rendering it on first view (the default); do not put `_files` or image bytes in `seed.json`.
|
|
260
|
+
- **If the app has an `access.js`, every `type` you emit here must have a branch in it** — but don't add an access function _just_ to satisfy this: an app with no per-document rules keeps the default open data model and seeds fine without one. When there **is** an `access.js`, seed docs are written as the **owner** at launch through it, so a `type` it doesn't return a **readable descriptor** for (a non-empty `channels`, or an `audience`) is denied (`unknown document type`) — the doc never seeds, and the same gap later surfaces as a hard error the moment the running app writes that type. Before finishing, if you emitted an `access.js`, confirm it returns a readable descriptor for every distinct `type` present in `seed.json`. (Deletes go through the same gate: a `db.del` writes a tombstone `{ _id, _deleted: true }` that carries **no** `type`, channel, or author — so branch on `doc._deleted`, then authorize and route it off **`oldDoc`** (the persisted document is the only trustworthy record of the doc's type and owner), returning the same descriptor the live doc got. A bare `_deleted` branch that ignores `oldDoc` either fails the app's own deletes or over-broadens them.)
|
|
260
261
|
|
|
261
262
|
## End every turn with one improvement question
|
|
262
263
|
|
package/system-prompt.md
CHANGED
|
@@ -573,6 +573,7 @@ Rules for the items:
|
|
|
573
573
|
- Set `_id` explicitly **only** for a well-known singleton (a `config:setname` settings doc); the `key` is still required.
|
|
574
574
|
- Multi-db apps get one array per db; use the exact db names from `App.jsx`.
|
|
575
575
|
- **JSON only — no images or binary.** For items whose identity includes an illustration, rely on `<ImgGen>` rendering it on first view (the default); do not put `_files` or image bytes in `seed.json`.
|
|
576
|
+
- **If the app has an `access.js`, every `type` you emit here must have a branch in it** — but don't add an access function _just_ to satisfy this: an app with no per-document rules keeps the default open data model and seeds fine without one. When there **is** an `access.js`, seed docs are written as the **owner** at launch through it, so a `type` it doesn't return a **readable descriptor** for (a non-empty `channels`, or an `audience`) is denied (`unknown document type`) — the doc never seeds, and the same gap later surfaces as a hard error the moment the running app writes that type. Before finishing, if you emitted an `access.js`, confirm it returns a readable descriptor for every distinct `type` present in `seed.json`. (Deletes go through the same gate: a `db.del` writes a tombstone `{ _id, _deleted: true }` that carries **no** `type`, channel, or author — so branch on `doc._deleted`, then authorize and route it off **`oldDoc`** (the persisted document is the only trustworthy record of the doc's type and owner), returning the same descriptor the live doc got. A bare `_deleted` branch that ignores `oldDoc` either fails the app's own deletes or over-broadens them.)
|
|
576
577
|
|
|
577
578
|
## End every turn with one improvement question
|
|
578
579
|
|