Files
behind_glass/scripts/diagnose-stock-transmission.mjs
horoli e358e24436 Replace project with current server release and Windows setup
Import the 2026-10-09 source release, Fastify/SQLite backend, restored source
assets, and analytics-enabled server build. Replace files from the previous
release while retaining the existing repository history.

Make builds portable across Windows and Linux, configure local startup,
normalize Windows static asset paths, and fix test fixture cleanup and file
URL handling. Exclude local environment settings, databases, dependencies,
validation output, and TypeScript build cache from version control.

Validation: npm ci, static and server builds, 106 arrival tests, and actual
local HTTP checks passed. The full suite passed 467 of 470 tests; three tests
still require QA fixtures omitted from the supplied release, documented in
README-서버실행.md.
2026-10-09 16:43:35 +09:00

47 lines
6.4 KiB
JavaScript

// Numerical v13 stock-transmission formula applied to the unchanged real B geometry/thickness.
// CPU-only diagnostic: this does not execute the corrected shader or measure GPU pixels.
import {readFile} from 'node:fs/promises';
import {pathToFileURL,fileURLToPath} from 'node:url';
import ts from 'typescript';
import * as THREE from 'three';
const root=fileURLToPath(new URL('../',import.meta.url));
async function compile(file){
let s=await readFile(file,'utf8');
s=s.replace(/from ['"]three['"]/g,`from '${pathToFileURL(root+'node_modules/three/build/three.module.js')}'`);
s=s.replace(/from ['"]three\/addons\/([^'"]+)['"]/g,(_,p)=>`from '${pathToFileURL(root+'node_modules/three/examples/jsm/'+p)}'`);
for(const m of [...s.matchAll(/from ['"](\.[^'"]+)['"]/g)]) s=s.replace(m[0],`from '${await compile(new URL(m[1]+'.ts',file))}'`);
return 'data:text/javascript;base64,'+Buffer.from(ts.transpileModule(s,{compilerOptions:{module:ts.ModuleKind.ES2020,target:ts.ScriptTarget.ES2020}}).outputText).toString('base64');
}
const {PhysicalGlassComparison}=await import(await compile(pathToFileURL(root+'src/rendering/PhysicalGlassComparison.ts')));
const {sampleGobangSurface}=await import(await compile(pathToFileURL(root+'src/rendering/PatternedGlassGeometry.ts')));
const {comparisonLayout}=await import(await compile(pathToFileURL(root+'src/rendering/ComparisonLayout.ts')));
const photo=new THREE.Mesh(new THREE.PlaneGeometry(1,1,128,128),new THREE.ShaderMaterial({uniforms:{uTexture:{value:new THREE.Texture()},uDepthMap:{value:new THREE.Texture()},uDepthScale:{value:.25},uDepthBias:{value:0}}}));
const adapter=new PhysicalGlassComparison({extensions:{has:()=>false}},photo);
const w=Number(process.argv[2]||390),h=Number(process.argv[3]||844), aspect=w/h, photoAspect=.8;
const baseH=Math.min(1.78*aspect/photoAspect,1.45),shape={width:baseH*photoAspect*.8,height:baseH*.8,grid:20,curvature:1};
const panes=comparisonLayout(w,h,aspect).panes, pane=panes[0];
const camera=new THREE.PerspectiveCamera(40,aspect,.1,100);camera.position.z=3;camera.updateMatrixWorld();
adapter.sync(shape,.6,new THREE.Vector3(.6,.8,2.6),new THREE.Color());
const geometry=adapter.glass.geometry,p=geometry.attributes.position,n=geometry.attributes.normal,uv=geometry.attributes.uv;
const tex=adapter.material.thicknessMap,texW=tex.image.width,texH=tex.image.height,texData=tex.image.data;
const thicknessFactor=adapter.material.thickness;
const xValues=[];for(let i=0;;i++){if(i>0&&uv.getY(i)!==0)break;xValues.push(uv.getX(i));}
const nx=xValues.length;const yValues=[];for(let i=0;i<p.count;i+=nx){if(n.getZ(i)<.5)break;yValues.push(uv.getY(i));if(uv.getY(i)===1)break;}
function clamp(v,a,b){return Math.max(a,Math.min(b,v));}
function tAt(u,v){const x=u*texW-.5,y=v*texH-.5,x0=Math.floor(x),y0=Math.floor(y),fx=x-x0,fy=y-y0;const at=(x,y)=>texData[(clamp(y,0,texH-1)*texW+clamp(x,0,texW-1))*4+1]/255;return thicknessFactor*((1-fy)*((1-fx)*at(x0,y0)+fx*at(x0+1,y0))+fy*((1-fx)*at(x0,y0+1)+fx*at(x0+1,y0+1)));}
function idx(a,v){let lo=0,hi=a.length-1;while(hi-lo>1){const m=(lo+hi)>>1;if(a[m]<=v)lo=m;else hi=m;}return lo;}
function interpAt(u,v){const ix=idx(xValues,u),iy=idx(yValues,v),fx=(u-xValues[ix])/(xValues[ix+1]-xValues[ix]),fy=(v-yValues[iy])/(yValues[iy+1]-yValues[iy]),a=iy*nx+ix,b=a+1,c=a+nx,d=c+1;const ids=fx>=fy?[a,b,d]:[a,d,c],ws=fx>=fy?[1-fx,fx-fy,fy]:[1-fy,fx,fy-fx];const pos=new THREE.Vector3(),norm=new THREE.Vector3();ids.forEach((id,i)=>{pos.addScaledVector(new THREE.Vector3().fromBufferAttribute(p,id),ws[i]);norm.addScaledVector(new THREE.Vector3().fromBufferAttribute(n,id),ws[i]);});pos.z+=.6;return{pos,norm:norm.normalize()};}
function refract(i,n,eta){const d=n.dot(i),k=1-eta*eta*(1-d*d);return k<0?new THREE.Vector3():i.clone().multiplyScalar(eta).addScaledVector(n,-(eta*d+Math.sqrt(k)));}
function screen(pt){const s=pt.clone().project(camera);return new THREE.Vector2(s.x*pane.width*.5,s.y*pane.height*.5);}
function resultAt(u,v){const {pos,norm}=interpAt(u,v),i=pos.clone().sub(camera.position).normalize(),r=refract(i,norm,1/1.5),t=tAt(u,v),e=pos.clone().addScaledVector(r,t),b=screen(e).distanceTo(screen(pos)),s=sampleGobangSurface(u,v,shape),normal=new THREE.Vector3(-s.dx,-s.dy,1).normalize(),front=new THREE.Vector3((u-.5)*shape.width,(v-.5)*shape.height,.6+s.height),inc=front.clone().sub(camera.position).normalize(),inside=refract(inc,normal,1/1.5),backZ=.6-.008-.085*shape.width/20,back=front.clone().addScaledVector(inside,(backZ-front.z)/inside.z),out=refract(inside,new THREE.Vector3(0,0,1),1.5);const aAt=z=>screen(back.clone().addScaledVector(out,(z-backZ)/out.z)).distanceTo(screen(front));
const aBasePoint=back.clone().addScaledVector(out,(.6-.1296-.25-backZ)/out.z),aNearPoint=back.clone().addScaledVector(out,(.6-.1296-backZ)/out.z);
const nearReprojected=camera.position.clone().addScaledVector(aNearPoint.clone().sub(camera.position),(.6-.1296-.25-camera.position.z)/(aNearPoint.z-camera.position.z));
const delta=nearReprojected.sub(aBasePoint);delta.z=0;delta.multiplyScalar(Math.min(1,(shape.width/20)*.35/Math.max(delta.length(),.00001))*.65);
const aNearRefined=screen(aBasePoint.clone().add(delta)).distanceTo(screen(front));
return{u,v,normal:norm.toArray(),thickness:t,axial:pos.z-backZ,mapError:t-(pos.z-backZ),normalBakeCorrection:1/norm.z,pathCorrection:1/-r.z,b,aNear:aAt(.6-.1296),aBase:aAt(.6-.1296-.25),aNearRefined};}
let results=[];for(let y=0;y<250;y++)for(let x=0;x<200;x++)results.push(resultAt((x+.5)/200,(y+.5)/250));
function stats(key){const vals=results.map(x=>x[key]).sort((a,b)=>a-b);return{min:vals[0],median:vals[(vals.length*.5)|0],p95:vals[(vals.length*.95)|0],max:vals.at(-1),mean:vals.reduce((a,b)=>a+b,0)/vals.length};}
const samples=[[.511,.509],[.5125,.5125],[.525,.525],[.505,.505],[.499,.5]].map(([u,v])=>resultAt(u,v));
const pixelProjectionWidth=screen(new THREE.Vector3(shape.width/2,0,.6)).x*2;
console.log(JSON.stringify({shape,pane,glassDisplayedCssWidth:pixelProjectionWidth,material:{thickness:thicknessFactor,thicknessMap:{width:texW,height:texH,flipY:tex.flipY,colorSpace:tex.colorSpace,wrapS:tex.wrapS,wrapT:tex.wrapT,greenMin:Math.min(...texData.filter((v,i)=>i%4===1)),greenMax:Math.max(...texData.filter((v,i)=>i%4===1))}},frontSegments:[nx-1,yValues.length-1],thickness:stats('thickness'),mapError:stats('mapError'),normalBakeCorrection:stats('normalBakeCorrection'),actualNearAInteriorCss:stats('aNearRefined'),pathCorrection:stats('pathCorrection'),stockOffsetCss:stats('b'),twoSurfaceNearCss:stats('aNear'),twoSurfaceBaseCss:stats('aBase'),samples},null,2));