{
  "reviewedAt": "2026-10-23",
  "sourceKind": "Production editor working copy; not claimed to be a public GitHub commit",
  "excerpts": [
    {
      "file": "app/studio/camera-raw-import.ts",
      "start": 10,
      "end": 14,
      "code": "export async function decodeCameraRaw(file:File,options:RawDecodeOptions,signal:AbortSignal,onProgress=(s:string)=>{}):Promise<DecodedCameraRaw>{\n if(options.profile)validateDcp(options.profile);if(options.profileTemperature!==undefined&&(!Number.isFinite(options.profileTemperature)||options.profileTemperature<2000||options.profileTemperature>50000))throw Error('Profile illumination must be between 2000 and 50000 K.');if(!isCameraRaw(file.name)||!file.size||file.size>100_000_000)throw Error('Choose a camera RAW file up to 100 MB.');if(signal.aborted)throw new DOMException('RAW import cancelled.','AbortError');const buffer=await file.arrayBuffer();if(signal.aborted)throw new DOMException('RAW import cancelled.','AbortError');\n const base=((import.meta as ImportMeta & {env?:{BASE_URL?:string}}).env?.BASE_URL||'/photoshot/editor/')+'raw-decoder/';\n return new Promise((resolve,reject)=>{const worker=new Worker(base+'photoshot-worker.js',{type:'module'});let finished=false;const finish=(error?:Error,result?:DecodedCameraRaw)=>{if(finished)return;finished=true;worker.terminate();clearTimeout(timeout);signal.removeEventListener('abort',abort);if(error)reject(error);else resolve(result!);},abort=()=>finish(new DOMException('RAW import cancelled.','AbortError')),timeout=setTimeout(()=>finish(Error('RAW decode timed out. Try Half Size or a different file.')),120000);signal.addEventListener('abort',abort,{once:true});worker.onmessage=async({data})=>{if(data.progress)onProgress(data.progress);else if(data.error)finish(Error(data.error));else if(data.result){try{const raw=data.result as DecodedCameraRaw;if(options.profile){if(!cameraFits(options.profile.camera,raw.metadata.make,raw.metadata.model))throw Error('This DCP belongs to '+options.profile.camera+', not '+raw.metadata.make+' '+raw.metadata.model+'.');onProgress('Applying camera profile…');raw.bytes=await applyDcp(raw.bytes,options.profile,options.profileTemperature??6504,signal);raw.metadata.cameraProfile=options.profile.name;}if(signal.aborted)throw new DOMException('RAW import cancelled.','AbortError');finish(undefined,raw);}catch(e){finish(e as Error);}}};worker.onerror=()=>finish(Error('RAW decoder stopped. Try a smaller file or Half Size.'));worker.onmessageerror=()=>finish(Error('Invalid RAW decoder response.'));worker.postMessage({buffer,options:{half:options.half,cameraWB:options.cameraWB,autoBright:options.autoBright,cameraProfile:!!options.profile}},[buffer]);if(signal.aborted)abort();});\n}",
      "sha256": "b7ca37aada2cb4b0febdfae8d8ad3a11bad946796d75fe3bb867eb56fe5c650b"
    },
    {
      "file": "app/studio/camera-raw-import.ts",
      "start": 17,
      "end": 19,
      "code": "export function cameraRawRGBA16(raw:DecodedCameraRaw){const out=new Uint16Array(raw.width*raw.height*4),view=new DataView(raw.bytes.buffer,raw.bytes.byteOffset,raw.bytes.byteLength);for(let i=0;i<raw.width*raw.height;i++){for(let k=0;k<3;k++)out[i*4+k]=view.getUint16((i*3+k)*2,true);out[i*4+3]=65535;}return out;}\n\nexport function cameraRawPrecisionDocument(raw:DecodedCameraRaw,name:string):StudioDoc{if(raw.width*raw.height>16_000_000)throw Error('Native 16-bit documents support up to 16 MP. Develop at Half Size.');const doc=cameraRawDocument(raw,name),data=new Float32Array(raw.width*raw.height*4),view=new DataView(raw.bytes.buffer,raw.bytes.byteOffset,raw.bytes.byteLength);for(let i=0;i<raw.width*raw.height;i++){for(let c=0;c<3;c++)data[i*4+c]=view.getUint16((i*3+c)*2,true)/65535;data[i*4+3]=1;}const layer=doc.layers[0];return {...doc,precision:{space:'RGB',depth:16},sourceFile:{...doc.sourceFile!,warnings:['LibRaw developed RGB 16-bit sensor values are retained for native precision editing. Some raster filters require explicit RGB 8-bit conversion.']},layers:[{...layer,precision:packPrecision({width:raw.width,height:raw.height,space:'RGB',depth:16,data}),precisionSource:precisionSource(layer.src)}]};}",
      "sha256": "b7ca37aada2cb4b0febdfae8d8ad3a11bad946796d75fe3bb867eb56fe5c650b"
    },
    {
      "file": "app/studio/raw-source.ts",
      "start": 3,
      "end": 8,
      "code": "export type EmbeddedRaw={version:1;name:string;bytes:number;base64:string;sha256:string;options:RawDecodeOptions};\nexport function validateEmbeddedRaw(value:unknown):asserts value is EmbeddedRaw{const r=value as EmbeddedRaw;if(!r||r.version!==1||typeof r.name!=='string'||r.name.length>255||!Number.isInteger(r.bytes)||r.bytes<1||r.bytes>32_000_000||typeof r.base64!=='string'||r.base64.length!==Math.ceil(r.bytes/3)*4||!/^[A-Za-z0-9+/]*={0,2}$/.test(r.base64)||!/^[0-9a-f]{64}$/.test(r.sha256)||!r.options||['half','cameraWB','autoBright'].some(k=>typeof r.options[k as keyof RawDecodeOptions]!=='boolean'))throw Error('Invalid embedded RAW source.');if(r.options.profile)validateDcp(r.options.profile);if(r.options.profileTemperature!==undefined&&(!Number.isFinite(r.options.profileTemperature)||r.options.profileTemperature<2000||r.options.profileTemperature>50000))throw Error('Invalid RAW profile illumination.');}\nconst digest=async(bytes:Uint8Array)=>Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',bytes as BufferSource)),v=>v.toString(16).padStart(2,'0')).join('');\nexport async function embedRawSource(file:File,options:RawDecodeOptions):Promise<EmbeddedRaw>{if(file.size<1||file.size>32_000_000)throw Error('Embedded RAW supports original files up to 32 MB. Uncheck Keep RAW Original for larger files.');const bytes=new Uint8Array(await file.arrayBuffer());let binary='';for(let i=0;i<bytes.length;i+=8192)binary+=String.fromCharCode(...bytes.subarray(i,i+8192));const result:EmbeddedRaw={version:1,name:file.name,bytes:bytes.length,base64:btoa(binary),sha256:await digest(bytes),options:{...options}};validateEmbeddedRaw(result);return result;}\nexport async function embeddedRawFile(source:EmbeddedRaw){validateEmbeddedRaw(source);const binary=atob(source.base64),bytes=Uint8Array.from(binary,c=>c.charCodeAt(0));if(bytes.length!==source.bytes||await digest(bytes)!==source.sha256)throw Error('Embedded RAW checksum mismatch. The original is damaged.');return new File([bytes],source.name,{type:'application/octet-stream'});}",
      "sha256": "09aa49cfedea13693d1841cc71cece738d2a8e78f22522ecae1c7a884c1a33a2"
    },
    {
      "file": "app/studio/raw-dcp.ts",
      "start": 1,
      "end": 7,
      "code": "// DCP tag interpretation adapted from RAWmakase (MIT); vendor/rawmakase.LICENSE.\nimport {defaultDngTone} from './raw-profile-tone.ts';\nimport {type RGB,type HsvTable,validateHsv,validateCurve,mul,XYZ_PRO,PRO_RGB,applyHsv,tone,encode,clamp} from './raw-profile-math.ts';\nexport type DcpProfile={version:1;name:string;camera:string;copyright:string;forward1:number[];forward2:number[];color1?:number[];color2?:number[];kelvin1:number;kelvin2:number;hue1?:HsvTable;hue2?:HsvTable;look?:HsvTable;curve:number[];exposure:number;blackRender:number};\nexport function validateDcp(p:DcpProfile){if(!p||p.version!==1||[p.name,p.camera,p.copyright].some(v=>typeof v!=='string'||v.length>4096)||!p.name||!p.camera||[p.kelvin1,p.kelvin2].some(v=>!Number.isFinite(v)||v<1500||v>50000)||!Number.isFinite(p.exposure)||Math.abs(p.exposure)>10||![0,1].includes(p.blackRender))throw Error('Invalid DCP profile.');for(const m of [p.forward1,p.forward2,p.color1,p.color2])if(m!==undefined&&(!Array.isArray(m)||m.length!==9||m.some(x=>!Number.isFinite(x)||Math.abs(x)>100)))throw Error('Invalid DCP matrix.');if(!p.forward1||!p.forward2)throw Error('DCP forward matrices required.');validateCurve(p.curve);for(const t of [p.hue1,p.hue2,p.look])if(t)validateHsv(t);if(p.hue2&&(!p.hue1||p.hue1.srgb!==p.hue2.srgb||String(p.hue1.dims)!==String(p.hue2.dims)))throw Error('DCP illuminant tables do not match.');return p;}\nexport function readDcp(b:Uint8Array):DcpProfile{if(b.length<8||b.length>16_000_000)throw Error('DCP files must be below 16 MB.');const v=new DataView(b.buffer,b.byteOffset,b.byteLength),le=b[0]===73&&b[1]===73;if(!le&&!(b[0]===77&&b[1]===77))throw Error('Invalid DCP byte order.');const bounds=(p:number,n:number)=>{if(!Number.isSafeInteger(p)||p<0||p+n>b.length)throw Error('DCP data is outside the file.');return p;},u16=(p:number)=>v.getUint16(bounds(p,2),le),u32=(p:number)=>v.getUint32(bounds(p,4),le);if(![42,0x4352].includes(u16(2)))throw Error('Invalid DCP header.');const off=u32(4),count=u16(off),tags=new Map<number,{kind:number;count:number;offset:number}>();if(count>512)throw Error('Too many DCP tags.');bounds(off+2,count*12+4);for(let i=0;i<count;i++){const p=off+2+i*12,id=u16(p),kind=u16(p+2),n=u32(p+4),size=({1:1,2:1,7:1,3:2,4:4,9:4,11:4,5:8,10:8,12:8} as Record<number,number>)[kind];if(!size||tags.has(id))throw Error('Invalid or duplicate DCP tag.');const offset=n*size<=4?p+8:u32(p+8);bounds(offset,n*size);tags.set(id,{kind,count:n,offset});}const nums=(id:number)=>{const t=tags.get(id);if(!t)throw Error('Missing DCP tag '+id);if(t.count>3_000_000)throw Error('DCP table is too large.');const out:number[]=[];for(let i=0;i<t.count;i++){const p=t.offset,n=t.kind===3?u16(p+i*2):t.kind===4?u32(p+i*4):t.kind===11?v.getFloat32(p+i*4,le):t.kind===10?v.getInt32(p+i*8,le)/v.getInt32(p+i*8+4,le):t.kind===5?u32(p+i*8)/u32(p+i*8+4):NaN;if(!Number.isFinite(n))throw Error('Invalid DCP numeric tag '+id);out.push(n);}return out;},scalar=(id:number,fallback:number)=>{if(!tags.has(id))return fallback;const a=nums(id);if(a.length!==1)throw Error('Invalid DCP scalar.');return a[0];},text=(id:number)=>{const t=tags.get(id);return t?new TextDecoder().decode(b.subarray(t.offset,t.offset+t.count)).replace(/\\0+$/,''):'';},kelvin=(id:number,fallback=6504)=>{if(!tags.has(id))return fallback;const n=({17:2856,21:6504,23:5003,20:5503,22:7504,1:5500,4:5500,9:5500,24:3200} as Record<number,number>)[scalar(id,21)];if(!n)throw Error('Unsupported DCP calibration light.');return n;},table=(dims:number,data:number,enc:number):HsvTable|undefined=>{if(!tags.has(data))return;const d=nums(dims),e=scalar(enc,0);if(d.length!==3||![0,1].includes(e))throw Error('Invalid DCP table encoding.');return {dims:d as RGB,data:nums(data),srgb:e===1};};if([52531,52537,52538,52551].some(k=>tags.has(k)))throw Error('HDR and triple-illuminant DCP profiles are not supported.');if(scalar(50941,0)===2)throw Error('This DCP forbids embedding; choose an embeddable profile.');if(!tags.has(50964))throw Error('This DCP has no forward matrix.');const f=nums(50964),k=kelvin(50778),p:DcpProfile={version:1,name:text(50936),camera:text(50708),copyright:text(50942),forward1:f,forward2:tags.has(50965)?nums(50965):f.slice(),color1:tags.has(50721)?nums(50721):undefined,color2:tags.has(50722)?nums(50722):undefined,kelvin1:k,kelvin2:kelvin(50779,k),hue1:table(50937,50938,51107),hue2:table(50937,50939,51107),look:table(50981,50982,51108),curve:tags.has(50940)?nums(50940):defaultDngTone.flatMap((y,i)=>[i/(defaultDngTone.length-1),y]),exposure:scalar(51109,0),blackRender:scalar(51110,0)===1?1:0};return validateDcp(p);}\nexport function cameraFits(camera:string,make:string,model:string){const norm=(s:string)=>s.toLowerCase().replace(/[\\s_\\-]/g,'');",
      "sha256": "e4817a39d0709ac0f824397e331e371d6b03fb8388322513a12ff58af7b4d17e"
    }
  ],
  "productionUI": {
    "episode": 36,
    "at": "2026-10-24T00:46:29.882Z",
    "url": "https://slohero.com/photoshot/editor/",
    "inputKind": "AI-generated fictional adult demonstration PNG, not a real-person photograph",
    "portraitInputIsNotRaw": true,
    "portraitRawFilterExposure": 0.35,
    "portraitSourcePreserved": true,
    "portraitProjectReopenExact": true,
    "sensorFixture": "synthetic Bayer DNG, not the portrait",
    "sensor16BitDocument": true,
    "embeddedSensorBytesExact": true,
    "halfSize": [
      48,
      32
    ],
    "sensorHashPreserved": true,
    "passed": true,
    "errors": [],
    "imageRevision": "v3: restrained intermediate facial redesign from the original textual face descriptions; text-only generation."
  },
  "checks": [
    {
      "file": "raw-source-results.json",
      "sha256": "7e25758a428cf4ac572c7fb112507d88c382f904443e90d09f4ed216b0a4ff95",
      "results": {
        "passed": true,
        "exactSensorBytes": true,
        "projectJsonRoundTrip": true,
        "decodeOptions": true,
        "checksumRejectsDamage": true,
        "sizeAndTypeValidation": true
      },
      "freshRun": true
    },
    {
      "file": "raw-source-native-ui-results.json",
      "sha256": "020d9c7d0dd08620e90556ea984ffb60e2d8ccd4b5d9d8f7dff484a3d52bb817",
      "results": {
        "passed": true,
        "native16BitDocument": true,
        "originalSensorBytesEmbedded": true,
        "projectReopen": true,
        "optionsRestored": true,
        "redevelopedHalfSize": true,
        "originalDocumentPreserved": true,
        "errors": []
      },
      "freshRun": true
    },
    {
      "file": "raw-dcp-integration-results.json",
      "sha256": "e219e74827a52ba66d5f786d8423c3305d98c0f590091acaeba338b154dcdc26",
      "results": {
        "passed": true,
        "realSonyARW": true,
        "matchingInstalledDcp": true,
        "dimensions": [
          3120,
          2084
        ],
        "sourceUnchanged": true,
        "changedSamples": 64794,
        "clippedSamples": 106,
        "elapsedSeconds": 16.989599999994038
      },
      "freshRun": true
    }
  ],
  "imageDisclosure": "AI 생성 시연 이미지 · 실제 인물 촬영 및 공식 협업 이미지가 아닙니다. 편집 화면과 결과는 실제 포토샷에서 만들었습니다.",
  "imageRevision": {
    "version": 3,
    "at": "2026-10-24T00:46:29.882Z",
    "method": "Text-only intermediate variation with two local adjustments; no reference photographs provided to generator.",
    "sourceSha256": "a6b96c1be3789c2bcbd5f5caa565ec14780a8ca3d6ac07612999dbae03acab80",
    "previousImagesRetained": true
  }
}