clone

an endless marble-race generator in the browser; the url is the seed, so every track can be shared

VOID SHARDS (user correction): the void stops growing bushes

Dami committed b82e133
Floating ground props read as set-dressing that forgot gravity. The
void vocabulary is now things that plausibly float:
- GENERIC: floating island SHARDS — torn-off islets (cliff course(s) +
  grass cap + ragged quarter underside), every piece sharing ONE float
  so the islet bobs and drifts as a body; plus space debris (meteors,
  crystals).
- Biome tables purged of ground props (pirate sand rocks, holiday rock
  piles, autumn sand rocks OUT); ships, the ghost, the crypt and the
  snowflakes stay.
- Shard clearance covers the whole islet (cap and underside), not just
  its base.
- Test: floating-ground-prop ban (nature ids in the void must be shard
  components), capless-grass ban, shared-float/scale shard coherence.
  203 green; endless goldens re-pinned.
commit
b82e133b3c8b151f300cbb23c2ff29c4de94422d
parent
2657265edb30939aae7e5cfd81bc7205072f4dbf
author
Dami <dami.etoile@gmail.com>
3 files changed, +114 -69
Msrc/gen/endless/voidDecor.ts85+++++++++++++++++-------------
Mtests/golden_endless.json64+++++++++++-----------
Mtests/void_decor_test.ts34++++++++++++
M src/gen/endless/voidDecor.ts +48 -37
@@ -41,21 +41,18 @@ interface VoidGroup {
4141 ids: readonly string[];4242 scale: readonly [number, number];4343 weight: number;44+ /** Composite floating-island SHARD (user correction: a bush or a45+ * ground rock floating in air reads WRONG — the void wants things46+ * that plausibly float). A shard is a torn-off islet: cliff block(s)47+ * with a grass cap and a ragged quarter-course underside, every piece48+ * sharing ONE float phase so the islet bobs as a unit. */49+ shard?: boolean;4450 }4551 46-/** Always-on shoals: nature rocks read as void-neutral debris. */52+/** Always-on vocabulary: floating island shards + space debris — never53+ * bare ground props (those read as set-dressing that forgot gravity). */4754 const GENERIC: readonly VoidGroup[] = [48- {49- ids: [50- "nature-kit:rock_largeA",51- "nature-kit:rock_largeD",52- "nature-kit:rock_tallB",53- "nature-kit:stone_largeC",54- "nature-kit:stone_tallD",55- ],56- scale: [0.8, 1.6],57- weight: 3,58- },55+ { ids: [], scale: [0.5, 1.1], weight: 3, shard: true },5956 {6057 ids: [6158 "space-kit:meteor_half",
@@ -67,7 +64,9 @@ const GENERIC: readonly VoidGroup[] = [
6764 },6865 ];6966 70-/** Biome-flavored clusters, keyed by this segment's island biomes. */67+/** Biome-flavored clusters, keyed by this segment's island biomes —68+ * silhouettes that plausibly ride the air (ships, a ghost, snow), never69+ * ground props. */7170 const BIOME_GROUPS: Record<string, readonly VoidGroup[]> = {7271 tropical: [7372 {
@@ -80,11 +79,10 @@ const BIOME_GROUPS: Record<string, readonly VoidGroup[]> = {
8079 scale: [0.6, 0.9],8180 weight: 3,8281 },83- { ids: ["pirate-kit:rocks-sand-a"], scale: [0.25, 0.4], weight: 1.5 },8482 ],8583 spooky: [8684 { ids: ["graveyard-kit:character-ghost"], scale: [1.2, 1.8], weight: 2.5 },87- { ids: ["graveyard-kit:crypt"], scale: [1.6, 2.4], weight: 1.5 },85+ { ids: ["graveyard-kit:crypt"], scale: [1.6, 2.4], weight: 1 },8886 ],8987 winter: [9088 {
@@ -96,22 +94,6 @@ const BIOME_GROUPS: Record<string, readonly VoidGroup[]> = {
9694 scale: [0.8, 1.4],9795 weight: 3,9896 },99- {100- ids: ["holiday-kit:rocks-medium", "holiday-kit:rocks-small"],101- scale: [0.5, 0.8],102- weight: 2,103- },104- ],105- autumn: [106- {107- ids: [108- "survival-kit:rock-sand-a",109- "survival-kit:rock-sand-b",110- "survival-kit:rock-sand-c",111- ],112- scale: [0.9, 1.5],113- weight: 2.5,114- },11597 ],11698 };11799
@@ -254,18 +236,47 @@ export function planVoidDecor(
254236 (r.float() - 0.5) * 0.24,255237 ] as Vec3256238 : undefined;239+ const float: NonNullable<Placement["float"]> = {240+ amp: 0.1 + r.float() * 0.3,241+ period,242+ phase: r.float() * period,243+ ...(drift ? { drift } : {}),244+ };245+ if (group.shard) {246+ // Floating island shard: cliff course(s) + grass cap + a ragged247+ // quarter underside, one rotation, ONE shared float (the islet248+ // bobs and drifts as a body). Offsets scale with the shard (the249+ // pieces' origins are bottom-center; a course is 1.0 authored).250+ const mat = r.chance(0.6) ? "rock" : "stone";251+ const tall = r.chance(0.3);252+ const rot = r.int(0, 4) as Placement["rotation"];253+ if (out.length + (tall ? 4 : 3) > VOID_MAX_PIECES) continue;254+ // The whole islet must clear the line, cap and underside too.255+ if (256+ !clear(px, py + (tall ? 2 : 1) * scale, pz) ||257+ !clear(px, py - 0.25 * scale, pz)258+ ) continue;259+ const shardPiece = (id: string, dy: number): Placement => ({260+ pieceId: id,261+ pos: [px, py + dy * scale, pz],262+ rotation: rot,263+ role: "decor",264+ scale,265+ float,266+ });267+ out.push(shardPiece(`nature-kit:cliff_blockQuarter_${mat}`, -0.25));268+ out.push(shardPiece(`nature-kit:cliff_block_${mat}`, 0));269+ if (tall) out.push(shardPiece(`nature-kit:cliff_block_${mat}`, 1));270+ out.push(shardPiece("nature-kit:ground_grass", tall ? 2 : 1));271+ continue;272+ }257273 out.push({258274 pieceId: r.pick(group.ids as string[]),259275 pos: [px, py, pz],260276 rotation: r.int(0, 4) as Placement["rotation"],261277 role: "decor",262278 scale,263- float: {264- amp: 0.1 + r.float() * 0.3,265- period,266- phase: r.float() * period,267- ...(drift ? { drift } : {}),268- },279+ float,269280 });270281 }271282 };
M tests/golden_endless.json +32 -32
@@ -2,58 +2,58 @@
22 "__version": "g555803244402",33 "alpha": [44 "57ffc533ec8d9cd6",5- "e16d3cc236c8ea8f",6- "a4a5bce81ce5960f",7- "afb9de4dd5d431fe",8- "d9ed72047151d83b",9- "41fd606a56b859f9",5+ "bc238a46934c67e1",6+ "721fc506c55827b3",7+ "56a60f71540070a3",8+ "fa11ce6f24876d10",9+ "4753ec196247059c",1010 "5e2cc6bfef74ca65",11- "643768d862c523b4",12- "957a5c74e259eea2",13- "8b0072a4f673353d",14- "1855054916b86a25",15- "59205ee048b615b0"11+ "3ea7f45732d55eee",12+ "a8cc3cb074cdd426",13+ "3614bd0a00898560",14+ "25c495c16c7e686a",15+ "147c3d090efddb99"1616 ],1717 "zx9k2m4p1q": [18- "28b90ec41fc026a4",19- "6980f6eed6e673a3",20- "bc88a345783b5a0b",18+ "4f301318b921d736",19+ "e4cff0ba37438341",20+ "f42849d9719de86a",2121 "91ed1e1fb57cf853",2222 "152008334da2dac6",23- "8b85689c81846012",23+ "ca3c912955f256ed",2424 "509409b6fd771230",2525 "4ed82fb13b24dc73",2626 "6b6184fc9a642961",27- "6482a0b5548df169",28- "e7c31d2e4fdcfe95",29- "9c8a38414ca0e53c"27+ "36d1abddc25b9ef3",28+ "08fadf7911b07e85",29+ "1f0686c06916f55f"3030 ],3131 "dami": [32- "dca6314d8a394a4c",33- "1a3b97a3750a6e90",32+ "3d6b9971a9f81e73",33+ "5b341ee6e4d55e9a",3434 "7f8b098959be3ffa",35- "25b26c94ff2ecb39",35+ "0854f062a74a4797",3636 "56daa31fbe30f013",37- "ccdc69f9dc9e4322",37+ "2b9204f2a1c38639",3838 "53a805d9b41c764c",39- "32d88306c8a07e8a",40- "4befa3ea1bfca303",41- "0878138bec723532",39+ "edaa5a7d93b0ba7d",40+ "65376c2cbdca45fe",41+ "8b0dd61ebfbb8178",4242 "6640de5a3b4c5f78",43- "ad7e72fe64fbe1ff"43+ "b87bcda9bf82596f"4444 ],4545 "marbles": [4646 "2c2040556134a62e",4747 "e79e59f656a205c1",48- "618433fa54fb570c",49- "206723058faac281",50- "0b6701e7934db90f",51- "f236d07ffcc7e41c",52- "4dd60e2296baf3cf",48+ "446bfb98d3746d5f",49+ "a9d82f47ee723d6e",50+ "58d7465e7448a844",51+ "6187b38749531e8b",52+ "ee96d613b9344a51",5353 "e77b8633d9e2015a",54- "139c393a347b10c4",54+ "1445cf669ab83d34",5555 "ae7e93c04de083b3",5656 "0d5e85ca411aa200",57- "274216627012303f"57+ "df8f0b5182c73dc9"5858 ]5959 }
M tests/void_decor_test.ts +34 -0
@@ -133,3 +133,37 @@ Deno.test("void decor: events are rare and never consecutive", () => {
133133 assert(events > 0, "no void events across the corpus");134134 assert(events / segments < 0.2, `events too dense: ${events}/${segments}`);135135 });136+137+Deno.test("void decor: no floating ground props; shards bob as one body", () => {138+ // User correction: bushes/rocks floating in air read wrong. The only139+ // nature-kit pieces allowed in the void are shard components (cliff140+ // courses + the grass cap); every grass cap must ride a cliff course141+ // at the same (x,z) sharing the SAME float params.142+ const SHARD_OK = /^nature-kit:(cliff_block(Quarter|Half)?_(rock|stone)|ground_grass)$/;143+ let shards = 0;144+ for (let n = 0; n < 20; n++) {145+ const w = new EndlessWorld(`void-${n}`, catalog);146+ for (let i = 0; i < 8; i++) {147+ const vd = w.get(i).voidDecor;148+ for (const p of vd) {149+ if (!p.pieceId.startsWith("nature-kit:")) continue;150+ assert(151+ SHARD_OK.test(p.pieceId),152+ `void-${n} seg ${i}: floating ground prop ${p.pieceId}`,153+ );154+ if (p.pieceId === "nature-kit:ground_grass") {155+ shards++;156+ const under = vd.find((q) =>157+ q !== p && q.pieceId.includes("cliff_block") &&158+ Math.abs(q.pos[0] - p.pos[0]) < 1e-9 &&159+ Math.abs(q.pos[2] - p.pos[2]) < 1e-9160+ );161+ assert(under, `void-${n} seg ${i}: capless grass in the void`);162+ assertEquals(under!.float, p.float, "shard pieces must share float");163+ assertEquals(under!.scale, p.scale, "shard pieces must share scale");164+ }165+ }166+ }167+ }168+ assert(shards > 0, "no island shards emerged across the corpus");169+});