@vibes.diy/prompts 14.3.0 → 14.3.1
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 +2 -1
- package/llms/voxel.js.map +1 -1
- package/llms/voxel.md +254 -13
- package/package.json +4 -4
package/llms/voxel.js
CHANGED
|
@@ -5,7 +5,8 @@ export const voxelConfig = {
|
|
|
5
5
|
"procedural terrain (noise heightmaps, biomes, caves, ore veins, trees), place/break with a " +
|
|
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
|
-
"streaming, a
|
|
8
|
+
"streaming, a shared voxel palette with material-colored controls, a day/night cycle, " +
|
|
9
|
+
"ambient mobs, and live multiplayer block edits with temporary player presence over Fireproof. " +
|
|
9
10
|
"Covers the survival layer (table-driven crafting tiers, tools with durability, smelting, " +
|
|
10
11
|
"armor, mob combat, hunger, dimensions, per-player inventory, world reset) and the flat " +
|
|
11
12
|
"multi-file project split for bigger games. " +
|
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
|
|
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"}
|
package/llms/voxel.md
CHANGED
|
@@ -13,7 +13,11 @@ veins, endless chunk streaming, day/night, live multiplayer edits, ambient
|
|
|
13
13
|
wildlife — and its bigger successor **Blockcraft World**, which adds the full
|
|
14
14
|
survival layer: crafting tiers, tool durability, smelting, armor, mob combat,
|
|
15
15
|
hunger, creative mode, nether/end dimensions, per-player inventory, and a
|
|
16
|
-
shared world reset.
|
|
16
|
+
shared world reset. **Blocktopia** contributes material-colored controls,
|
|
17
|
+
compact procedural island settings, and sparse per-cell edits; **Block Craft**
|
|
18
|
+
contributes exposed-face chunk meshes and survival feedback. Adapt the useful
|
|
19
|
+
patterns to the persistence contracts below: whole-world autosaves and durable
|
|
20
|
+
movement heartbeats are expensive examples to improve, rather than defaults.
|
|
17
21
|
|
|
18
22
|
## Project shape: an engine in a React shell, split across a few flat files
|
|
19
23
|
|
|
@@ -43,9 +47,10 @@ access.js the access function (see multiplayer section)
|
|
|
43
47
|
Multi-file rules — each one is load-bearing, breaking any of them breaks the
|
|
44
48
|
deployed app:
|
|
45
49
|
|
|
46
|
-
- **All files flat at the root
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
- **All files flat at the root** is a simple default for this example.
|
|
51
|
+
Nested `components/` and `lib/` modules also work in the CLI and codegen.
|
|
52
|
+
**Relative imports resolve to uploaded files** at their exact paths; keep
|
|
53
|
+
the special App, access, backend, and seed entry files at the root.
|
|
49
54
|
- **Relative imports include the extension**: `./core.js`, `./ui.jsx` — the
|
|
50
55
|
client serves files by exact path, so an extensionless `./core` 404s in the
|
|
51
56
|
browser.
|
|
@@ -114,17 +119,83 @@ const state = {
|
|
|
114
119
|
};
|
|
115
120
|
```
|
|
116
121
|
|
|
117
|
-
Keep block types as a table of face colors so one material set covers the world
|
|
122
|
+
Keep block types as a table of face colors so one material set covers the world.
|
|
123
|
+
Use the shared palette below for matching materials, with darker side faces:
|
|
118
124
|
|
|
119
125
|
```js
|
|
120
126
|
const BLOCK_TYPES = {
|
|
121
|
-
grass: { top:
|
|
122
|
-
stone: { top:
|
|
123
|
-
water: { top:
|
|
127
|
+
grass: { top: 0x6fcf5b, side: 0x8a5a3b }, dirt: { top: 0x8a5a3b, side: 0x6b4626 },
|
|
128
|
+
stone: { top: 0x9b9b9b, side: 0x7a7a7a }, sand: { top: 0xf2dc9b, side: 0xd6c78a },
|
|
129
|
+
water: { top: 0x2f8fe0, side: 0x2f8fe0 }, wood: { top: 0xb5793a, side: 0x6b4626 },
|
|
124
130
|
leaves:{ top: 0x3f8f2f, side: 0x3f8f2f }, /* ...ores, planks, snow, etc. */
|
|
125
131
|
};
|
|
126
132
|
```
|
|
127
133
|
|
|
134
|
+
## Voxel palette: the block in hand colors the controls
|
|
135
|
+
|
|
136
|
+
Use the Blocktopia voxel palette in every voxel app. Keep the game-specific
|
|
137
|
+
colors in one named table, shared by material swatches and HUD controls. Add
|
|
138
|
+
game-specific materials to that table; preserve these colors for matching
|
|
139
|
+
materials. The surrounding app retains its supplied theme's surfaces and text.
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
export const VOXEL_PALETTE = {
|
|
143
|
+
grass: "#6fcf5b", dirt: "#8a5a3b", stone: "#9b9b9b",
|
|
144
|
+
wood: "#b5793a", glass: "#9fe3ff", candy: "#ff6fb1",
|
|
145
|
+
neon: "#39ff9e", water: "#2f8fe0", lava: "#ff5a2f",
|
|
146
|
+
erase: "#ff4d6d", ink: "#1a1325", light: "#ffffff",
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
function luminance(hex) {
|
|
150
|
+
const linear = [1, 3, 5].map((i) => {
|
|
151
|
+
const c = parseInt(hex.slice(i, i + 2), 16) / 255;
|
|
152
|
+
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
153
|
+
});
|
|
154
|
+
return linear[0] * 0.2126 + linear[1] * 0.7152 + linear[2] * 0.0722;
|
|
155
|
+
}
|
|
156
|
+
export function readableInk(background) {
|
|
157
|
+
const bg = luminance(background);
|
|
158
|
+
const ratio = (color) => (Math.max(bg, luminance(color)) + 0.05) /
|
|
159
|
+
(Math.min(bg, luminance(color)) + 0.05);
|
|
160
|
+
return ratio(VOXEL_PALETTE.ink) >= ratio(VOXEL_PALETTE.light)
|
|
161
|
+
? VOXEL_PALETTE.ink : VOXEL_PALETTE.light;
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The selected material supplies the active accent; erase mode supplies `erase`.
|
|
166
|
+
Apply it consistently to active view/gravity buttons, action buttons, the
|
|
167
|
+
stage border, selected hotbar ring, and keycap accents. Keep unselected panels
|
|
168
|
+
on theme surfaces (`bg-background/85`, `bg-card`, `text-foreground`) so the
|
|
169
|
+
controls stay readable over sky, water, caves, and bright terrain. Scope the
|
|
170
|
+
variables to the game container, including its HUD and fullscreen subtree:
|
|
171
|
+
|
|
172
|
+
```jsx
|
|
173
|
+
const accent = eraseMode ? VOXEL_PALETTE.erase : selectedMaterial.color;
|
|
174
|
+
const controlStyle = {
|
|
175
|
+
backgroundColor: "var(--voxel-accent)",
|
|
176
|
+
color: "var(--voxel-accent-fg)",
|
|
177
|
+
};
|
|
178
|
+
return <section style={{
|
|
179
|
+
"--voxel-accent": accent,
|
|
180
|
+
"--voxel-accent-fg": readableInk(accent),
|
|
181
|
+
"--voxel-accent-soft": `${accent}33`, // palette colors are six-digit hex
|
|
182
|
+
}}>
|
|
183
|
+
{/* canvas and HUD share this scope */}
|
|
184
|
+
<button style={controlStyle} onClick={cycleView}>Switch view</button>
|
|
185
|
+
</section>;
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Choose the higher-contrast foreground from `ink` and `light` using sRGB relative
|
|
189
|
+
luminance and contrast ratio ([normal text: at least 4.5:1](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html)). For the palette above,
|
|
190
|
+
`dirt` takes white and the other materials and erase take `ink`; recompute for
|
|
191
|
+
additional or customized colors. Apply the same foreground selection to
|
|
192
|
+
hotbar number labels too. A simple brightness threshold can choose white on
|
|
193
|
+
wood, water, or lava even when dark text is more readable. Keep labels/icons,
|
|
194
|
+
`aria-pressed` or `aria-checked`, a visible focus ring, and a two-tone selection
|
|
195
|
+
outline: the accent reinforces the selected mode rather than being its only
|
|
196
|
+
signal. On touch, keep movement/look separate from Place/Break/Jump controls,
|
|
197
|
+
and clear held input on pointer cancellation or lost capture.
|
|
198
|
+
|
|
128
199
|
## Rendering: mesh only the EXPOSED blocks (this is the whole performance game)
|
|
129
200
|
|
|
130
201
|
The single most important rule: **do not create a `THREE.Mesh` per stored
|
|
@@ -151,6 +222,14 @@ Raycasting and collision must read `state.blocks` (the full model), never the
|
|
|
151
222
|
meshes — otherwise you can't hit or collide with a face that isn't currently
|
|
152
223
|
meshed.
|
|
153
224
|
|
|
225
|
+
For larger worlds, use Block Craft's **exposed-face chunk batching**: append
|
|
226
|
+
only visible faces into a `BufferGeometry` per material per chunk, sharing
|
|
227
|
+
materials across chunks. An edit dirties its chunk and any neighboring chunk
|
|
228
|
+
whose boundary face changes; rebuild only those chunks and dispose their old
|
|
229
|
+
geometries. Keep transparent faces in a separate material pass with deliberate
|
|
230
|
+
occlusion rules. The small-world shared-cube example above remains a simpler
|
|
231
|
+
starting point.
|
|
232
|
+
|
|
154
233
|
## Procedural terrain: deterministic noise, layered
|
|
155
234
|
|
|
156
235
|
Terrain must be a **pure function of world coordinates** so chunks generate
|
|
@@ -200,6 +279,19 @@ function loadChunk(cx, cz) {
|
|
|
200
279
|
}
|
|
201
280
|
```
|
|
202
281
|
|
|
282
|
+
### Described islands: save the recipe, regenerate the terrain
|
|
283
|
+
|
|
284
|
+
For a world described in a sentence, use Blocktopia's approach: ask `callAI`
|
|
285
|
+
for structured generator settings, normalize them, then persist that compact
|
|
286
|
+
recipe with a fixed seed and generator version. Bound map dimensions, heights,
|
|
287
|
+
densities, array lengths, and supported tree/animal kinds before generating.
|
|
288
|
+
Keep a usable flat-world option and preserve the description if generation
|
|
289
|
+
fails. Generate the voxel field locally from the saved recipe on every client;
|
|
290
|
+
save only player overrides. Keep seeded randomness and traversal deterministic,
|
|
291
|
+
and retain the matching generator for existing worlds when generation changes.
|
|
292
|
+
Ambient animals, flowers, clouds, and particles stay capped and local unless
|
|
293
|
+
their shared gameplay state actually needs persistence.
|
|
294
|
+
|
|
203
295
|
## Endless streaming: one chunk per frame
|
|
204
296
|
|
|
205
297
|
Each frame, figure out which chunks should be resident around the player
|
|
@@ -258,6 +350,20 @@ toggle** (e.g. `F`) that swaps gravity for direct vertical thrust — great for
|
|
|
258
350
|
building. Add **sprint** (hold `Shift` while moving forward → higher speed, and
|
|
259
351
|
nudge the camera FOV up a few degrees for a sense of speed).
|
|
260
352
|
|
|
353
|
+
### Keyboard: game actions keep the page still
|
|
354
|
+
|
|
355
|
+
During active play, prevent the browser defaults for every key bound to a game action.
|
|
356
|
+
Use `KeyboardEvent.code` for the binding map (`Space`, `ArrowUp`, `KeyW`,
|
|
357
|
+
etc.), and call `event.preventDefault()` in the handled keyboard events so
|
|
358
|
+
Space jumps and arrow keys move without scrolling the page. Apply this only
|
|
359
|
+
while the game has focus or its canvas owns pointer lock, with gameplay active;
|
|
360
|
+
leave typing in chat, inputs, and contenteditable fields, normal button
|
|
361
|
+
activation, and browser shortcuts available outside that input path. CSS
|
|
362
|
+
`touch-action: none` handles touch gestures; keyboard defaults need their own
|
|
363
|
+
handlers. Clear held keys on blur, pointer-lock loss, pause, and tab hiding so
|
|
364
|
+
movement stops even when the matching keyup never arrives. Remove listeners
|
|
365
|
+
when the engine unmounts.
|
|
366
|
+
|
|
261
367
|
### Looking: pointer lock with a click-to-play overlay (+ touch drag)
|
|
262
368
|
|
|
263
369
|
Pointer lock is the number-one confusion in browser voxel games: players click
|
|
@@ -299,6 +405,32 @@ In the engine, read `movementX/Y` when `document.pointerLockElement === canvas`
|
|
|
299
405
|
**drag-to-look on touch** so phones work without pointer lock. Clamp pitch to
|
|
300
406
|
±1.5 rad. Provide on-screen move/jump/place/break buttons for touch.
|
|
301
407
|
|
|
408
|
+
### View switcher: see your own character
|
|
409
|
+
|
|
410
|
+
Include a view switcher between first-person and third-person so players can see their own avatar.
|
|
411
|
+
Provide a labeled HUD button that works on touch and a keyboard shortcut
|
|
412
|
+
(e.g. `V`, shown in the control legend); toggle once per press, ignoring key
|
|
413
|
+
repeat. Render a simple blocky local-player avatar in third-person, following
|
|
414
|
+
the player's position and heading, and hide it in first-person so it does not
|
|
415
|
+
cover the camera. Keep the same player physics and position when switching:
|
|
416
|
+
offset the third-person camera behind the player and shorten that offset when
|
|
417
|
+
terrain blocks the view. Preserve aiming and the player's block-interaction
|
|
418
|
+
reach as the camera moves. Keep the button reachable when pointer lock is
|
|
419
|
+
released, and let the shortcut switch views during locked play.
|
|
420
|
+
|
|
421
|
+
### Optional fullscreen: keep the HUD with the world
|
|
422
|
+
|
|
423
|
+
Offer an optional fullscreen button for a larger play area; the Vibes iframe allows fullscreen.
|
|
424
|
+
When included, call `requestFullscreen()` directly from the button's user
|
|
425
|
+
gesture on the game container that includes both the canvas and HUD. Check
|
|
426
|
+
`document.fullscreenEnabled` and method availability, and handle a rejected
|
|
427
|
+
request by keeping play usable in the embedded view. Derive the Enter/Exit
|
|
428
|
+
fullscreen label from `document.fullscreenElement` on `fullscreenchange`, use
|
|
429
|
+
`document.exitFullscreen()` to exit, and resize the renderer and camera aspect
|
|
430
|
+
to the container on entry and exit. Keep fullscreen and pointer lock as
|
|
431
|
+
separate controls; reacquire pointer lock through the click-to-play overlay
|
|
432
|
+
when needed. Remove the fullscreen listener on unmount.
|
|
433
|
+
|
|
302
434
|
### No text selection, and bind touch as well as mouse
|
|
303
435
|
|
|
304
436
|
Two things separate "works on my laptop" from "works", and both are easy to
|
|
@@ -368,6 +500,109 @@ when a block breaks — cheap, and it makes mining feel real.
|
|
|
368
500
|
|
|
369
501
|
## Multiplayer: live block edits over Fireproof
|
|
370
502
|
|
|
503
|
+
### Keep saved edits separate from temporary movement
|
|
504
|
+
|
|
505
|
+
`useDocument.merge(updates)` updates a local draft and broadcasts its **full
|
|
506
|
+
merged snapshot** to authorized connected peers when it has an `_id`.
|
|
507
|
+
It is a shallow merge, not a durable field-patch API; `save()` persists through
|
|
508
|
+
`database.put()`. The app-facing database has no `database.merge()` method.
|
|
509
|
+
|
|
510
|
+
Use small, separate documents for different lifetimes:
|
|
511
|
+
|
|
512
|
+
- **World recipe and membership:** persist the seed, generator settings,
|
|
513
|
+
version, and world membership once, then only when deliberately changed.
|
|
514
|
+
- **Placed/mined cells:** persist one small override per world and coordinate.
|
|
515
|
+
Include `worldId` in both the ID and indexed fields when an app has several
|
|
516
|
+
worlds. Keep a `removed: true` override for mined procedural terrain so it
|
|
517
|
+
stays gone after reload. Use the consistency model below for concurrent edits.
|
|
518
|
+
- **Inventory, health, and resume checkpoints:** persist per world and player,
|
|
519
|
+
with change detection and an explicit commit/checkpoint cadence. Commit
|
|
520
|
+
important collected items when the action completes; a player-pose heartbeat
|
|
521
|
+
should carry only pose. `save()` still creates a durable write. To commit in
|
|
522
|
+
the same callback as a draft update, pass the complete next snapshot to
|
|
523
|
+
`save(snapshot)`; a bare `save()` can read the previous render's draft.
|
|
524
|
+
A nested object replaces that entire field rather than merging individual
|
|
525
|
+
coordinates.
|
|
526
|
+
- **Live pose/aim:** use a dedicated presence draft and `merge()` without
|
|
527
|
+
`save()`. Keep the movement cadence bounded and heartbeat while stationary.
|
|
528
|
+
Temporary overlays expire after about 12 seconds without updates or when the
|
|
529
|
+
peer disconnects; they are not a durable resume position or offline edit log.
|
|
530
|
+
- **Activity feed:** use a bounded/coalesced recent-action summary unless a
|
|
531
|
+
durable audit trail is required. Showing only eight results does not bound
|
|
532
|
+
storage when every block placement appends a new activity document.
|
|
533
|
+
|
|
534
|
+
Blocktopia's per-coordinate writes already avoid saving an entire world for
|
|
535
|
+
each block. The useful `merge()` substitution is its recurring **presence**
|
|
536
|
+
write. Block Craft's whole-world snapshot every two seconds combines edits,
|
|
537
|
+
inventory, vitals, and pose; split those lifetimes before adopting its survival
|
|
538
|
+
systems. Fewer durable commits and smaller documents reduce persistence work;
|
|
539
|
+
measure wire bytes and delivery latency separately before claiming faster sync.
|
|
540
|
+
|
|
541
|
+
For a world whose membership is already established, mount this component
|
|
542
|
+
with `key={worldId + ":" + handle}`. Its hook stays at component scope and
|
|
543
|
+
starts a new draft when the player changes worlds. `poseRef` is the engine's
|
|
544
|
+
latest `{ x, y, z, theta }` snapshot; animation updates that ref without React
|
|
545
|
+
state or database writes:
|
|
546
|
+
|
|
547
|
+
```jsx
|
|
548
|
+
function PlayerPresence({ worldId, handle, poseRef, useDocument }) {
|
|
549
|
+
const { merge } = useDocument({
|
|
550
|
+
_id: `presence:${worldId}:${handle}`, type: "presence",
|
|
551
|
+
worldId, userHandle: handle,
|
|
552
|
+
});
|
|
553
|
+
React.useEffect(() => {
|
|
554
|
+
let previous = "";
|
|
555
|
+
let lastSent = 0;
|
|
556
|
+
const timer = setInterval(() => {
|
|
557
|
+
const p = poseRef.current;
|
|
558
|
+
if (!p) return;
|
|
559
|
+
const pose = { x: p.x, y: p.y, z: p.z, theta: p.theta };
|
|
560
|
+
const stamp = JSON.stringify(pose);
|
|
561
|
+
const now = Date.now();
|
|
562
|
+
if (stamp === previous && now - lastSent < 5000) return;
|
|
563
|
+
merge({ ...pose, at: now });
|
|
564
|
+
previous = stamp;
|
|
565
|
+
lastSent = now;
|
|
566
|
+
}, 350);
|
|
567
|
+
return () => clearInterval(timer);
|
|
568
|
+
}, [merge, poseRef]);
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
Peers can receive this never-saved row through a live query on `worldId`;
|
|
574
|
+
the temporary overlay supplies the index fields. The access function must
|
|
575
|
+
validate the sender's identity, bind `_id` to its world/player, validate finite
|
|
576
|
+
pose coordinates, and require world membership before returning the existing
|
|
577
|
+
world channel. Persist the world/invitation that grants that membership first:
|
|
578
|
+
a presence broadcast does not create grants. Use this matching branch in the
|
|
579
|
+
multi-world access function (the single-world example later uses different IDs
|
|
580
|
+
and `authorHandle`):
|
|
581
|
+
|
|
582
|
+
```js
|
|
583
|
+
if (doc.type === "presence") {
|
|
584
|
+
if (!user || doc.userHandle !== user.userHandle) throw { forbidden: "not you" };
|
|
585
|
+
if (typeof doc.worldId !== "string" || !doc.worldId) throw { forbidden: "world required" };
|
|
586
|
+
if (doc._id !== `presence:${doc.worldId}:${user.userHandle}`) throw { forbidden: "presence ID" };
|
|
587
|
+
if (![doc.x, doc.y, doc.z, doc.theta, doc.at].every(Number.isFinite)) throw { forbidden: "invalid pose" };
|
|
588
|
+
const channel = `world:${doc.worldId}`;
|
|
589
|
+
ctx.requireAccess(channel);
|
|
590
|
+
return { channels: [channel] };
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
A hidden or disconnected tab may miss its heartbeat, so peers expire it rather
|
|
595
|
+
than treating it as saved state. Use receiver arrival time for update freshness.
|
|
596
|
+
Rely on disconnect/expiry for departure:
|
|
597
|
+
an unmount-time `merge()` can be canceled before broadcast. This example uses
|
|
598
|
+
one presence ID per handle; independent same-handle tabs need a session suffix
|
|
599
|
+
and access rules that validate that ID. During migration from saved presence,
|
|
600
|
+
admit old durable rows only if their timestamps are plausibly fresh; their
|
|
601
|
+
first query arrival or reappearance after overlay expiry is not a heartbeat.
|
|
602
|
+
Alternatively, use a distinct ephemeral namespace with matching access rules.
|
|
603
|
+
|
|
604
|
+
### Choose the durable edit conflict model
|
|
605
|
+
|
|
371
606
|
**Know exactly what the substrate gives you before you design for it.** A live
|
|
372
607
|
query returns the **full, latest-per-document result set**, deliveries are
|
|
373
608
|
**unordered** (notifications can be delayed; overlapping re-queries can resolve
|
|
@@ -406,6 +641,11 @@ so **there is no free lunch here.** Pick a model with eyes open:
|
|
|
406
641
|
|
|
407
642
|
### Model A: one doc per coordinate
|
|
408
643
|
|
|
644
|
+
The durable examples below use a single shared world. For a world picker,
|
|
645
|
+
carry `worldId` through document IDs, queries, reset watermarks, accumulator
|
|
646
|
+
lifecycles, and access checks together; use one channel per world. Keep a reset
|
|
647
|
+
in one world from invalidating edits or inventories in another.
|
|
648
|
+
|
|
409
649
|
Give the doc a **deterministic `_id` of `block:<key>`** so every edit at a
|
|
410
650
|
coordinate updates the same doc — the database stays one-doc-per-cell instead of
|
|
411
651
|
replaying every historical value. A break writes a `removed: true` tombstone at
|
|
@@ -739,7 +979,7 @@ tables once in `core.js` and every system (mining, crafting, combat, UI) reads
|
|
|
739
979
|
them — adding content then means adding rows, not code:
|
|
740
980
|
|
|
741
981
|
```js
|
|
742
|
-
export const BLOCKS = [ { id: "grass", name: "Grass", top:
|
|
982
|
+
export const BLOCKS = [ { id: "grass", name: "Grass", top: 0x6fcf5b, side: 0x8a5a3b, drops: "dirt" }, /* … */ ]
|
|
743
983
|
export const ITEMS = { plank: { name: "Plank" }, stick: { name: "Stick" }, ironIngot: { name: "Iron Ingot" }, /* … */ }
|
|
744
984
|
export const RECIPES = [
|
|
745
985
|
{ id: "planks", out: { plank: 4 }, needs: { wood: 1 } },
|
|
@@ -772,10 +1012,11 @@ The systems that ride those tables:
|
|
|
772
1012
|
- **World depth** — keep a real vertical range (e.g. bedrock at `y = -128`)
|
|
773
1013
|
so caves and ores have somewhere to live.
|
|
774
1014
|
|
|
775
|
-
Persist the survival state as typed docs (see access function
|
|
776
|
-
`inventory` doc per player (`inv:<handle>`), and a `worldReset`
|
|
777
|
-
owner writes — clients ignore voxel edits older than its
|
|
778
|
-
|
|
1015
|
+
Persist the survival state as typed docs (see the single-world access function
|
|
1016
|
+
above): one `inventory` doc per player (`inv:<handle>`), and a `worldReset`
|
|
1017
|
+
singleton the owner writes — clients ignore voxel edits older than its
|
|
1018
|
+
timestamp. For several worlds, scope these IDs and queries by world as well
|
|
1019
|
+
(`inv:<worldId>:<handle>`), and update the access checks to enforce that scope.
|
|
779
1020
|
|
|
780
1021
|
## Ambient life & atmosphere (cheap, high-impact)
|
|
781
1022
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibes.diy/prompts",
|
|
3
|
-
"version": "14.3.
|
|
3
|
+
"version": "14.3.1",
|
|
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.
|
|
41
|
-
"@vibes.diy/identity": "14.3.
|
|
42
|
-
"@vibes.diy/use-vibes-types": "14.3.
|
|
40
|
+
"@vibes.diy/call-ai-v2": "14.3.1",
|
|
41
|
+
"@vibes.diy/identity": "14.3.1",
|
|
42
|
+
"@vibes.diy/use-vibes-types": "14.3.1",
|
|
43
43
|
"arktype": "~2.2.3",
|
|
44
44
|
"json-schema-faker": "~0.6.3"
|
|
45
45
|
},
|