mapspinner 0.1.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/.claude/workflows/fps-perf.js +147 -0
- package/.claude/workflows/optimize-dna.js +201 -0
- package/.claude/workflows/shader-bottleneck-dna.js +168 -0
- package/.claude/workflows/speed-dna.js +209 -0
- package/.claude/workflows/startup-perf.js +117 -0
- package/.github/workflows/publish.yml +43 -0
- package/AGENTS.md +265 -0
- package/CHANGELOG.md +31 -0
- package/CLAUDE.md +1 -0
- package/README.md +82 -0
- package/examples/basic-sdk-usage.html +114 -0
- package/package.json +28 -0
- package/planet.html +2181 -0
- package/planet.zip +0 -0
- package/scripts/backend-ab.mjs +88 -0
- package/scripts/dev-chrome.cmd +22 -0
- package/scripts/verify.mjs +69 -0
- package/server.js +127 -0
- package/src/anchor-field.js +559 -0
- package/src/gl-render.js +944 -0
- package/src/index.js +41 -0
- package/src/planet-orchestrator.js +790 -0
- package/src/quadtree.js +160 -0
- package/src/shaders/atmosphere.glsl +215 -0
- package/src/shaders/terrain.glsl +2109 -0
- package/src/terrain-gen-controls.js +122 -0
- package/tests/run.js +58 -0
- package/textures/grass-color.jpg +0 -0
- package/textures/grass-displacement.jpg +0 -0
- package/textures/rock-color.jpg +0 -0
- package/textures/rock-displacement.jpg +0 -0
- package/textures/sand-color.jpg +0 -0
- package/textures/sand-displacement.jpg +0 -0
- package/textures/snow-color.jpg +0 -0
- package/textures/snow-displacement.jpg +0 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// terrain-gen-controls.js -- the ONE live control surface for terrain generation params.
|
|
2
|
+
// Imported by planet.html; installs window.__gen. No rebuild needed: shader params are window.*
|
|
3
|
+
// globals the render reads per-frame; HPF band scales go through the anchor field; biome colors
|
|
4
|
+
// are read live by gl-render.
|
|
5
|
+
//
|
|
6
|
+
// After the C++/wasm + cascade/producer deletion (GPU one-fractal), the old elevation noiseAmp
|
|
7
|
+
// cascade + material ortho-noise tables are GONE: they fed the deleted wasm PL.setNoiseAmp/
|
|
8
|
+
// setSeaBias/setOrthoNoiseAmp setters, which no longer exist. Only the params below still drive
|
|
9
|
+
// anything, so the panel shows only live knobs.
|
|
10
|
+
//
|
|
11
|
+
// __gen.defaults - canonical default for every field (exact reset).
|
|
12
|
+
// __gen.state - current values (mutated by sliders / set()).
|
|
13
|
+
// __gen.set(path,v)- set one field by dot-path, re-apply.
|
|
14
|
+
// __gen.apply() - push the whole state to shader globals + HPF band scales.
|
|
15
|
+
// __gen.get() - read back the live shader globals.
|
|
16
|
+
// __gen.reset() - restore defaults and apply.
|
|
17
|
+
// __gen.serialize()/load(json) - persist/restore.
|
|
18
|
+
|
|
19
|
+
const ORCH = () => window.__planetOrch || null;
|
|
20
|
+
const HPF = () => window.__hpf || null;
|
|
21
|
+
|
|
22
|
+
// ---- canonical defaults (only the params that still drive the GPU one-fractal) ----
|
|
23
|
+
const DEFAULTS = {
|
|
24
|
+
normal: {
|
|
25
|
+
pvNormal: 1, // window.__pvNormal (per-vertex seamless normal, default on)
|
|
26
|
+
fsNormal: 0, // window.__fsNormal (cross-dFdx FS normal, diagnostic, default off)
|
|
27
|
+
},
|
|
28
|
+
lod: {
|
|
29
|
+
splitFactor: null, // window.__splitFactor (null = orchestrator altitude ramp)
|
|
30
|
+
},
|
|
31
|
+
geometry: {
|
|
32
|
+
elevEdgeInset: 0.5, // window.__elevEdgeInset (mesh-edge elevation sample inset; gl-render)
|
|
33
|
+
},
|
|
34
|
+
// biome ramp colors + band edges (gl-render reads window.__gen.state.biome live).
|
|
35
|
+
biome: {
|
|
36
|
+
bcDeepSea: [0.04,0.10,0.28], bcSea: [0.10,0.22,0.42], bcShore: [0.78,0.72,0.50],
|
|
37
|
+
bcLowland: [0.20,0.34,0.15], bcGrass: [0.26,0.40,0.17], bcRock: [0.52,0.43,0.34], // bcRock -> a clearly WARMER TAN-GREY (R>G>B) so it reads as ROCK when lit, not olive-green (the [0.46,0.42,0.37] near-grey read green next to the biome). state.biome OVERRIDES gl-render defaults via window.__gen.
|
|
38
|
+
bcSnow: [0.92,0.94,0.97],
|
|
39
|
+
bandEdgesLo: [150.0,1200.0], bandEdgesHi: [3500.0,6500.0], snowEdges: [6000.0,8500.0], // 8000/10500->6000/8500 (user 2026-06-11 'snowy mountains disappeared' -- see gl-render snowEdges note) // snowEdges 5200/7000->8000/10500 (user 2026-06-10 'entire terrain white': the rock-by-height fix unmasked snow gates tuned pre-4x; full snow from 5.2km whitened the 11.6km massifs; coldSnow onset = snowEdges.x*0.5 follows) // bandEdgesHi 1600/3200->3500/6500 (user 2026-06-10 'rockface everywhere'): tuned on the pre-4x terrain; with 11.6km peaks everything above 3200m read rock BY HEIGHT alone -- rescale the treeline to the new elevation range
|
|
40
|
+
seaDepthM: 3000.0, slopeRock: [0.25,0.5], // [0.25,0.5] USER-SET 2026-06-11 (explicit: 'set __gen.state.biome.slopeRock = [0.25,0.5]'); supersedes the [0.22,0.6] anti-rock-patch revert and the earlier [-0.6,1] calibration
|
|
41
|
+
},
|
|
42
|
+
// REAL-WORLD LOOK overhaul (terraformable lighting/shading levers; applyShaderGlobals sets window
|
|
43
|
+
// globals; gl-render reads them via _g()). Beer-Lambert ocean, biome sat, mottle, sky-fill relief,
|
|
44
|
+
// terminator glow, night floor + earthshine, exposure + post-ACES Look.
|
|
45
|
+
look: {
|
|
46
|
+
exposure: 1.0, skyFill: 0.45, biomeSat: 0.72, variationAmt: 0.04, colorVar: 0.5, vertexAO: 1.0, // variationAmt 0.08->0.04 (user 2026-06-10 'blotchy': the ~50km value mottle painted light/dark patches across the massifs)
|
|
47
|
+
nightFloor: 0.16, termWidth: 0.25, terminatorGlow: 0.30, lookSat: 1.15, lookContrast: 1.08, // nightFloor 0.05->0.16: no black night terrain (2026-06-09)
|
|
48
|
+
detailOverlay: 6.0, hazeMul: 0.65, // 2026-06-10 'pale hazy + featureless': perlin-everywhere albedo+elevation fbm (user-tuned 6) + aerial-perspective strength cut
|
|
49
|
+
ocean: { deep: [0.008,0.025,0.06], shallow: [0.07,0.22,0.26], k: [0.030,0.012,0.0045] },
|
|
50
|
+
},
|
|
51
|
+
// HPF band scales (multipliers on the anchor-field band base values; anchor-field.setBandScales).
|
|
52
|
+
hpf: {
|
|
53
|
+
enabled: 1,
|
|
54
|
+
band: [
|
|
55
|
+
// [continental, regional, local] -- scales multiply the band's baked param magnitude.
|
|
56
|
+
{ seaBiasScale: 1.0, elevAmpScale: 1.0, roughnessScale: 1.0 },
|
|
57
|
+
{ seaBiasScale: 1.0, elevAmpScale: 1.0, roughnessScale: 1.0 },
|
|
58
|
+
{ seaBiasScale: 1.0, elevAmpScale: 1.0, roughnessScale: 1.0 },
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function deepClone(o){ return JSON.parse(JSON.stringify(o)); }
|
|
64
|
+
|
|
65
|
+
function applyShaderGlobals(state){
|
|
66
|
+
window.__pvNormal = state.normal.pvNormal;
|
|
67
|
+
window.__fsNormal = state.normal.fsNormal;
|
|
68
|
+
window.__elevEdgeInset = state.geometry.elevEdgeInset;
|
|
69
|
+
if(state.lod.splitFactor != null) window.__splitFactor = state.lod.splitFactor;
|
|
70
|
+
const L = state.look; if(L){
|
|
71
|
+
window.__exposure = L.exposure; window.__skyFill = L.skyFill; window.__biomeSat = L.biomeSat;
|
|
72
|
+
window.__variationAmt = L.variationAmt; window.__nightFloor = L.nightFloor; window.__termWidth = L.termWidth;
|
|
73
|
+
if(L.colorVar != null) window.__colorVar = L.colorVar;
|
|
74
|
+
if(L.detailOverlay != null) window.__detailOverlay = L.detailOverlay;
|
|
75
|
+
if(L.hazeMul != null) window.__hazeMul = L.hazeMul;
|
|
76
|
+
if(L.vertexAO != null) window.__vertexAO = L.vertexAO;
|
|
77
|
+
window.__terminatorGlow = L.terminatorGlow; window.__lookSat = L.lookSat; window.__lookContrast = L.lookContrast;
|
|
78
|
+
// gl-render reads window['__'+uniformName] (gl-render.js o3 helper) -> the globals must carry
|
|
79
|
+
// the 'u' prefix (__uOceanDeep, not __oceanDeep); the unprefixed names were dead levers.
|
|
80
|
+
if(L.ocean){ window.__uOceanDeep = L.ocean.deep; window.__uOceanShallow = L.ocean.shallow; window.__uOceanK = L.ocean.k; }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function applyHpf(state){
|
|
84
|
+
const f = HPF(); if(!f) return false;
|
|
85
|
+
if(f.setBandScales){ state.hpf.band.forEach((b,i)=> f.setBandScales(i, b)); }
|
|
86
|
+
if(window.__hpfRebake) window.__hpfRebake();
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const __gen = {
|
|
91
|
+
defaults: DEFAULTS,
|
|
92
|
+
state: deepClone(DEFAULTS),
|
|
93
|
+
|
|
94
|
+
apply(){
|
|
95
|
+
applyShaderGlobals(this.state);
|
|
96
|
+
applyHpf(this.state);
|
|
97
|
+
const orch = ORCH(); if(orch && orch.clearCache) orch.clearCache(); // re-sync biome/HPF into the next frame
|
|
98
|
+
return { ok: true };
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
// set one field by dot-path (e.g. 'biome.seaDepthM', 'hpf.band.1.elevAmpScale') and re-apply.
|
|
102
|
+
set(path, v){
|
|
103
|
+
const parts = path.split('.'); let o = this.state;
|
|
104
|
+
for(let i=0;i<parts.length-1;i++){ o = o[parts[i]]; if(o==null) return {err:'bad-path:'+path}; }
|
|
105
|
+
o[parts[parts.length-1]] = v;
|
|
106
|
+
return this.apply();
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
// read the LIVE shader globals back (truth, not just this.state).
|
|
110
|
+
get(){
|
|
111
|
+
return { state: deepClone(this.state),
|
|
112
|
+
liveShader: { pvNormal:window.__pvNormal, fsNormal:window.__fsNormal,
|
|
113
|
+
elevEdgeInset:window.__elevEdgeInset, splitFactor:window.__splitFactor } };
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
reset(){ this.state = deepClone(this.defaults); return this.apply(); },
|
|
117
|
+
serialize(){ return JSON.stringify(this.state); },
|
|
118
|
+
load(json){ try{ this.state = (typeof json==='string')?JSON.parse(json):json; return this.apply(); }catch(e){ return {err:String(e)}; } },
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (typeof window !== 'undefined') window.__gen = __gen;
|
|
122
|
+
export default __gen;
|
package/tests/run.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// mapspinner SDK test runner
|
|
2
|
+
// Validates SDK geometry, API, and core functionality
|
|
3
|
+
|
|
4
|
+
import { createPlanet, createRenderer, Quadtree } from '../src/index.js';
|
|
5
|
+
|
|
6
|
+
console.log('mapspinner SDK Test Suite\n');
|
|
7
|
+
|
|
8
|
+
// Test 1: Module exports
|
|
9
|
+
console.log('[Test 1] Module exports');
|
|
10
|
+
try {
|
|
11
|
+
if (typeof createPlanet !== 'function') throw new Error('createPlanet not exported');
|
|
12
|
+
if (typeof createRenderer !== 'function') throw new Error('createRenderer not exported');
|
|
13
|
+
if (typeof Quadtree !== 'function') throw new Error('Quadtree not exported');
|
|
14
|
+
console.log(' PASS: All core exports present\n');
|
|
15
|
+
} catch (e) {
|
|
16
|
+
console.log(` FAIL: ${e.message}\n`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Test 2: API contract check
|
|
21
|
+
console.log('[Test 2] API contract');
|
|
22
|
+
try {
|
|
23
|
+
if (typeof createPlanet.toString !== 'function') throw new Error('createPlanet not a function');
|
|
24
|
+
const sig = createPlanet.toString();
|
|
25
|
+
if (!sig.includes('gl') && !sig.includes('opts')) throw new Error('createPlanet signature unexpected');
|
|
26
|
+
console.log(' PASS: API contracts valid\n');
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.log(` FAIL: ${e.message}\n`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Test 3: Quadtree structure
|
|
33
|
+
console.log('[Test 3] Quadtree structure');
|
|
34
|
+
try {
|
|
35
|
+
const qt = new Quadtree({ radius: 6360000 });
|
|
36
|
+
if (!qt) throw new Error('Quadtree instantiation failed');
|
|
37
|
+
if (typeof qt.update !== 'function') throw new Error('Quadtree.update not present');
|
|
38
|
+
if (typeof qt.collectVisibleLeaves !== 'function') throw new Error('Quadtree.collectVisibleLeaves not present');
|
|
39
|
+
console.log(' PASS: Quadtree interface valid\n');
|
|
40
|
+
} catch (e) {
|
|
41
|
+
console.log(` FAIL: ${e.message}\n`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Test 4: Configuration validation
|
|
46
|
+
console.log('[Test 4] Config validation');
|
|
47
|
+
try {
|
|
48
|
+
const config = { radius: 6360000, gridMeshSize: 16 };
|
|
49
|
+
if (!config.radius || typeof config.radius !== 'number') throw new Error('Invalid radius');
|
|
50
|
+
if (!config.gridMeshSize || typeof config.gridMeshSize !== 'number') throw new Error('Invalid gridMeshSize');
|
|
51
|
+
console.log(' PASS: Config validation works\n');
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.log(` FAIL: ${e.message}\n`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log('All tests passed (local validation)');
|
|
58
|
+
console.log('Note: Full integration tests require a WebGL2 context (run in browser)');
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|