first commit

This commit is contained in:
2026-10-06 12:16:46 +09:00
commit 619d980098
415 changed files with 22283 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
const controller = await readFile(new URL('../src/input/CameraController.ts', import.meta.url), 'utf8');
const html = await readFile(new URL('../index.html', import.meta.url), 'utf8');
const main = await readFile(new URL('../src/main.ts', import.meta.url), 'utf8');
const historyShader = await readFile(new URL('../src/shaders/region-history.frag', import.meta.url), 'utf8');
const mosaicShader = await readFile(new URL('../src/shaders/mosaic.frag', import.meta.url), 'utf8');
assert.match(controller, /maxOffset = 0\.5/);
assert.match(controller, /clamp\(value, 0, 0\.9\)/);
assert.match(controller, /multiplyScalar\(sensitivity\)/);
assert.match(html, /id="camera-offset" type="range" min="0" max="0\.9" step="0\.01" value="0\.5"/);
assert.match(html, /id="sensitivity" type="range" min="0\.2" max="2"/);
assert.match(html, /Camera Offset <output id="cameraOut">0\.50/);
assert.match(html, /권장 0\.35–0\.65 · 시점 이동 범위/);
assert.match(mosaicShader, /edgeTaper=style<=\.5\?1\.25:\.75/);
assert.match(mosaicShader, /historyTexel=clamp\(floor\(fract\(gridUv\)\*8\.\),vec2\(0\.\),vec2\(7\.\)\)/);
assert.equal(Math.floor(0.45 * 8), 3, 'split sample just left of the half-cell boundary stays in texel 3');
assert.equal(Math.floor(0.50 * 8), 4, 'split sample at the half-cell boundary enters texel 4');
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
const cameraPosition = (target, sensitivity, maxOffset) => clamp(target * sensitivity, -1, 1) * maxOffset;
assert.equal(cameraPosition(1, 1, 0.5), 0.5, 'default offset reaches the configured midpoint');
assert.equal(cameraPosition(1, 1, 0.9), 0.9, 'maximum offset reaches 0.9');
assert.equal(cameraPosition(1, 2, 0.9), 0.9, 'sensitivity does not exceed the view clamp');
assert.equal(clamp(1, 0, 0.9), 0.9, 'UI maximum matches controller clamp');
const signatureStart = main.indexOf('function currentSplitHistorySignature()');
const signatureEnd = main.indexOf('\nfunction clearSplitHistory()', signatureStart);
assert.ok(signatureStart >= 0 && signatureEnd > signatureStart, 'history reset signature exists');
const signature = main.slice(signatureStart, signatureEnd);
for (const key of ['mode', 'effectInput.value', 'mosaicStyleInput.value', 'active.uuid', 'depth.uuid', 'uRegionAvailable', 'uGridSize', 'uStrength', 'uRegionStrength', 'uCurvature', 'uPaneSize', 'matrixWorld', 'uDepthScale', 'uDepthBias', 'renderTarget.width', 'regionAtlasTarget.width', 'regionPaletteTarget.width']) {
assert.ok(signature.includes(key), `model-reset signature includes ${key}`);
}
assert.doesNotMatch(signature, /uContentViewProjection|cameraController|uContentCameraPosition/, 'camera movement must not reset split history');
assert.match(main, /const writeIndex = 1 - splitHistoryRead/);
assert.match(main, /uPreviousHistory\.value = splitHistoryTargets\[splitHistoryRead\]\.texture/);
assert.match(main, /uHistoryBlend\.value = splitHistoryInitialized \? 1 - Math\.exp\(-dt \/ 0\.07\) : 1/);
assert.match(main, /updateSplitHistory\(dt\); renderer\.setRenderTarget\(null\); renderer\.render\(scene, camera\)/, 'inactive view updates the signature and clears stale history');
assert.match(main, /splitHistoryTargets\.forEach\(\(target\) => target\.dispose\(\)\)/);
assert.match(main, /target\.setSize\(atlasWidth, atlasHeight\)/);
assert.match(historyShader, /if\(uHistoryBlend>=\.9999\)\{gl_FragColor=current;return;\}/, 'first frame avoids sampling uninitialized history');
assert.match(historyShader, /gl_FragColor=mix\(previous,current,clamp\(uHistoryBlend,0\.,1\.\)\)/);
function advance(fps, seconds, initial, target) {
let value = initial;
const dt = 1 / fps;
for (let frame = 0; frame < Math.round(fps * seconds); frame++) {
const alpha = 1 - Math.exp(-dt / 0.07);
value += (target - value) * alpha;
}
return value;
}
const cadence = [30, 60, 120].map(fps => advance(fps, 0.7, 0, 1));
assert.ok(Math.max(...cadence) - Math.min(...cadence) < 1e-12, `30/60/120Hz history cadence differs: ${cadence}`);
assert.ok(Math.abs(cadence[0] - (1 - Math.exp(-0.7 / 0.07))) < 1e-12, 'history follows the 70ms exponential time constant');
assert.ok(cadence[0] > 0.999, 'a settled model reaches its target after 700ms');
const color = [0.8, 0.25, 0.1], confidence = 0.6;
const premultiplied = [...color.map(channel => channel * confidence), confidence];
assert.deepEqual(premultiplied.slice(0, 3).map(channel => channel / premultiplied[3]), color, 'split color is stored premultiplied by confidence');
const previous = [0, 0, 0, 0], blend = 0.25;
const filtered = previous.map((channel, index) => channel + (premultiplied[index] - channel) * blend);
assert.equal(filtered[3], confidence * blend);
assert.deepEqual(filtered.slice(0, 3).map(channel => channel / filtered[3]), color, 'filtered premultiplied color retains its hue');
console.log(`Camera/history checks passed: default/max clamp, model reset keys, ping-pong and disposal, premultiplied color, cadence ${cadence.map(v => v.toFixed(6)).join('/')}, 70ms settling.`);