Good base to start generation
@@ -231,7 +231,6 @@ mask follows the shifted artwork. Card thickness and geometry are unchanged.
|
||||
- Saturation threshold: `smoothstep(0.18, 0.62)`
|
||||
- Value threshold: `smoothstep(0.015, 0.16)`
|
||||
- Retains soft title-panel protection.
|
||||
- Does not use circular or elliptical exclusions over the face, hand, or lamb.
|
||||
|
||||
### Timothy
|
||||
|
||||
@@ -242,11 +241,12 @@ All finish masks should:
|
||||
|
||||
- Protect critical text unless a treatment is intentionally designed for it.
|
||||
- Follow compositional regions such as stained-glass panes, ornament, borders, garments, and environmental color.
|
||||
- Avoid artificial holes, isolated skin spots, and recognizable geometric exclusions.
|
||||
- Be reviewed while the card rotates under multiple lighting angles.
|
||||
|
||||
## Wear and Imperfections
|
||||
|
||||
Wear/condition and imperfections are deferred product features. These settings document existing experimental harness behavior, not M1 requirements.
|
||||
|
||||
Condition uses `1.0` for mint and `0.0` for the most worn supported appearance. The approved v4 material baseline remains unchanged at the mint default.
|
||||
|
||||
- Edge wear uses seeded irregular width and substrate-appropriate exposed colors.
|
||||
|
||||
@@ -9,7 +9,7 @@ The approved visual baseline is recorded in [`CURRENT-DEFAULTS.md`](CURRENT-DEFA
|
||||
- David printed-ink, foil, and holographic presets
|
||||
- Independently selectable paper, linen, plastic, metal, and wood substrates
|
||||
- Timothy as the second-artwork fixture
|
||||
- David color-based coverage and title-panel protection without face/hand/lamb cutouts; automatic saturation/value coverage for Timothy
|
||||
- David color-based coverage and title-panel protection; automatic saturation/value coverage for Timothy
|
||||
- Procedural rounded-card geometry generated from shared physical dimensions
|
||||
- Deterministic linen normal map
|
||||
- Card manipulation, camera inspection, zoom, frame-rate-independent flip/reset, interruption-safe sweep, and movable lighting
|
||||
@@ -18,7 +18,7 @@ The approved visual baseline is recorded in [`CURRENT-DEFAULTS.md`](CURRENT-DEFA
|
||||
- Live light color, position, intensity, environment contribution, and exposure controls
|
||||
- Lab-mode loading of local artwork and finish-mask images without rebuilding
|
||||
- Named browser experiment saves, versioned JSON import/export, and PNG capture
|
||||
- Deterministic condition wear with substrate-aware edges, scratches, scuffs, and imperfection seeds
|
||||
- Experimental condition wear with substrate-aware edges, scratches, scuffs, and imperfection seeds; wear and imperfections are deferred product features
|
||||
- Bare Inspect mode and small Lab-mode parameter panel
|
||||
- Pack mode: a touch-responsive foil pouch with a draggable tear strip, reveal-safe ordered stack, and one-at-a-time inspection
|
||||
- Drawing-buffer resolution plus median, p95, and worst frame-time display
|
||||
@@ -136,7 +136,7 @@ asset manifest therefore retains its v4 reference revision; the scoped material
|
||||
approvals and parameters are tracked in `CURRENT-DEFAULTS.md`.
|
||||
|
||||
The runtime shader and geometry are runtime implementations, not a claim of pixel-identical Blender output.
|
||||
Live review removed only David's face, hand, and lamb ellipse cutouts. Color-based coverage and title-panel protection remain. Holographic color and angle response are preserved; reduced sheen applies only to foil.
|
||||
David uses color-based coverage and title-panel protection. Holographic color and angle response are preserved; reduced sheen applies only to foil.
|
||||
|
||||
## Run
|
||||
|
||||
|
||||
138
spikes/timothy-layer-proof/build.mjs
Normal file
@@ -0,0 +1,138 @@
|
||||
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
const root = dirname(fileURLToPath(import.meta.url));
|
||||
const layout = JSON.parse(readFileSync(resolve(root, 'layout.json')));
|
||||
const card = JSON.parse(readFileSync(resolve(root, 'card.json')));
|
||||
const { width: W, height: H, radius, frameInset: inset, fields } = layout;
|
||||
const out = resolve(root, 'output');
|
||||
for (const dir of ['layers', 'cards', 'masks', 'review']) mkdirSync(resolve(out, dir), { recursive: true });
|
||||
const run = (bin, args, input) => execFileSync(bin, args, { input, maxBuffer: 128 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] });
|
||||
const magick = process.env.MAGICK_BIN || 'magick';
|
||||
const inkscape = process.env.INKSCAPE_BIN || 'inkscape';
|
||||
const path = name => resolve(out, name);
|
||||
const hash = data => createHash('sha256').update(data).digest('hex');
|
||||
const escape = value => String(value).replaceAll('&', '&').replaceAll('<', '<').replaceAll('"', '"');
|
||||
const rect = ([x, y, w, h], attrs = String()) => `<rect x="${x}" y="${y}" width="${w}" height="${h}" ${attrs}/>`;
|
||||
const svg = (body, w = W, h = H) => `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">${body}</svg>`;
|
||||
const round = (x, y, w, h, r) => `M${x+r},${y}H${x+w-r}A${r},${r} 0 0 1 ${x+w},${y+r}V${y+h-r}A${r},${r} 0 0 1 ${x+w-r},${y+h}H${x+r}A${r},${r} 0 0 1 ${x},${y+h-r}V${y+r}A${r},${r} 0 0 1 ${x+r},${y}Z`;
|
||||
const runtimeFrame = `<path fill="${layout.inkColor}" fill-rule="evenodd" d="M0,0H${W}V${H}H0Z ${round(inset,inset,W-2*inset,H-2*inset,radius-inset)}"/>`;
|
||||
const frame = `<path fill="${layout.inkColor}" fill-rule="evenodd" d="${round(0,0,W,H,radius)} ${round(inset,inset,W-2*inset,H-2*inset,radius-inset)}"/>`;
|
||||
const backing = layout.panels.map(box => rect(box, `fill="${layout.panelColor}"`)).join('') +
|
||||
`<path d="M48 357H1952 M48 2183H1952" stroke="${layout.inkColor}" stroke-width="6"/>`;
|
||||
const textValues = { title: card.name.toUpperCase(), subtitle: card.subtitle, category: card.category.toUpperCase(), reference: `${card.reference} · ${card.translation}` };
|
||||
assert.equal(card.verseLines.join(' '), card.verse, 'Verse line breaks must preserve exact source text');
|
||||
const textElement = (id, f, value) => `<text id="${id}" x="${f.x}" y="${f.y}" font-family="${f.font}" font-size="${f.size}" text-anchor="${f.anchor}" fill="${layout.inkColor}">${escape(value)}</text>`;
|
||||
const textMarkup = Object.entries(fields).map(([id, f]) => id === 'verse'
|
||||
? card.verseLines.map((line, i) => textElement(`verse-${i}`, { ...f, y: f.y + i*f.lineHeight }, `${i === 0 ? '“' : ''}${line}${i === card.verseLines.length-1 ? '”' : ''}`)).join('')
|
||||
: textElement(id, f, textValues[id])).join('');
|
||||
const exportSvg = (name, body, w = W, h = H) => {
|
||||
writeFileSync(path(`${name}.svg`), svg(body, w, h));
|
||||
run(inkscape, [path(`${name}.svg`), '--export-area-page', '--export-png-color-mode=RGBA_8', `--export-filename=${path(`${name}.png`)}`]);
|
||||
};
|
||||
const rgba = file => run(magick, [file, '-alpha', 'on', '-depth', '8', 'rgba:-']);
|
||||
const rawPng = (bytes, file, channels = 'rgba') => run(magick, ['-size', `${W}x${H}`, '-depth', '8', `${channels}:-`, file], bytes);
|
||||
const source = resolve(root, 'source/timothy-art.png');
|
||||
const dimensions = run(magick, ['identify', '-format', '%w %h', source]).toString().split(' ').map(Number);
|
||||
assert.equal(dimensions[0]/dimensions[1], W/H, 'Source requires an explicitly reviewed crop');
|
||||
run(magick, [source, '-resize', `${W}x${H}`, '-colorspace', 'sRGB', path('layers/art.png')]);
|
||||
const fonts = {};
|
||||
for (const family of new Set(Object.values(fields).map(f => f.font))) {
|
||||
const [matched, file] = run('fc-match', ['-f', '%{family}\n%{file}', family]).toString().split('\n');
|
||||
assert.equal(matched, family, `Missing font: ${family}`);
|
||||
fonts[family] = { file, sha256: hash(readFileSync(file)) };
|
||||
}
|
||||
exportSvg('layers/frame', frame);
|
||||
exportSvg('layers/frame-runtime', runtimeFrame);
|
||||
exportSvg('layers/backing', backing);
|
||||
exportSvg('layers/text', textMarkup);
|
||||
exportSvg('layers/normal-overlay', backing + frame);
|
||||
exportSvg('layers/normal-runtime-overlay', backing + runtimeFrame);
|
||||
exportSvg('layers/card-silhouette', `<rect width="${W}" height="${H}" rx="${radius}" fill="white"/>`);
|
||||
const queries = run(inkscape, [path('layers/text.svg'), '--query-all']).toString();
|
||||
writeFileSync(path('review/text-bounds.csv'), queries);
|
||||
const glyphBounds = {};
|
||||
for (const row of queries.trim().split('\n')) { const [id, ...numbers] = row.split(','); glyphBounds[id] = numbers.map(Number); }
|
||||
function fits(box, bounds) {
|
||||
return bounds?.length === 4 && bounds[0] >= box[0] && bounds[1] >= box[1] && bounds[0]+bounds[2] <= box[0]+box[2] && bounds[1]+bounds[3] <= box[1]+box[3];
|
||||
}
|
||||
for (const [id, f] of Object.entries(fields)) {
|
||||
const ids = id === 'verse' ? card.verseLines.map((_, i) => `verse-${i}`) : [id];
|
||||
for (const textId of ids) assert.ok(fits(f.box, glyphBounds[textId]), `${textId} overflows: ${glyphBounds[textId]}`);
|
||||
}
|
||||
// Measure an actual overlong text object to prove the check rejects overflow.
|
||||
writeFileSync(path('review/overflow-fixture.svg'), svg(textElement('too-long', fields.title, 'TIMOTHY '.repeat(12))));
|
||||
const overflow = run(inkscape, [path('review/overflow-fixture.svg'), '--query-id=too-long', '--query-all']).toString().trim().split('\n').find(row => row.startsWith('too-long,'));
|
||||
assert.ok(!fits(fields.title.box, overflow.split(',').slice(1).map(Number)), 'Overflow fixture was not rejected');
|
||||
const art = rgba(path('layers/art.png'));
|
||||
const normalOverlay = rgba(path('layers/normal-runtime-overlay.png'));
|
||||
const backingPixels = rgba(path('layers/backing.png'));
|
||||
const framePixels = rgba(path('layers/frame.png'));
|
||||
const runtimeFramePixels = rgba(path('layers/frame-runtime.png'));
|
||||
const alphaAt = (buf, x, y) => buf[(y*W+x)*4+3];
|
||||
let inspectedJoinPixels = 0;
|
||||
// Interior joins, deliberately away from the rounded exterior and antialias bands.
|
||||
for (const [x0,y0,w,h] of [[50,110,20,230],[1930,110,20,230],[50,2200,20,470],[1930,2200,20,470],[110,50,1780,20],[110,2730,1780,20]]) {
|
||||
for (let y=y0;y<y0+h;y++) for (let x=x0;x<x0+w;x++) {
|
||||
assert.equal(alphaAt(normalOverlay,x,y),255, `Gap at ${x},${y}`); inspectedJoinPixels++;
|
||||
}
|
||||
}
|
||||
assert.equal(alphaAt(normalOverlay,1000,1200),0, 'Art window must be transparent');
|
||||
assert.equal(alphaAt(framePixels,1000,2400),0, 'Textless must expose the lower art');
|
||||
const seamFixture = Buffer.from(normalOverlay);
|
||||
seamFixture[(150*W+59)*4+3] = 0;
|
||||
assert.notEqual(alphaAt(seamFixture,59,150),255, 'One-pixel seam fixture was not detected');
|
||||
const smooth = (a,b,v) => { const t=Math.max(0,Math.min(1,(v-a)/(b-a))); return t*t*(3-2*t); };
|
||||
const variants = { normal: ['normal-runtime-overlay','text'], textless: ['frame-runtime'], borderless: ['backing','text'], boundless: [] };
|
||||
const decodedHashes = {};
|
||||
for (const [name, layers] of Object.entries(variants)) {
|
||||
const args = [path('layers/art.png')];
|
||||
for (const layer of layers) args.push(path(`layers/${layer}.png`), '-compose', 'Over', '-composite');
|
||||
// Runtime textures stay opaque through their square corners; harness geometry owns the rounded silhouette.
|
||||
args.push('-alpha', 'off', path(`cards/timothy-${name}.png`));
|
||||
run(magick,args);
|
||||
const pixels = rgba(path(`cards/timothy-${name}.png`));
|
||||
for (let i = 3; i < pixels.length; i += 4) assert.equal(pixels[i], 255, `${name} runtime texture has a transparent pixel at index ${Math.floor(i / 4)}`);
|
||||
decodedHashes[name] = hash(pixels);
|
||||
const mask = Buffer.alloc(W*H);
|
||||
for(let i=0;i<W*H;i++) {
|
||||
const p=i*4, r=art[p]/255,g=art[p+1]/255,b=art[p+2]/255;
|
||||
const hi=Math.max(r,g,b),lo=Math.min(r,g,b),sat=hi===0?0:(hi-lo)/hi;
|
||||
let coverage=smooth(.08,.58,sat)*smooth(.015,.16,hi);
|
||||
// This proof treats the frame and text panels as printed ink; finish is on visible art.
|
||||
if (name==='normal'||name==='textless') coverage*=1-runtimeFramePixels[p+3]/255;
|
||||
if (name==='normal'||name==='borderless') coverage*=1-backingPixels[p+3]/255;
|
||||
mask[i]=Math.round(255*coverage);
|
||||
}
|
||||
rawPng(mask,path(`masks/timothy-${name}-finish.png`),'gray');
|
||||
for(const width of [1000,500]) run(magick,[path(`cards/timothy-${name}.png`),'-resize',String(width),path(`cards/timothy-${name}-${width}.png`)]);
|
||||
for(const [id,f] of Object.entries(fields)) if(layers.includes('text')) {
|
||||
const [x,y,w,h]=f.box;
|
||||
for(let yy=y;yy<y+h;yy++) for(let xx=x;xx<x+w;xx++) assert.equal(mask[yy*W+xx],0,`Finish under ${id}`);
|
||||
}
|
||||
}
|
||||
// The textless variants retain the original art's finish coverage in former panel regions.
|
||||
const boundlessMask=run(magick,[path('masks/timothy-boundless-finish.png'),'-depth','8','gray:-']);
|
||||
assert.ok(boundlessMask.subarray(2250*W,2530*W).some(v=>v>0),'Stale lower text protection in Boundless');
|
||||
// Repeat an independent composition and compare decoded pixels, ignoring PNG metadata.
|
||||
const repeated=run(magick,[path('layers/art.png'),path('layers/normal-runtime-overlay.png'),'-compose','Over','-composite',path('layers/text.png'),'-compose','Over','-composite','-alpha','off','-depth','8','rgba:-']);
|
||||
assert.equal(hash(repeated),decodedHashes.normal,'Composition is not repeatable');
|
||||
const embeddedArt=`<image width="${W}" height="${H}" xlink:href="data:image/png;base64,${readFileSync(source).toString('base64')}"/>`;
|
||||
// Inkscape's actual namespace is required for editable layer groups.
|
||||
const group=(label,body)=>`<g inkscape:groupmode="layer" inkscape:label="${label}" id="${label}">${body}</g>`;
|
||||
writeFileSync(path('timothy-editable.svg'),svg(group('Art',embeddedArt)+group('Backing',backing)+group('Frame',frame)+group('Text',textMarkup)).replace('http://www.inkscape.org/namespaces/inkscape','http://www.inkscape.org/namespaces/inkscape'));
|
||||
const guides=Object.entries(fields).map(([id,f])=>rect(f.box,'fill="none" stroke="#db4652" stroke-width="4"')+`<text x="${f.box[0]}" y="${f.box[1]-12}" font-family="Noto Sans" font-size="30" fill="#db4652">${id}</text>`).join('')+rect(layout.focalRegion,'fill="none" stroke="#4289bd" stroke-width="5" stroke-dasharray="20 12"');
|
||||
exportSvg('review/layout-guide',embeddedArt+backing+frame+textMarkup+guides);
|
||||
let sheet=`<rect width="2240" height="920" fill="#f6f3eb"/><text x="60" y="62" font-family="Noto Serif" font-size="32" fill="#304747">Timothy / Common — four printings, one illustration</text>`;
|
||||
for(const [i,name] of Object.keys(variants).entries()) {
|
||||
const x=60+i*550;
|
||||
sheet+=`<text x="${x}" y="120" font-family="Noto Sans" font-size="22" fill="#304747">${name[0].toUpperCase()+name.slice(1)}</text><image x="${x}" y="150" width="500" height="700" xlink:href="data:image/png;base64,${readFileSync(path(`cards/timothy-${name}-500.png`)).toString('base64')}"/>`;
|
||||
}
|
||||
exportSvg('review/printings',sheet,2240,920);
|
||||
const report={status:'passed',template:layout.revision,source:{dimensions,sha256:hash(readFileSync(source)),transform:`Uniform scale ${W/dimensions[0]}; no crop. Master is upscaled from generated source.`},tools:{inkscape:run(inkscape,['--version']).toString().trim(),imagemagick:run(magick,['-version']).toString().split('\n')[0]},fonts,glyphBounds,inspectedJoinPixels,runtimeTextureAlpha:'Every card pixel is fully opaque; harness geometry owns the rounded silhouette',negativeChecks:['Rendered overlong title rejected','One-pixel alpha seam detected'],decodedHashes,finishRecipe:'Timothy saturation smoothstep(0.08,0.58) × value smoothstep(0.015,0.16), occluded by active printed-ink frame/backing',limitations:['Visual approval pending','No automated semantic or optical-centering judgment','No runtime lighting review performed by this script','Two-line alternate title layout not implemented in this proof']};
|
||||
writeFileSync(path('validation.json'),JSON.stringify(report,null,2)+'\n');
|
||||
console.log(`Built four printings and aligned finish masks. Validation passed (${inspectedJoinPixels} join pixels). See ${path('review/printings.png')}`);
|
||||
13
spikes/timothy-layer-proof/card.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "timothy-common-proof-v1",
|
||||
"name": "Timothy",
|
||||
"rarity": "Common",
|
||||
"subtitle": "Faithful disciple",
|
||||
"category": "Person",
|
||||
"verse": "I have fought a good fight, I have finished my course, I have kept the faith:",
|
||||
"verseLines": ["I have fought a good fight,", "I have finished my course,", "I have kept the faith:"],
|
||||
"reference": "2 Timothy 4:7",
|
||||
"translation": "KJV",
|
||||
"source": "https://www.biblegateway.com/passage/?search=2+Timothy+4%3A7&version=KJV",
|
||||
"context": "Paul's words to Timothy; not a quotation spoken by Timothy. The scene is an illustrative interpretation, not a claim to depict a documented encounter."
|
||||
}
|
||||
18
spikes/timothy-layer-proof/layout.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"revision": "common-proof-v1",
|
||||
"width": 2000,
|
||||
"height": 2800,
|
||||
"radius": 88.888889,
|
||||
"frameInset": 60,
|
||||
"panelColor": "#eee8d5",
|
||||
"inkColor": "#304747",
|
||||
"panels": [[48, 48, 1904, 312], [48, 2180, 1904, 572]],
|
||||
"focalRegion": [160, 430, 1680, 1680],
|
||||
"fields": {
|
||||
"title": {"box": [160, 105, 1680, 105], "x": 1000, "y": 197, "size": 110, "font": "Noto Serif", "anchor": "middle"},
|
||||
"subtitle": {"box": [160, 240, 1100, 64], "x": 160, "y": 288, "size": 44, "font": "Noto Sans", "anchor": "start"},
|
||||
"category": {"box": [1400, 240, 440, 64], "x": 1840, "y": 288, "size": 38, "font": "Noto Sans", "anchor": "end"},
|
||||
"verse": {"box": [160, 2250, 1680, 280], "x": 1000, "y": 2320, "size": 72, "lineHeight": 95, "font": "Noto Serif", "anchor": "middle"},
|
||||
"reference": {"box": [160, 2580, 1680, 80], "x": 1000, "y": 2635, "size": 44, "font": "Noto Sans", "anchor": "middle"}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 497 KiB |
BIN
spikes/timothy-layer-proof/output/cards/timothy-borderless.png
Normal file
|
After Width: | Height: | Size: 4.6 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 658 KiB |
BIN
spikes/timothy-layer-proof/output/cards/timothy-boundless.png
Normal file
|
After Width: | Height: | Size: 6.4 MiB |
BIN
spikes/timothy-layer-proof/output/cards/timothy-normal-1000.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
spikes/timothy-layer-proof/output/cards/timothy-normal-500.png
Normal file
|
After Width: | Height: | Size: 441 KiB |
BIN
spikes/timothy-layer-proof/output/cards/timothy-normal.png
Normal file
|
After Width: | Height: | Size: 4.0 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
BIN
spikes/timothy-layer-proof/output/cards/timothy-textless-500.png
Normal file
|
After Width: | Height: | Size: 603 KiB |
BIN
spikes/timothy-layer-proof/output/cards/timothy-textless.png
Normal file
|
After Width: | Height: | Size: 5.8 MiB |
BIN
spikes/timothy-layer-proof/output/layers/art.png
Normal file
|
After Width: | Height: | Size: 6.4 MiB |
BIN
spikes/timothy-layer-proof/output/layers/backing.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
1
spikes/timothy-layer-proof/output/layers/backing.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="2000" height="2800" viewBox="0 0 2000 2800"><rect x="48" y="48" width="1904" height="312" fill="#eee8d5"/><rect x="48" y="2180" width="1904" height="572" fill="#eee8d5"/><path d="M48 357H1952 M48 2183H1952" stroke="#304747" stroke-width="6"/></svg>
|
||||
|
After Width: | Height: | Size: 399 B |
BIN
spikes/timothy-layer-proof/output/layers/card-silhouette.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="2000" height="2800" viewBox="0 0 2000 2800"><rect width="2000" height="2800" rx="88.888889" fill="white"/></svg>
|
||||
|
After Width: | Height: | Size: 263 B |
BIN
spikes/timothy-layer-proof/output/layers/frame-runtime.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="2000" height="2800" viewBox="0 0 2000 2800"><path fill="#304747" fill-rule="evenodd" d="M0,0H2000V2800H0Z M88.888889,60H1911.111111A28.888889000000006,28.888889000000006 0 0 1 1940,88.888889V2711.111111A28.888889000000006,28.888889000000006 0 0 1 1911.111111,2740H88.888889A28.888889000000006,28.888889000000006 0 0 1 60,2711.111111V88.888889A28.888889000000006,28.888889000000006 0 0 1 88.888889,60Z"/></svg>
|
||||
|
After Width: | Height: | Size: 560 B |
BIN
spikes/timothy-layer-proof/output/layers/frame.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
1
spikes/timothy-layer-proof/output/layers/frame.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="2000" height="2800" viewBox="0 0 2000 2800"><path fill="#304747" fill-rule="evenodd" d="M88.888889,0H1911.111111A88.888889,88.888889 0 0 1 2000,88.888889V2711.111111A88.888889,88.888889 0 0 1 1911.111111,2800H88.888889A88.888889,88.888889 0 0 1 0,2711.111111V88.888889A88.888889,88.888889 0 0 1 88.888889,0Z M88.888889,60H1911.111111A28.888889000000006,28.888889000000006 0 0 1 1940,88.888889V2711.111111A28.888889000000006,28.888889000000006 0 0 1 1911.111111,2740H88.888889A28.888889000000006,28.888889000000006 0 0 1 60,2711.111111V88.888889A28.888889000000006,28.888889000000006 0 0 1 88.888889,60Z"/></svg>
|
||||
|
After Width: | Height: | Size: 762 B |
BIN
spikes/timothy-layer-proof/output/layers/normal-overlay.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="2000" height="2800" viewBox="0 0 2000 2800"><rect x="48" y="48" width="1904" height="312" fill="#eee8d5"/><rect x="48" y="2180" width="1904" height="572" fill="#eee8d5"/><path d="M48 357H1952 M48 2183H1952" stroke="#304747" stroke-width="6"/><path fill="#304747" fill-rule="evenodd" d="M88.888889,0H1911.111111A88.888889,88.888889 0 0 1 2000,88.888889V2711.111111A88.888889,88.888889 0 0 1 1911.111111,2800H88.888889A88.888889,88.888889 0 0 1 0,2711.111111V88.888889A88.888889,88.888889 0 0 1 88.888889,0Z M88.888889,60H1911.111111A28.888889000000006,28.888889000000006 0 0 1 1940,88.888889V2711.111111A28.888889000000006,28.888889000000006 0 0 1 1911.111111,2740H88.888889A28.888889000000006,28.888889000000006 0 0 1 60,2711.111111V88.888889A28.888889000000006,28.888889000000006 0 0 1 88.888889,60Z"/></svg>
|
||||
|
After Width: | Height: | Size: 960 B |
|
After Width: | Height: | Size: 29 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="2000" height="2800" viewBox="0 0 2000 2800"><rect x="48" y="48" width="1904" height="312" fill="#eee8d5"/><rect x="48" y="2180" width="1904" height="572" fill="#eee8d5"/><path d="M48 357H1952 M48 2183H1952" stroke="#304747" stroke-width="6"/><path fill="#304747" fill-rule="evenodd" d="M0,0H2000V2800H0Z M88.888889,60H1911.111111A28.888889000000006,28.888889000000006 0 0 1 1940,88.888889V2711.111111A28.888889000000006,28.888889000000006 0 0 1 1911.111111,2740H88.888889A28.888889000000006,28.888889000000006 0 0 1 60,2711.111111V88.888889A28.888889000000006,28.888889000000006 0 0 1 88.888889,60Z"/></svg>
|
||||
|
After Width: | Height: | Size: 758 B |
BIN
spikes/timothy-layer-proof/output/layers/text.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
1
spikes/timothy-layer-proof/output/layers/text.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="2000" height="2800" viewBox="0 0 2000 2800"><text id="title" x="1000" y="197" font-family="Noto Serif" font-size="110" text-anchor="middle" fill="#304747">TIMOTHY</text><text id="subtitle" x="160" y="288" font-family="Noto Sans" font-size="44" text-anchor="start" fill="#304747">Faithful disciple</text><text id="category" x="1840" y="288" font-family="Noto Sans" font-size="38" text-anchor="end" fill="#304747">PERSON</text><text id="verse-0" x="1000" y="2320" font-family="Noto Serif" font-size="72" text-anchor="middle" fill="#304747">“I have fought a good fight,</text><text id="verse-1" x="1000" y="2415" font-family="Noto Serif" font-size="72" text-anchor="middle" fill="#304747">I have finished my course,</text><text id="verse-2" x="1000" y="2510" font-family="Noto Serif" font-size="72" text-anchor="middle" fill="#304747">I have kept the faith:”</text><text id="reference" x="1000" y="2635" font-family="Noto Sans" font-size="44" text-anchor="middle" fill="#304747">2 Timothy 4:7 · KJV</text></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 2.5 MiB |
BIN
spikes/timothy-layer-proof/output/review/layout-guide.png
Normal file
|
After Width: | Height: | Size: 4.6 MiB |
|
After Width: | Height: | Size: 3.6 MiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="2000" height="2800" viewBox="0 0 2000 2800"><text id="too-long" x="1000" y="197" font-family="Noto Serif" font-size="110" text-anchor="middle" fill="#304747">TIMOTHY TIMOTHY TIMOTHY TIMOTHY TIMOTHY TIMOTHY TIMOTHY TIMOTHY TIMOTHY TIMOTHY TIMOTHY TIMOTHY </text></svg>
|
||||
|
After Width: | Height: | Size: 418 B |
BIN
spikes/timothy-layer-proof/output/review/printings.png
Normal file
|
After Width: | Height: | Size: 930 KiB |
1
spikes/timothy-layer-proof/output/review/printings.svg
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
8
spikes/timothy-layer-proof/output/review/text-bounds.csv
Normal file
@@ -0,0 +1,8 @@
|
||||
svg9,164.268,117.25,1672.05,2528.31
|
||||
title,745.295,117.25,511.94,80.85
|
||||
subtitle,164.268,254.34,315.744,44.22
|
||||
category,1696.51,260.45,139.802,27.93
|
||||
verse-0,541.72,2264.2,916.56,73.08
|
||||
verse-1,553.348,2359.2,892.008,73.08
|
||||
verse-2,632.584,2454.56,733.536,72.72
|
||||
reference,812.318,2601.56,377.476,44
|
||||
|
1
spikes/timothy-layer-proof/output/timothy-editable.svg
Normal file
|
After Width: | Height: | Size: 3.6 MiB |
95
spikes/timothy-layer-proof/output/validation.json
Normal file
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"status": "passed",
|
||||
"template": "common-proof-v1",
|
||||
"source": {
|
||||
"dimensions": [
|
||||
1060,
|
||||
1484
|
||||
],
|
||||
"sha256": "64431ddf894056d0936200117002ecce120fe553e2cce81bb4763e783e646393",
|
||||
"transform": "Uniform scale 1.8867924528301887; no crop. Master is upscaled from generated source."
|
||||
},
|
||||
"tools": {
|
||||
"inkscape": "Inkscape 1.2.2 (b0a8486541, 2022-12-01)",
|
||||
"imagemagick": "Version: ImageMagick 7.1.2-31 Q16-HDRI x86_64 8309dc92a:20260903 https://imagemagick.org"
|
||||
},
|
||||
"fonts": {
|
||||
"Noto Serif": {
|
||||
"file": "/usr/share/fonts/truetype/noto/NotoSerif-Regular.ttf",
|
||||
"sha256": "9d7583b7dc9e812afd32a14280c5cac3160012efe50c8d08938f4fea266ff67f"
|
||||
},
|
||||
"Noto Sans": {
|
||||
"file": "/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf",
|
||||
"sha256": "89c3c497f618fdaa0b2d1e98fef93582f28c71debd2c4a8cdf41f190ced2909d"
|
||||
}
|
||||
},
|
||||
"glyphBounds": {
|
||||
"svg9": [
|
||||
164.268,
|
||||
117.25,
|
||||
1672.05,
|
||||
2528.31
|
||||
],
|
||||
"title": [
|
||||
745.295,
|
||||
117.25,
|
||||
511.94,
|
||||
80.85
|
||||
],
|
||||
"subtitle": [
|
||||
164.268,
|
||||
254.34,
|
||||
315.744,
|
||||
44.22
|
||||
],
|
||||
"category": [
|
||||
1696.51,
|
||||
260.45,
|
||||
139.802,
|
||||
27.93
|
||||
],
|
||||
"verse-0": [
|
||||
541.72,
|
||||
2264.2,
|
||||
916.56,
|
||||
73.08
|
||||
],
|
||||
"verse-1": [
|
||||
553.348,
|
||||
2359.2,
|
||||
892.008,
|
||||
73.08
|
||||
],
|
||||
"verse-2": [
|
||||
632.584,
|
||||
2454.56,
|
||||
733.536,
|
||||
72.72
|
||||
],
|
||||
"reference": [
|
||||
812.318,
|
||||
2601.56,
|
||||
377.476,
|
||||
44
|
||||
]
|
||||
},
|
||||
"inspectedJoinPixels": 99200,
|
||||
"runtimeTextureAlpha": "Every card pixel is fully opaque; harness geometry owns the rounded silhouette",
|
||||
"negativeChecks": [
|
||||
"Rendered overlong title rejected",
|
||||
"One-pixel alpha seam detected"
|
||||
],
|
||||
"decodedHashes": {
|
||||
"normal": "da8a7aa62f0bc4369527859f2f625adb656f201059a765efe6238198b36d6d64",
|
||||
"textless": "3d64324a77eb31313c7123018580fd4ab834aeff6fe0b14b586814f1f8c9980c",
|
||||
"borderless": "bffe0a4d7a58caeda72fdc990333e5466bd89a5ac7f19f86d241fbbb412cd6de",
|
||||
"boundless": "d418f16ff392b1f2b3e237db0a67e1b218b7aeeb5f0d6927a4cde2b984ffb3a9"
|
||||
},
|
||||
"finishRecipe": "Timothy saturation smoothstep(0.08,0.58) × value smoothstep(0.015,0.16), occluded by active printed-ink frame/backing",
|
||||
"limitations": [
|
||||
"Visual approval pending",
|
||||
"No automated semantic or optical-centering judgment",
|
||||
"No runtime lighting review performed by this script",
|
||||
"Two-line alternate title layout not implemented in this proof"
|
||||
]
|
||||
}
|
||||
BIN
spikes/timothy-layer-proof/source/timothy-art.png
Normal file
|
After Width: | Height: | Size: 2.7 MiB |