[READ-ONLY] Mirror of https://github.com/flo-bit/shapecraft. flo-bit.dev/shapecraft/
0

Configure Feed

Select the types of activity you want to include in your feed.

shapecraft / tests / generators.test.ts
3.6 kB 94 lines
1import { describe, it, expect } from 'vitest' 2import { tree, pine, palm, bush, grass, fern, flower, deadTree, rock } from '../src/generators' 3import type { Mesh } from '../src/core/mesh' 4 5const generators = [ 6 { name: 'tree', gen: tree }, 7 { name: 'pine', gen: pine }, 8 { name: 'palm', gen: palm }, 9 { name: 'bush', gen: bush }, 10 { name: 'grass', gen: grass }, 11 { name: 'fern', gen: fern }, 12 { name: 'flower', gen: flower }, 13 { name: 'dead', gen: deadTree }, 14 { name: 'rock', gen: rock }, 15] as const 16 17// Generators that support snow (trees + shrubs). Grass/ferns don't take snow options. 18const snowGenerators = generators.filter((g) => ['tree', 'pine', 'palm', 'bush'].includes(g.name)) 19 20// Golden snapshots captured after the named-stream RNG migration. A change here means 21// generator output shifted — intentional changes should update these numbers deliberately. 22const golden: Record<string, { verts: number }> = { 23 tree: { verts: 1830 }, 24 pine: { verts: 840 }, 25 palm: { verts: 3630 }, 26 bush: { verts: 3600 }, 27 grass: { verts: 768 }, 28 fern: { verts: 6480 }, 29 flower: { verts: 654 }, 30 dead: { verts: 1400 }, 31 rock: { verts: 1440 }, 32} 33 34function allFinite(m: Mesh): boolean { 35 const p = m.positions 36 for (let i = 0; i < p.length; i++) if (!Number.isFinite(p[i])) return false 37 return true 38} 39 40describe.each(generators)('$name generator', ({ name, gen }) => { 41 it('produces a valid, colored, finite mesh', () => { 42 const m = gen({ seed: 1 }) 43 expect(m.vertexCount).toBeGreaterThan(0) 44 expect(m.colors).not.toBeNull() 45 expect(allFinite(m)).toBe(true) 46 }) 47 48 it('matches the golden vertex count for seed 1', () => { 49 expect(gen({ seed: 1 }).vertexCount).toBe(golden[name].verts) 50 }) 51 52 it('is deterministic: same seed → identical positions', () => { 53 const a = gen({ seed: 7 }).positions 54 const b = gen({ seed: 7 }).positions 55 expect(Array.from(a)).toEqual(Array.from(b)) 56 }) 57 58 it('different seeds produce different geometry', () => { 59 const a = gen({ seed: 1 }).positions 60 const b = gen({ seed: 2 }).positions 61 expect(Array.from(a)).not.toEqual(Array.from(b)) 62 }) 63}) 64 65describe('stream independence at the model level', () => { 66 // The headline benefit of named streams: a feature that only affects color (snow) 67 // must not perturb geometry, because positions come from independent streams. 68 it.each(snowGenerators)('$name: painted snow (depth 0) leaves geometry byte-identical', ({ gen }) => { 69 const bare = gen({ seed: 3, snowColors: [] }) 70 const snowy = gen({ seed: 3, snowColors: ['#ffffff', '#eeeeee'] }) 71 72 // Same geometry... 73 expect(Array.from(snowy.positions)).toEqual(Array.from(bare.positions)) 74 // ...but different coloring. 75 expect(Array.from(snowy.colors!)).not.toEqual(Array.from(bare.colors!)) 76 }) 77}) 78 79describe('geometric snow (snowDepth > 0)', () => { 80 it.each(snowGenerators)('$name: adds a snow layer of real geometry', ({ gen }) => { 81 const bare = gen({ seed: 4, snowColors: [] }) 82 const snowy = gen({ seed: 4, snowColors: ['#eef0f5'], snowDepth: 0.08 }) 83 // The snow shell adds vertices on top of the model. 84 expect(snowy.vertexCount).toBeGreaterThan(bare.vertexCount) 85 // And reaches higher than the bare model (snow sits on top). 86 expect(snowy.boundingBox.max.y).toBeGreaterThan(bare.boundingBox.max.y) 87 }) 88 89 it.each(snowGenerators)('$name: geometric snow is deterministic', ({ gen }) => { 90 const a = gen({ seed: 4, snowColors: ['#eef0f5'], snowDepth: 0.08 }).positions 91 const b = gen({ seed: 4, snowColors: ['#eef0f5'], snowDepth: 0.08 }).positions 92 expect(Array.from(a)).toEqual(Array.from(b)) 93 }) 94})