@vibes.diy/prompts 5.5.12 → 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 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,uEAAuE;IACzE,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,+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 example throughout is a real community app, **Voxel World** by
11
- `isaac-subach` (shared with permission). It ships procedural biomes, caves,
12
- ore veins, endless chunk streaming, a day/night cycle, live multiplayer edits,
13
- and ambient wildlife — in a single `App.jsx`. Every pattern below is pulled
14
- from it.
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
- ## The one-file shape
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). Keep the shared world **open** so anyone
295
- with the link can build — an `access.js` that channels block docs publicly and
296
- allows anonymous writers:
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, and per-voxel Fireproof
343
- docs is what turns a voxel tech demo into a game people actually play.
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.12",
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.12",
28
- "@vibes.diy/identity": "^5.5.12",
29
- "@vibes.diy/use-vibes-types": "^5.5.12",
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
 
@@ -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