import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import ts from 'typescript'; const projectRoot = path.resolve( path.dirname(fileURLToPath(import.meta.url)), '..' ); const helperPath = path.join( projectRoot, 'src/game/ui/dialoguePortrait.ts' ); const helperSource = await readFile(helperPath, 'utf8'); const transpiledHelper = ts.transpileModule(helperSource, { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: helperPath }).outputText; const helperModule = await import( `data:text/javascript;base64,${Buffer.from(transpiledHelper).toString('base64')}` ); const { calculateDialogueFacePortraitLayout, fitDialogueFacePortrait } = helperModule; const squareLayout = calculateDialogueFacePortraitLayout( 1254, 1254, 220 ); assert.deepEqual( { cropX: squareLayout.cropX, cropY: squareLayout.cropY, cropSize: squareLayout.cropSize }, { cropX: 251, cropY: 44, cropSize: 752 }, 'The standard portrait must use the established square face-and-shoulders crop.' ); assertCenteredCrop(squareLayout, 1254, 1254, 220); const landscapeLayout = calculateDialogueFacePortraitLayout( 1600, 1000, 192 ); assert.deepEqual( { cropX: landscapeLayout.cropX, cropY: landscapeLayout.cropY, cropSize: landscapeLayout.cropSize }, { cropX: 500, cropY: 35, cropSize: 600 }, 'Landscape sources must still produce a square, horizontally centered face crop.' ); assertCenteredCrop(landscapeLayout, 1600, 1000, 192); const portraitLayout = calculateDialogueFacePortraitLayout( 1000, 1600, 218 ); assert.deepEqual( { cropX: portraitLayout.cropX, cropY: portraitLayout.cropY, cropSize: portraitLayout.cropSize }, { cropX: 200, cropY: 56, cropSize: 600 }, 'Portrait-oriented sources must preserve the upper face-and-shoulders framing.' ); assertCenteredCrop(portraitLayout, 1000, 1600, 218); assert.throws( () => calculateDialogueFacePortraitLayout(0, 1254, 220), RangeError, 'Invalid source dimensions must fail explicitly.' ); assert.throws( () => calculateDialogueFacePortraitLayout(1254, 1254, Number.NaN), RangeError, 'Invalid display dimensions must fail explicitly.' ); const imageCalls = []; const image = { frame: { realWidth: 1254, realHeight: 1254 }, setCrop(...values) { imageCalls.push(['crop', ...values]); return this; }, setOrigin(...values) { imageCalls.push(['origin', ...values]); return this; }, setScale(...values) { imageCalls.push(['scale', ...values]); return this; } }; const appliedLayout = fitDialogueFacePortrait(image, 220); assert.deepEqual( imageCalls.map(([operation]) => operation), ['crop', 'origin', 'scale'], 'Applying the layout must crop, center the crop origin, and then scale it.' ); assert.deepEqual( imageCalls[0], [ 'crop', appliedLayout.cropX, appliedLayout.cropY, appliedLayout.cropSize, appliedLayout.cropSize ] ); assert.deepEqual( imageCalls[1], ['origin', appliedLayout.originX, appliedLayout.originY] ); assert.deepEqual( imageCalls[2], ['scale', appliedLayout.scale] ); await assertSceneIntegration( 'src/game/scenes/StoryScene.ts', 3 ); await assertSceneIntegration( 'src/game/scenes/CityStayScene.ts', 1 ); await assertSceneIntegration( 'src/game/scenes/PrologueVillageScene.ts', 1 ); await assertSceneIntegration( 'src/game/scenes/PrologueMilitiaCampScene.ts', 1 ); await assertSceneIntegration( 'src/game/scenes/CampVisitExplorationScene.ts', 1 ); console.log( 'Verified shared dialogue portrait face crops, centered origins, retained display sizes, and all story/exploration dialogue integrations.' ); function assertCenteredCrop( layout, sourceWidth, sourceHeight, displaySize ) { const centeredX = layout.cropX + layout.cropSize / 2 - layout.originX * sourceWidth; const centeredY = layout.cropY + layout.cropSize / 2 - layout.originY * sourceHeight; assert.ok( Math.abs(centeredX) < 1e-9 && Math.abs(centeredY) < 1e-9, 'The visible crop center must remain anchored to the image position.' ); assert.ok( Math.abs(layout.cropSize * layout.scale - displaySize) < 1e-9, 'The cropped portrait must retain its requested display size.' ); } async function assertSceneIntegration(relativePath, minimumCalls) { const source = await readFile( path.join(projectRoot, relativePath), 'utf8' ); assert.match( source, /import\s+\{\s*fitDialogueFacePortrait\s*\}\s+from\s+'\.\.\/ui\/dialoguePortrait';/, `${relativePath} must import the shared dialogue portrait helper.` ); const callCount = source.match(/fitDialogueFacePortrait\s*\(/g)?.length ?? 0; assert.ok( callCount >= minimumCalls, `${relativePath} must apply the shared helper at every portrait rendering path.` ); }