Add Legendary Moses card and production guidance
BIN
artifacts/cards/BP-001-moses/review/art-card-size.png
Normal file
|
After Width: | Height: | Size: 762 KiB |
BIN
artifacts/cards/BP-001-moses/review/art-review.png
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
artifacts/cards/BP-001-moses/review/art-thumbnail.png
Normal file
|
After Width: | Height: | Size: 207 KiB |
463
artifacts/cards/BP-001-moses/review/build-validation.json
Normal file
@@ -0,0 +1,463 @@
|
||||
{
|
||||
"status": "passed",
|
||||
"cardId": "BP-001",
|
||||
"revision": "v05",
|
||||
"finalCardApproval": null,
|
||||
"assemblyAuthorization": "Okay. Let's finish the Moses card with the actual generating the prints/masks",
|
||||
"selectedPrototype": {
|
||||
"path": "in-progress/cards/BP-001-moses/revisions/v05/review/vertical-text-prototype-v7",
|
||||
"normalSHA256": "0bef4f96859790f5c42bc782a26f45a0835134338f35feaccbd60d4827c5d14d",
|
||||
"validationSHA256": "e5fe7a009d03ecc33ee6a92b1c357f78992b39e88e58c51079eb5675546c7772",
|
||||
"authority": "v7 comparison PNG; inherited antialias pixels intentionally pinned by the selected proof"
|
||||
},
|
||||
"inputs": {
|
||||
"art": {
|
||||
"path": "in-progress/cards/BP-001-moses/revisions/v05/source/art-master.png",
|
||||
"sha256": "372fb8f9f5b398c943364a620c970fd81e000f3e0613f71eff84e087f6396bee"
|
||||
},
|
||||
"foreground": {
|
||||
"path": "in-progress/cards/BP-001-moses/revisions/v05/source/foreground/foreground-2000.png",
|
||||
"sha256": "094ec94400a21dd78f8e237104ebf11bf8cafc868f27c1ef78b640b7b3d5f857"
|
||||
},
|
||||
"text": {
|
||||
"path": "in-progress/cards/BP-001-moses/revisions/v05/source/text/text-2000.png",
|
||||
"sha256": "2623f00ffe8d9e5883215e08452f2ecd51eb21bb55ab79f485ce6ca82f1e1155"
|
||||
},
|
||||
"builderSHA256": "19729b7ba2a42d02788f2a8add6f613a87ed660e1c5dc2cd4944bd6aa1785373"
|
||||
},
|
||||
"bespokeLayers": {
|
||||
"template": "BP-001-moses-legendary-tent-textile-v1",
|
||||
"canvas": [
|
||||
2000,
|
||||
2800
|
||||
],
|
||||
"compositionOrder": [
|
||||
"base-art",
|
||||
"frame",
|
||||
"curtain-over-rail",
|
||||
"backing",
|
||||
"adapted-cloud-and-sleeve",
|
||||
"text"
|
||||
],
|
||||
"printingComponents": {
|
||||
"normal": [
|
||||
"base-art",
|
||||
"frame",
|
||||
"curtain-over-rail",
|
||||
"backing",
|
||||
"adapted-foreground",
|
||||
"text"
|
||||
],
|
||||
"boundless": [
|
||||
"base-art"
|
||||
],
|
||||
"borderless": [
|
||||
"base-art",
|
||||
"backing",
|
||||
"adapted-foreground",
|
||||
"text"
|
||||
],
|
||||
"textless": [
|
||||
"base-art",
|
||||
"frame",
|
||||
"curtain-over-rail",
|
||||
"cloud-crown-over-frame"
|
||||
]
|
||||
}
|
||||
},
|
||||
"typography": {
|
||||
"sharedFontManifestSHA256": "ef68ee6393981d54ca068a950f21c53b374d20e6e46c21c788f36380d3aae178",
|
||||
"title": {
|
||||
"font": "P052-Bold.otf",
|
||||
"weight": 700,
|
||||
"size": 142
|
||||
},
|
||||
"verse": {
|
||||
"font": "SanctificationP052-Medium.otf",
|
||||
"weight": 500,
|
||||
"size": 66,
|
||||
"lines": [
|
||||
"And there has not arisen",
|
||||
"a prophet since in Israel",
|
||||
"like Moses, whom the LORD",
|
||||
"knew face to face"
|
||||
]
|
||||
},
|
||||
"reference": {
|
||||
"font": "SanctificationP052-Medium.otf",
|
||||
"weight": 500,
|
||||
"size": 48,
|
||||
"text": "Deuteronomy 34:10 \u2022 ESV"
|
||||
},
|
||||
"proofChecks": {
|
||||
"fontManifestSHA256": "ef68ee6393981d54ca068a950f21c53b374d20e6e46c21c788f36380d3aae178",
|
||||
"checks": [
|
||||
{
|
||||
"id": "title",
|
||||
"text": "MOSES",
|
||||
"baseline": 244,
|
||||
"font": "P052",
|
||||
"weight": 700,
|
||||
"size": 142,
|
||||
"inkBounds": [
|
||||
1202,
|
||||
145,
|
||||
1711,
|
||||
247
|
||||
],
|
||||
"safeBounds": [
|
||||
1050,
|
||||
90,
|
||||
1905,
|
||||
330
|
||||
],
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"foregroundIntersectionPixels": 0
|
||||
},
|
||||
{
|
||||
"id": "verse-0",
|
||||
"text": "And there has not arisen",
|
||||
"baseline": 2189,
|
||||
"font": "Sanctification P052",
|
||||
"weight": 500,
|
||||
"size": 66,
|
||||
"inkBounds": [
|
||||
172,
|
||||
2140,
|
||||
889,
|
||||
2191
|
||||
],
|
||||
"safeBounds": [
|
||||
95,
|
||||
2070,
|
||||
965,
|
||||
2560
|
||||
],
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"foregroundIntersectionPixels": 0
|
||||
},
|
||||
{
|
||||
"id": "verse-1",
|
||||
"text": "a prophet since in Israel",
|
||||
"baseline": 2299,
|
||||
"font": "Sanctification P052",
|
||||
"weight": 500,
|
||||
"size": 66,
|
||||
"inkBounds": [
|
||||
185,
|
||||
2250,
|
||||
876,
|
||||
2318
|
||||
],
|
||||
"safeBounds": [
|
||||
95,
|
||||
2070,
|
||||
965,
|
||||
2560
|
||||
],
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"foregroundIntersectionPixels": 0
|
||||
},
|
||||
{
|
||||
"id": "verse-2",
|
||||
"text": "like Moses, whom the LORD",
|
||||
"baseline": 2409,
|
||||
"font": "Sanctification P052",
|
||||
"weight": 500,
|
||||
"size": 66,
|
||||
"inkBounds": [
|
||||
112,
|
||||
2360,
|
||||
948,
|
||||
2420
|
||||
],
|
||||
"safeBounds": [
|
||||
95,
|
||||
2070,
|
||||
965,
|
||||
2560
|
||||
],
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"foregroundIntersectionPixels": 0
|
||||
},
|
||||
{
|
||||
"id": "verse-3",
|
||||
"text": "knew face to face",
|
||||
"baseline": 2519,
|
||||
"font": "Sanctification P052",
|
||||
"weight": 500,
|
||||
"size": 66,
|
||||
"inkBounds": [
|
||||
282,
|
||||
2470,
|
||||
778,
|
||||
2521
|
||||
],
|
||||
"safeBounds": [
|
||||
95,
|
||||
2070,
|
||||
965,
|
||||
2560
|
||||
],
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"foregroundIntersectionPixels": 0
|
||||
},
|
||||
{
|
||||
"id": "reference",
|
||||
"text": "Deuteronomy 34:10 \u2022 ESV",
|
||||
"baseline": 2640,
|
||||
"font": "Sanctification P052",
|
||||
"weight": 500,
|
||||
"size": 48,
|
||||
"inkBounds": [
|
||||
253,
|
||||
2605,
|
||||
807,
|
||||
2654
|
||||
],
|
||||
"safeBounds": [
|
||||
200,
|
||||
2570,
|
||||
870,
|
||||
2690
|
||||
],
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"foregroundIntersectionPixels": 0
|
||||
}
|
||||
],
|
||||
"verseInkUnion": [
|
||||
112,
|
||||
2140,
|
||||
948,
|
||||
2521
|
||||
],
|
||||
"upperFlourishToVerseInk": 84,
|
||||
"verseInkToReferenceLineBox": 84,
|
||||
"centeringDifference": 0,
|
||||
"lineBaselineGap": 110,
|
||||
"requestedSemanticLines": [
|
||||
"And there has not arisen",
|
||||
"a prophet since in Israel",
|
||||
"like Moses, whom the LORD",
|
||||
"knew face to face"
|
||||
],
|
||||
"usableBandTop": 2056
|
||||
}
|
||||
},
|
||||
"textMask": {
|
||||
"expansionRadius": 12,
|
||||
"gaussianRadius": 8,
|
||||
"units": "master pixels"
|
||||
},
|
||||
"finishRecipe": {
|
||||
"id": "raw-ridges-v1",
|
||||
"sha256": "23331ce56a717edb4e6f85956d11fdd98397009c4e6dd230d8c334c77571dd18",
|
||||
"path": "tools/card-production/recipes/raw-ridges-v1.json",
|
||||
"baseAnalysisCount": 1,
|
||||
"adaptedForegroundAnalysisCount": 1
|
||||
},
|
||||
"normalAuthorityCheck": {
|
||||
"productionEqualsSelectedPrototype": true,
|
||||
"selectedSHA256": "0bef4f96859790f5c42bc782a26f45a0835134338f35feaccbd60d4827c5d14d",
|
||||
"productionSHA256": "0bef4f96859790f5c42bc782a26f45a0835134338f35feaccbd60d4827c5d14d",
|
||||
"reconstructionChangedPixels": 3990925,
|
||||
"reconstructionMaximumChannelDelta": 127
|
||||
},
|
||||
"printings": {
|
||||
"normal": {
|
||||
"components": [
|
||||
"base-art",
|
||||
"frame",
|
||||
"curtain-over-rail",
|
||||
"backing",
|
||||
"adapted-foreground",
|
||||
"text"
|
||||
],
|
||||
"textMask": true,
|
||||
"finishCoverage": "base plus depth-resolved adapted foreground",
|
||||
"exports": [
|
||||
{
|
||||
"resolution": "low",
|
||||
"dimensions": [
|
||||
500,
|
||||
700
|
||||
],
|
||||
"files": {
|
||||
"card.png": "1835792bbd701f051e2128a71cd4ed26a97db64e7e72d3914ef7d97572ffbb31",
|
||||
"finish-mask.png": "7ef9b993c407d9d37e9477d5b6e8a59cf6f91dbfd951cf1899a31d459c51aa9d",
|
||||
"text-mask.png": "bd3e477df1354f2df81cca9585913502ba4d2f4f68b26c81b42a0d907da0e289"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resolution": "med",
|
||||
"dimensions": [
|
||||
1000,
|
||||
1400
|
||||
],
|
||||
"files": {
|
||||
"card.png": "c7ae7af2c377920c734d2dbbb2f933f3e30720877c7c42f9dcfd5874dfaeff45",
|
||||
"finish-mask.png": "bf66bfbd3a66ccde7ec565fc0dc6997826098f8490c74fb930fb100752f119ba",
|
||||
"text-mask.png": "43a26baa3d9155f8bb711f05b3e589f0cef757e0561b109dc01d5e7ab0681be6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resolution": "high",
|
||||
"dimensions": [
|
||||
2000,
|
||||
2800
|
||||
],
|
||||
"files": {
|
||||
"card.png": "0bef4f96859790f5c42bc782a26f45a0835134338f35feaccbd60d4827c5d14d",
|
||||
"finish-mask.png": "1ea7ff3ed6eb11ba97c7ff8e50fe7d4d57a30fd264f3dc8e12fb8f241bedc5a9",
|
||||
"text-mask.png": "c75a0c9e0250707b0e498307d0793351a5b27a162732986336c4038c86a9327f"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"boundless": {
|
||||
"components": [
|
||||
"base-art"
|
||||
],
|
||||
"textMask": false,
|
||||
"finishCoverage": "base illustration",
|
||||
"exports": [
|
||||
{
|
||||
"resolution": "low",
|
||||
"dimensions": [
|
||||
500,
|
||||
700
|
||||
],
|
||||
"files": {
|
||||
"card.png": "fede895433a8eed5f6156504748d47a330ea7a0306d24ab63017dc1c81867c1e",
|
||||
"finish-mask.png": "8fb8ba1356a9af345fc97ecd0b1b1b7a645f90d342b974f68b030b402137e9e6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resolution": "med",
|
||||
"dimensions": [
|
||||
1000,
|
||||
1400
|
||||
],
|
||||
"files": {
|
||||
"card.png": "8ff29b5cff2d8ad37f16925f3d0bc6e2102e5f8bb5eaca1401c43e30e58d5508",
|
||||
"finish-mask.png": "1a93b38af6fbbaf8535a48e4f3ee0eb19554edec8f5fac80fc4db8974fb9a4ef"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resolution": "high",
|
||||
"dimensions": [
|
||||
2000,
|
||||
2800
|
||||
],
|
||||
"files": {
|
||||
"card.png": "6eea105658e4c8ca85360b8c0e5225e060085bdc70918b17409728270734ad61",
|
||||
"finish-mask.png": "8ff4c66e561aaaf8d6107fd91680100f345de35744453940be838feae550c8a2"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"borderless": {
|
||||
"components": [
|
||||
"base-art",
|
||||
"backing",
|
||||
"adapted-foreground",
|
||||
"text"
|
||||
],
|
||||
"textMask": true,
|
||||
"finishCoverage": "base plus depth-resolved adapted foreground",
|
||||
"exports": [
|
||||
{
|
||||
"resolution": "low",
|
||||
"dimensions": [
|
||||
500,
|
||||
700
|
||||
],
|
||||
"files": {
|
||||
"card.png": "84edcdaa24b0f755921500c3ffedc533efedc117004f6d7b6c222e57440f4c21",
|
||||
"finish-mask.png": "cfa517b72d82dcaed7bda5c92a5691588a6cf5886a5bb7a4c6e71c9b0065244b",
|
||||
"text-mask.png": "bd3e477df1354f2df81cca9585913502ba4d2f4f68b26c81b42a0d907da0e289"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resolution": "med",
|
||||
"dimensions": [
|
||||
1000,
|
||||
1400
|
||||
],
|
||||
"files": {
|
||||
"card.png": "88726c0c6326df666df07c13c1d88aac314f4454c7cceb866797709d8c9f052a",
|
||||
"finish-mask.png": "ee75326152bfe25ee4abc7cc1c9f2623df0ad92ce524f6866c8c088007ea5739",
|
||||
"text-mask.png": "43a26baa3d9155f8bb711f05b3e589f0cef757e0561b109dc01d5e7ab0681be6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resolution": "high",
|
||||
"dimensions": [
|
||||
2000,
|
||||
2800
|
||||
],
|
||||
"files": {
|
||||
"card.png": "40ea9c9ae69438164b3bed749f83908f3093b0c8f1ee75ceba9cf78bddc4f13f",
|
||||
"finish-mask.png": "07c8f84149590e8dbf21b6e51af696d9a9610a291c0bf59508447e9ca7d08994",
|
||||
"text-mask.png": "c75a0c9e0250707b0e498307d0793351a5b27a162732986336c4038c86a9327f"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"textless": {
|
||||
"components": [
|
||||
"base-art",
|
||||
"frame",
|
||||
"curtain-over-rail",
|
||||
"cloud-crown-over-frame"
|
||||
],
|
||||
"textMask": false,
|
||||
"finishCoverage": "base illustration with frame depth resolved",
|
||||
"exports": [
|
||||
{
|
||||
"resolution": "low",
|
||||
"dimensions": [
|
||||
500,
|
||||
700
|
||||
],
|
||||
"files": {
|
||||
"card.png": "b1473c44ea20da61f9937639a70eb6df8ce46984facc4d278e77d9b82f773939",
|
||||
"finish-mask.png": "eb12314711c2b81dfd1b022087780fb05372fa7166ba164c37fc5b8de6cdbd38"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resolution": "med",
|
||||
"dimensions": [
|
||||
1000,
|
||||
1400
|
||||
],
|
||||
"files": {
|
||||
"card.png": "403b7ed0b163709747a32d62baa9bd50281fc0ea2b72996fa18b2abee6f4d98d",
|
||||
"finish-mask.png": "e51b00c6812a7fa7488f0e2c733f50857562080e269f018ffb4c6501db6c849c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resolution": "high",
|
||||
"dimensions": [
|
||||
2000,
|
||||
2800
|
||||
],
|
||||
"files": {
|
||||
"card.png": "bb46a64ec206dbc3c7ce1f50790a24deb1292fbba961852a786e4af334118ff9",
|
||||
"finish-mask.png": "11a80668e947ea8b58e89764fdbd81b46588ba3fd50a6ca37d7b192128fe37aa"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"inkscape": "Inkscape 1.2.2 (b0a8486541, 2022-12-01)",
|
||||
"pillow": "10.2.0",
|
||||
"numpy": "1.26.4"
|
||||
},
|
||||
"visualReview": {
|
||||
"static": "pending user review",
|
||||
"movingLight": "not performed",
|
||||
"userApproval": "pending"
|
||||
}
|
||||
}
|
||||
BIN
artifacts/cards/BP-001-moses/review/finish-masks-comparison.png
Normal file
|
After Width: | Height: | Size: 406 KiB |
1
artifacts/cards/BP-001-moses/review/fonts.conf
Normal file
@@ -0,0 +1 @@
|
||||
<fontconfig><dir>/home/dkzver/docker/sanctification/fonts</dir><cachedir>/home/dkzver/docker/sanctification/in-progress/cards/BP-001-moses/revisions/v05/review/font-cache</cachedir></fontconfig>
|
||||
@@ -0,0 +1,9 @@
|
||||
# Moses v05 — continuous near entrance post
|
||||
|
||||
Inspected at card size and enlarged upper/ground joins. The previously interrupted gold post now connects through the cloud to the ground socket, restoring the near entrance structure. Its width and slight leftward descent follow the upper post and counterpart support. Dark structural leading, unequal panel lengths, ivory highlight facets and restrained sampled glass variation connect its design to the existing posts. No extra textile binding was added.
|
||||
|
||||
The correction remains visually simple and narrow. At enlarged size its authored segments are more regular and cleaner than generated surrounding glass; the join and endpoint are retained for explicit review in `post-join-detail.png`. At card size it reads as one continuous support. No broad generative repaint was used.
|
||||
|
||||
Pixel comparison proves the entire remaining image is untouched: 72,357 changed pixels, exclusive bounding box `[318,994,377,2647]`, 1.2921% of the 2000×2800 canvas. All pixels outside the authorized post rectangle are identical to v04. Rebuilding produced byte-identical output images. Prior v04 files were hash-checked unchanged.
|
||||
|
||||
This is art-only, pending user review. No card frame, backing, text, printing, masks or final approval were created. The transparent post source is a narrow illustration correction, not a production printing layer package.
|
||||
BIN
artifacts/cards/BP-001-moses/review/layers-comparison.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
@@ -0,0 +1,30 @@
|
||||
# Moses v05 — Normal prototype
|
||||
|
||||
Review-only composition using the selected v05 art. The liked nameplate geometry, P052 typography and suspended linen layout carry forward from v03 layout-preview-astra-v5. New wave-like contours follow the pearl/cool cloud, replacing the previous round lobes. The lower closure is authored above the original ground occlusion. The existing continuous near post is retained from source pixels; no second post is drawn.
|
||||
|
||||
- [Full preview](composed-preview-1000.png)
|
||||
- [500 px preview](composed-preview-500.png)
|
||||
- [Source/composed comparison](source-composed-comparison.png)
|
||||
- [Prior/current layout comparison](layout-lineage-comparison.png)
|
||||
- [Enlarged junction details](cloud-textile-junction-details.png)
|
||||
- [Editable composition](composed-preview.svg)
|
||||
- [Builder](build-preview.py)
|
||||
- [Validation](preview-validation.json)
|
||||
|
||||
The linen field has a shallow sag, curved left edge, woven hem and right frame ties. Its left corner extends behind the cloud toward the existing post. The fine gold enclosure supports the illustrated curtain and cloud. The lineage comparison intentionally includes different source illustrations.
|
||||
|
||||
Source: ../../source/art-master.png, unchanged at original 2000 × 2800 registration. SHA256: 372fb8f9f5b398c943364a620c970fd81e000f3e0613f71eff84e087f6396bee.
|
||||
|
||||
Exact title: `MOSES`
|
||||
|
||||
Exact narrator text, without quotation marks or added final punctuation:
|
||||
|
||||
`And there has not arisen a prophet since in Israel like Moses, whom the LORD knew face to face`
|
||||
|
||||
Exact reference: `Deuteronomy 34:10 • ESV`
|
||||
|
||||
Validation checks exact content, P052 font hashes/glyph coverage, measured safe bounds, opaque backing beneath all glyphs, zero foreground/text intersection, unchanged source hash and text-pixel equality to the carried-forward layout. Verse spacing is 66 master pixels above and 71 pixels to reference ink. Geometry renders at 2× with premultiplied-alpha Lanczos downsampling; glass is not blurred. Static review includes full card and enlarged junctions.
|
||||
|
||||
Rebuild from the repository: python3 in-progress/cards/BP-001-moses/revisions/v05/review/normal-prototype-v1/build-preview.py. Requires Inkscape, Fontconfig, Pillow and NumPy.
|
||||
|
||||
Normal prototype only. No Borderless, Textless, Boundless, finish masks, production glyph masks, harness fixture, acceptance or promotion. Earlier revisions and candidates are preserved.
|
||||
|
After Width: | Height: | Size: 274 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><defs>
|
||||
<linearGradient id="paper" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fbf3df"/><stop offset=".5" stop-color="#f4ead4"/><stop offset="1" stop-color="#e8d4b0"/></linearGradient>
|
||||
<linearGradient id="gold" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f0d47d"/><stop offset=".48" stop-color="#bd8b31"/><stop offset="1" stop-color="#71501e"/></linearGradient>
|
||||
<pattern id="weave" width="44" height="44" patternUnits="userSpaceOnUse"><path d="M0 11H44 M0 33H44 M11 0V44 M33 0V44" stroke="#9d783d" stroke-width="1" opacity=".045"/><path d="M22 7l7 15-7 15-7-15Z" fill="none" stroke="#806438" stroke-width="1" opacity=".07"/></pattern>
|
||||
<pattern id="lapis-band" width="34" height="34" patternUnits="userSpaceOnUse"><path d="M17 2L32 17 17 32 2 17Z" fill="#173e59" stroke="#d7b35a" stroke-width="3"/><circle cx="17" cy="17" r="4" fill="url(#gold)"/></pattern>
|
||||
<clipPath id="curtain-clip"><path d="M-10-10 H770 C734 204 694 395 650 585 C610 770 566 972 522 1170 C474 1388 426 1604 378 1818 C338 2000 302 2190 286 2380 C276 2510 304 2640 412 2810 H-10Z"/></clipPath>
|
||||
<linearGradient id="linen" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#f7edd4"/><stop offset=".55" stop-color="#f2e7cd"/><stop offset="1" stop-color="#e8d7b6"/></linearGradient><clipPath id="cloud-top"><path d="M1045 0 C1048 62 1012 102 971 105 C942 108 910 123 913 146 C916 166 952 176 967 209 C991 248 983 287 967 310 C956 325 971 347 959 371 C946 397 900 400 876 432 L780 490 H0 V0Z"/></clipPath><clipPath id="cloud-low"><path d="M0 400H370 V1780H765 C765 1920 738 2010 724 2080 C720 2160 692 2180 704 2230 C711 2265 694 2294 677 2322 C664 2345 672 2385 695 2410 C721 2440 722 2498 678 2538 C642 2543 622 2480 586 2480 C535 2480 536 2458 500 2444 C462 2421 438 2445 409 2424 C384 2408 370 2375 370 2340 L369 2640 Q286 2659 205 2635 Q101 2583 0 2612Z"/></clipPath><clipPath id="title-edge-zone"><path d="M700 62 H1900 Q1940 62 1940 102 V342 H700Z"/></clipPath></defs><g id="legendary-frame"><rect x="19" y="19" width="1962" height="2762" rx="28" fill="none" stroke="#251c15" stroke-width="21"/><rect x="23" y="23" width="1954" height="2754" rx="23" fill="none" stroke="url(#gold)" stroke-width="11"/><rect x="43" y="43" width="1914" height="2714" rx="15" fill="none" stroke="#e2bf6c" stroke-width="3"/></g><g id="title-backing"><path d="M700 62 H1900 Q1940 62 1940 102 V342 H700Z" fill="url(#paper)" stroke="#62421e" stroke-width="9"/><path d="M700 62 H1900 Q1940 62 1940 102 V342 H700Z" fill="url(#weave)" stroke="url(#gold)" stroke-width="4"/><path d="M710 82 H1898 Q1920 82 1920 104 V320 H710" fill="none" stroke="#bb9040" stroke-width="3"/><path d="M1070 301H1870" stroke="#c5a257" stroke-width="2"/></g><g id="verse-backing"><path d="M340 2106 C445 2114 550 2118 655 2140 C960 2230 1435 2260 1960 2095 L1960 2720 C1535 2782 1045 2728 580 2750 C488 2755 437 2684 453 2592 C462 2506 520 2470 574 2404 C625 2341 641 2230 655 2140 L340 2106Z" transform="translate(3 6)" fill="#211b15" opacity=".6"/><path d="M340 2106 C445 2114 550 2118 655 2140 C960 2230 1435 2260 1960 2095 L1960 2720 C1535 2782 1045 2728 580 2750 C488 2755 437 2684 453 2592 C462 2506 520 2470 574 2404 C625 2341 641 2230 655 2140 L340 2106Z" fill="url(#linen)" stroke="#79552b" stroke-width="4"/><path d="M340 2106 C445 2114 550 2118 655 2140 C960 2230 1435 2260 1960 2095 L1960 2720 C1535 2782 1045 2728 580 2750 C488 2755 437 2684 453 2592 C462 2506 520 2470 574 2404 C625 2341 641 2230 655 2140 L340 2106Z" fill="url(#weave)"/><path d="M690 2170 C1000 2250 1470 2250 1925 2138 M490 2677 C516 2726 551 2728 591 2725 C1060 2702 1530 2757 1925 2696" fill="none" stroke="#a17533" stroke-width="3"/><path d="M688 2182 C1000 2262 1470 2262 1923 2150 M489 2665 C516 2714 551 2716 591 2713 C1060 2690 1530 2745 1925 2684" fill="none" stroke="#8c3d3c" stroke-width="2" opacity=".65"/><path d="M1925 2140V2695" fill="none" stroke="#b7924a" stroke-width="3" stroke-dasharray="7 9"/></g><g fill="none" stroke-linecap="round"><path d="M1940 2119 C1950 2106 1964 2105 1977 2109" stroke="#362719" stroke-width="13"/><path d="M1940 2119 C1950 2106 1964 2105 1977 2109" stroke="#d6b260" stroke-width="7"/><path d="M1940 2709 C1950 2696 1964 2695 1977 2699" stroke="#362719" stroke-width="13"/><path d="M1940 2709 C1950 2696 1964 2695 1977 2699" stroke="#d6b260" stroke-width="7"/></g></svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1,228 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Provisional BP-001 Moses Legendary composition; no production assembly."""
|
||||
from pathlib import Path
|
||||
import hashlib,json,os,subprocess
|
||||
from xml.sax.saxutils import escape
|
||||
import numpy as np
|
||||
from PIL import Image,ImageDraw,ImageFont
|
||||
|
||||
P=Path(__file__).resolve().parent
|
||||
REV=P.parents[1]
|
||||
REPO=next(p for p in P.parents if (p/'docs/card-layer-pipeline.md').is_file())
|
||||
ART=REV/'source/art-master.png'
|
||||
FONTS=REPO/'fonts'
|
||||
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
art_hash=sha(ART)
|
||||
font_manifest=json.loads((FONTS/'manifest.json').read_text())
|
||||
font_files={'title':'P052-Bold.otf','verse':'SanctificationP052-Medium.otf','reference':'SanctificationP052-Medium.otf'}
|
||||
for name in set(font_files.values()):
|
||||
assert sha(FONTS/name)==font_manifest['files'][name]['sha256']
|
||||
(P/'font-cache').mkdir(exist_ok=True)
|
||||
(P/'fonts.conf').write_text(f'<fontconfig><dir>{FONTS}</dir><cachedir>{P/"font-cache"}</cachedir></fontconfig>')
|
||||
env=dict(os.environ,FONTCONFIG_FILE=str(P/'fonts.conf'))
|
||||
def ink(*args):return subprocess.check_output(['inkscape',*map(str,args)],env=env,text=True,stderr=subprocess.PIPE).strip()
|
||||
def render(src,dst):
|
||||
# Supersample only geometry-heavy composite/foreground outputs; text measurements
|
||||
# remain native. LANCZOS uses premultiplied RGBA via Pillow's RGBa path.
|
||||
factor=2 if src.stem in {'foreground-curtain','composed-preview','without-curtain'} else 1
|
||||
ink(src,'--export-type=png',f'--export-filename={dst}',f'--export-width={2000*factor}',f'--export-height={2800*factor}')
|
||||
if factor>1:
|
||||
im=Image.open(dst).convert('RGBA')
|
||||
im.convert('RGBa').resize((2000,2800),Image.Resampling.LANCZOS).convert('RGBA').save(dst)
|
||||
|
||||
def resolve(family,style):return Path(subprocess.check_output(['fc-match','-f','%{file}',f'{family}:style={style}'],env=env,text=True).strip()).resolve()
|
||||
assert resolve('P052','Bold')==(FONTS/'P052-Bold.otf').resolve()
|
||||
assert resolve('Sanctification P052','Medium')==(FONTS/'SanctificationP052-Medium.otf').resolve()
|
||||
|
||||
head='<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800">'
|
||||
defs='''<defs>
|
||||
<linearGradient id="paper" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fbf3df"/><stop offset=".5" stop-color="#f4ead4"/><stop offset="1" stop-color="#e8d4b0"/></linearGradient>
|
||||
<linearGradient id="gold" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f0d47d"/><stop offset=".48" stop-color="#bd8b31"/><stop offset="1" stop-color="#71501e"/></linearGradient>
|
||||
<pattern id="weave" width="44" height="44" patternUnits="userSpaceOnUse"><path d="M0 11H44 M0 33H44 M11 0V44 M33 0V44" stroke="#9d783d" stroke-width="1" opacity=".045"/><path d="M22 7l7 15-7 15-7-15Z" fill="none" stroke="#806438" stroke-width="1" opacity=".07"/></pattern>
|
||||
<pattern id="lapis-band" width="34" height="34" patternUnits="userSpaceOnUse"><path d="M17 2L32 17 17 32 2 17Z" fill="#173e59" stroke="#d7b35a" stroke-width="3"/><circle cx="17" cy="17" r="4" fill="url(#gold)"/></pattern>
|
||||
<clipPath id="curtain-clip"><path d="M-10-10 H770 C734 204 694 395 650 585 C610 770 566 972 522 1170 C474 1388 426 1604 378 1818 C338 2000 302 2190 286 2380 C276 2510 304 2640 412 2810 H-10Z"/></clipPath>
|
||||
</defs>'''
|
||||
base='<image xlink:href="../../source/art-master.png" width="2000" height="2800"/>'
|
||||
frame='''<g id="legendary-frame">
|
||||
<rect x="14" y="14" width="1972" height="2772" rx="34" fill="none" stroke="#2c2115" stroke-width="28"/>
|
||||
<rect x="25" y="25" width="1950" height="2750" rx="27" fill="none" stroke="url(#gold)" stroke-width="14"/>
|
||||
<rect x="43" y="43" width="1914" height="2714" rx="20" fill="none" stroke="#173e59" stroke-width="10"/>
|
||||
<rect x="55" y="55" width="1890" height="2690" rx="15" fill="none" stroke="#d7b35a" stroke-width="4"/>
|
||||
<path d="M68 260V68H260 M1740 68H1932V260 M68 2540V2732H260 M1740 2732H1932V2540" fill="none" stroke="#9b6d26" stroke-width="6"/>
|
||||
<g fill="url(#gold)" stroke="#533810" stroke-width="3"><path d="M68 68l14 14-14 14-14-14Z"/><path d="M1932 68l14 14-14 14-14-14Z"/><path d="M68 2732l14 14-14 14-14-14Z"/><path d="M1932 2732l14 14-14 14-14-14Z"/></g>
|
||||
<rect x="1906" y="430" width="27" height="255" fill="url(#lapis-band)" opacity=".95"/><rect x="1906" y="2115" width="27" height="255" fill="url(#lapis-band)" opacity=".95"/>
|
||||
</g>'''
|
||||
# The panels tuck behind the original tent and the pillar of cloud.
|
||||
# No alteration, retouch, rescaling or regeneration of the selected source.
|
||||
title_path='M700 62 H1900 Q1940 62 1940 102 V342 H700Z'
|
||||
verse_path='M340 2106 C445 2114 550 2118 655 2140 C960 2230 1435 2260 1960 2095 L1960 2720 C1535 2782 1045 2728 580 2750 C488 2755 437 2684 453 2592 C462 2506 520 2470 574 2404 C625 2341 641 2230 655 2140 L340 2106Z'
|
||||
title_inner='<path d="M710 82 H1898 Q1920 82 1920 104 V320 H710" fill="none" stroke="#bb9040" stroke-width="3"/><path d="M1070 301H1870" stroke="#c5a257" stroke-width="2"/>'
|
||||
verse_inner='''<path d="M690 2170 C1000 2250 1470 2250 1925 2138 M490 2677 C516 2726 551 2728 591 2725 C1060 2702 1530 2757 1925 2696" fill="none" stroke="#a17533" stroke-width="3"/><path d="M688 2182 C1000 2262 1470 2262 1923 2150 M489 2665 C516 2714 551 2716 591 2713 C1060 2690 1530 2745 1925 2684" fill="none" stroke="#8c3d3c" stroke-width="2" opacity=".65"/><path d="M1925 2140V2695" fill="none" stroke="#b7924a" stroke-width="3" stroke-dasharray="7 9"/>'''
|
||||
def panel(path,inner,ident):
|
||||
if ident=='verse-backing':
|
||||
# A linen field with a woven seam, rather than a metallic plaque.
|
||||
return f'<g id="{ident}"><path d="{path}" transform="translate(3 6)" fill="#211b15" opacity=".6"/><path d="{path}" fill="url(#linen)" stroke="#79552b" stroke-width="4"/><path d="{path}" fill="url(#weave)"/>{inner}</g>'
|
||||
return f'<g id="{ident}"><path d="{path}" fill="url(#paper)" stroke="#62421e" stroke-width="9"/><path d="{path}" fill="url(#weave)" stroke="url(#gold)" stroke-width="4"/>{inner}</g>'
|
||||
defs=defs.replace('</defs>','<linearGradient id="linen" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#f7edd4"/><stop offset=".55" stop-color="#f2e7cd"/><stop offset="1" stop-color="#e8d7b6"/></linearGradient></defs>')
|
||||
# Ties sit at the existing right rail and visibly support the hanging field.
|
||||
ties='<g fill="none" stroke-linecap="round">'+''.join(f'<path d="M1940 {y+9} C1950 {y-4} 1964 {y-5} 1977 {y-1}" stroke="#362719" stroke-width="13"/><path d="M1940 {y+9} C1950 {y-4} 1964 {y-5} 1977 {y-1}" stroke="#d6b260" stroke-width="7"/>' for y in [2110,2700])+'</g>'
|
||||
panels=panel(title_path,title_inner,'title-backing')+panel(verse_path,verse_inner,'verse-backing')+ties
|
||||
# Trace the visible source silhouette, including the whole cloud lower lobe.
|
||||
# Upper crown is separately registered; both layers stay at source coordinates.
|
||||
# Cubic contours follow the source lead, not the rough v3 clipping guesses.
|
||||
# The lower closing arc ends above the old terrain occlusion.
|
||||
top_edge='M1045 0 C1048 62 1012 102 971 105 C942 108 910 123 913 146 C916 166 952 176 967 209 C991 248 983 287 967 310 C956 325 971 347 959 371 C946 397 900 400 876 432'
|
||||
lower_edge='M724 2080 C720 2160 692 2180 704 2230 C711 2265 694 2294 677 2322 C664 2345 672 2385 695 2410 C721 2440 722 2498 678 2538 C642 2543 622 2480 586 2480 C535 2480 536 2458 500 2444 C462 2421 438 2445 409 2424 C384 2408 370 2375 370 2340'
|
||||
cloud_top=top_edge+' L780 490 H0 V0Z'
|
||||
cloud_low='M0 400H370 V1780H765 C765 1920 738 2010 724 2080 '+lower_edge.removeprefix('M724 2080 ')+' L369 2640 Q286 2659 205 2635 Q101 2583 0 2612Z'
|
||||
defs=defs.replace('</defs>',f'<clipPath id="cloud-top"><path d="{cloud_top}"/></clipPath><clipPath id="cloud-low"><path d="{cloud_low}"/></clipPath><clipPath id="title-edge-zone"><path d="{title_path}"/></clipPath></defs>')
|
||||
# Quiet fine-gold enclosure: motifs reside in the original curtain and cloud.
|
||||
frame='''<g id="legendary-frame"><rect x="19" y="19" width="1962" height="2762" rx="28" fill="none" stroke="#251c15" stroke-width="21"/><rect x="23" y="23" width="1954" height="2754" rx="23" fill="none" stroke="url(#gold)" stroke-width="11"/><rect x="43" y="43" width="1914" height="2714" rx="15" fill="none" stroke="#e2bf6c" stroke-width="3"/></g>'''
|
||||
|
||||
# A narrow opaque lead edge removes blue/terrain fringe without softening glass.
|
||||
lead=f'<g fill="none" stroke="#2b2116" stroke-width="8" stroke-linejoin="round" stroke-linecap="round"><path d="{top_edge}" clip-path="url(#title-edge-zone)"/><path d="{lower_edge}"/></g>'
|
||||
curtain='<g id="presence-foreground">'+''.join(f'<g clip-path="url(#{clip})"><image xlink:href="../../source/art-master.png" width="2000" height="2800"/></g>' for clip in ['cloud-top','cloud-low'])+lead+'</g>'
|
||||
# The continuous near post already belongs to v05 art. The registered source
|
||||
# foreground retains it; no second geometry, gradient, or post overlay is drawn.
|
||||
(P/'foreground-curtain.svg').write_text(head+defs+curtain+'</svg>');render(P/'foreground-curtain.svg',P/'foreground-curtain.png')
|
||||
fg=Image.open(P/'foreground-curtain.png').convert('RGBA')
|
||||
fa=np.asarray(fg.getchannel('A'))
|
||||
assert fa.max()==255 and fa.min()==0
|
||||
|
||||
rows=[
|
||||
('title','MOSES',1458,244,142,'P052',700),
|
||||
('verse-0','And there has not arisen a',1320,2360,74,'Sanctification P052',500),
|
||||
('verse-1','prophet since in Israel like Moses,',1320,2468,74,'Sanctification P052',500),
|
||||
('verse-2','whom the LORD knew face to face',1320,2576,74,'Sanctification P052',500),
|
||||
('reference','Deuteronomy 34:10 • ESV',1320,2690,57,'Sanctification P052',500),
|
||||
]
|
||||
for ident,content,*_ in rows:
|
||||
fontfile=font_files['title' if ident=='title' else 'reference' if ident=='reference' else 'verse']
|
||||
charset=subprocess.check_output(['fc-query','--format=%{charset}',str(FONTS/fontfile)],text=True).split()
|
||||
ranges=[tuple(int(v,16) for v in token.split('-')) for token in charset]
|
||||
assert all(any(r[0]<=ord(char)<=r[-1] for r in ranges) for char in content), (ident, 'missing glyph')
|
||||
|
||||
texts=''.join(f'<text id="{ident}" x="{x}" y="{y}" font-family="{family}" font-weight="{weight}" font-size="{size}" text-anchor="middle" fill="#263f50">{escape(text)}</text>' for ident,text,x,y,size,family,weight in rows)
|
||||
(P/'text-preview.svg').write_text(head+texts+'</svg>');render(P/'text-preview.svg',P/'text-preview.png')
|
||||
text_image=Image.open(P/'text-preview.png').convert('RGBA');ta=np.asarray(text_image.getchannel('A'))
|
||||
assert not np.any((ta>0)&(fa>0))
|
||||
|
||||
(P/'border-backing-preview.svg').write_text(head+defs+frame+panels+'</svg>');render(P/'border-backing-preview.svg',P/'border-backing-preview.png')
|
||||
backing=np.asarray(Image.open(P/'border-backing-preview.png').convert('RGBA').getchannel('A'))
|
||||
checks=[];verse_bounds=[]
|
||||
for ident,text,x,y,size,family,weight in rows:
|
||||
node=next(v for v in texts.split('</text>') if f'id="{ident}"' in v)+'</text>'
|
||||
src=P/(ident+'-ink.svg');dst=P/(ident+'-ink.png');src.write_text(head+node+'</svg>');render(src,dst)
|
||||
alpha=np.asarray(Image.open(dst).convert('RGBA').getchannel('A'));ys,xs=np.where(alpha>0);assert len(xs)
|
||||
bounds=[int(xs.min()),int(ys.min()),int(xs.max()+1),int(ys.max()+1)]
|
||||
safe=[1050,90,1905,330] if ident=='title' else [730,2275,1900,2730]
|
||||
assert safe[0]<=bounds[0] and safe[1]<=bounds[1] and bounds[2]<=safe[2] and bounds[3]<=safe[3],(ident,bounds,safe)
|
||||
assert np.all(backing[alpha>0]==255),ident
|
||||
assert not np.any((alpha>0)&(fa>0)),ident
|
||||
checks.append({'id':ident,'text':text,'baseline':y,'font':family,'weight':weight,'size':size,'inkBounds':bounds,'safeBounds':safe,'glyphPixelsOutsideOpaqueBacking':0,'foregroundIntersectionPixels':0})
|
||||
if ident.startswith('verse-'):verse_bounds.append(bounds)
|
||||
|
||||
assert ' '.join(c['text'] for c in checks if c['id'].startswith('verse-'))==json.loads((REV/'card.json').read_text())['excerpt']
|
||||
assert checks[-1]['text']==json.loads((REV/'card.json').read_text())['referenceDisplay']
|
||||
union=[min(b[0] for b in verse_bounds),min(b[1] for b in verse_bounds),max(b[2] for b in verse_bounds),max(b[3] for b in verse_bounds)]
|
||||
upper=2240;ref_top=checks[-1]['inkBounds'][1];above=union[1]-upper;below=ref_top-union[3]
|
||||
assert min(above,below)>=35 and abs(above-below)<=20,(above,below)
|
||||
|
||||
for name,include_curtain in [('composed-preview',True),('without-curtain',False)]:
|
||||
body=base+frame+panels+(curtain if include_curtain else '')+texts
|
||||
src=P/(name+'.svg');src.write_text(head+defs+body+'</svg>');render(src,P/(name+'-2000.png'))
|
||||
master=Image.open(P/(name+'-2000.png')).convert('RGB')
|
||||
master.resize((1000,1400),Image.Resampling.LANCZOS).save(P/(name+'-1000.png'))
|
||||
master.resize((500,700),Image.Resampling.LANCZOS).save(P/(name+'-500.png'))
|
||||
|
||||
# Compare the structural effect of the attached curtain.
|
||||
board=Image.new('RGB',(1000,750),'#11161c');draw=ImageDraw.Draw(board)
|
||||
for i,(label,path) in enumerate([('Panels before foreground',P/'without-curtain-1000.png'),('Pillar of cloud over panels',P/'composed-preview-1000.png')]):
|
||||
draw.text((i*500+12,12),label,fill='#f4ead4')
|
||||
image=Image.open(path).convert('RGB');image.thumbnail((480,672));board.paste(image,(i*500+10,45))
|
||||
board.save(P/'curtain-comparison.png')
|
||||
|
||||
# A focused junction comparison at master crop.
|
||||
detail=Image.new('RGB',(1200,620),'#11161c');draw=ImageDraw.Draw(detail)
|
||||
for i,(label,path) in enumerate([('Without foreground',P/'without-curtain-2000.png'),('Cloud and curtain over backing',P/'composed-preview-2000.png')]):
|
||||
draw.text((i*600+12,12),label,fill='#f4ead4')
|
||||
image=Image.open(path).convert('RGB').crop((0,1780,900,2800));image.thumbnail((575,545));detail.paste(image,(i*600+12,52))
|
||||
detail.save(P/'curtain-junction-detail.png')
|
||||
|
||||
frame_alpha=np.asarray(Image.open(P/'border-backing-preview.png').convert('RGBA').getchannel('A'))
|
||||
foreground_frame_overlap=int(np.count_nonzero((fa>0)&(frame_alpha>0)))
|
||||
report={
|
||||
'status':'passed',
|
||||
'scope':'v05 Normal-only prototype — pearl cloud and suspended tent textile',
|
||||
'canvas':[2000,2800],
|
||||
'sourceArtwork':{'path':str(ART.relative_to(REPO)),'sha256':art_hash,'unchanged':sha(ART)==art_hash},
|
||||
'template':{
|
||||
'governingIdea':'Nameplate unchanged; a suspended linen field tucks behind the pillar of cloud and ties to the right frame',
|
||||
'titleBackingPath':title_path,
|
||||
'verseBackingPath':verse_path,
|
||||
'compositionOrder':['base-art','frame-and-backings','attached-curtain-foreground','deterministic-text'],
|
||||
'curtainContour':'foreground-curtain.svg',
|
||||
'cloudUpperContour':cloud_top,
|
||||
'cloudLowerContour':cloud_low,
|
||||
'curtainAlphaBounds':list(Image.fromarray(fa).getbbox()),
|
||||
'curtainFrameOrBackingOverlapPixels':foreground_frame_overlap,
|
||||
},
|
||||
'typography':{
|
||||
'fontManifestSHA256':sha(FONTS/'manifest.json'),
|
||||
'checks':checks,
|
||||
'verseInkUnion':union,
|
||||
'upperFlourishToVerseInk':above,
|
||||
'verseInkToReferenceLineBox':below,
|
||||
'centeringDifference':abs(above-below),
|
||||
'lineBaselineGap':108,
|
||||
},
|
||||
'structuralChecks':{
|
||||
'curtainTextIntersectionPixels':int(np.count_nonzero((ta>0)&(fa>0))),
|
||||
'glyphPixelsOutsideOpaqueBacking':0,
|
||||
'artworkSHA256Unchanged':True,
|
||||
'fontGlyphCoverage':'passed',
|
||||
'fontManifestHashes':'passed',
|
||||
},
|
||||
'notPerformed':['Borderless/Textless/Boundless composition','Production finish masks','Production text masks','Harness fixture installation','GPU moving-light validation','Final card approval'],
|
||||
'staticReview':'Provisional Normal-only prototype; static inspection at card size and enlarged junctions; pending user review',
|
||||
'edgeRendering':{'geometry':'source-traced cubic Bezier paths','leadWidthMasterPixels':8,'renderScale':2,'downsample':'premultiplied RGBA LANCZOS','blur':False},
|
||||
'foregroundAdaptation':'Liked nameplate geometry and suspended linen layout retained. New v05 pearl/cool cloud uses authored wave-like cubic contours; lower closure stays above source ground occlusion. The continuous entrance post is reused from the baked source and never redrawn.'
|
||||
}
|
||||
(P/'preview-validation.json').write_text(json.dumps(report,indent=2)+'\n')
|
||||
assert sha(ART)==art_hash
|
||||
print(json.dumps({'status':'passed','preview':str((P/'composed-preview-1000.png').relative_to(REPO)),'curtainOverlapPixels':foreground_frame_overlap,'spacing':[above,below]},indent=2))
|
||||
|
||||
# Source/composed review and comparison to the carried-forward layout.
|
||||
label_font=ImageFont.truetype(str(FONTS/'P052-Bold.otf'),26)
|
||||
previous=REPO/'in-progress/cards/BP-001-moses/revisions/v03/review/layout-preview-astra-v5'
|
||||
for name,items in [
|
||||
('source-composed-comparison',[('Selected v05 source',ART),('v05 Normal prototype',P/'composed-preview-1000.png')]),
|
||||
('layout-lineage-comparison',[('Prior v03 layout / prior art',previous/'composed-preview-1000.png'),('v05 layout / current art',P/'composed-preview-1000.png')]),
|
||||
]:
|
||||
board=Image.new('RGB',(1500,1100),'#11161c');d=ImageDraw.Draw(board)
|
||||
for i,(label,path) in enumerate(items):
|
||||
d.text((i*750+24,18),label,font=label_font,fill='#f4ead4')
|
||||
im=Image.open(path).convert('RGB');im.thumbnail((730,1022));board.paste(im,(i*750+10,63))
|
||||
board.save(P/(name+'.png'))
|
||||
master=Image.open(P/'composed-preview-2000.png').convert('RGB')
|
||||
detail=Image.new('RGB',(1500,1300),'#11161c');d=ImageDraw.Draw(detail)
|
||||
for label,box,origin,size in [
|
||||
('Pearl cloud / nameplate',(620,0,1140,480),(22,65),(700,550)),
|
||||
('Cloud / textile / existing post',(295,2020,800,2660),(795,65),(660,690)),
|
||||
]:
|
||||
d.text((origin[0],20),label,font=label_font,fill='#f4ead4')
|
||||
im=master.crop(box);im=im.resize((round(im.width*1.15),round(im.height*1.15)),Image.Resampling.LANCZOS);im.thumbnail(size);detail.paste(im,origin)
|
||||
im=master.crop((290,2070,2000,2800));im.thumbnail((1450,520));detail.paste(im,(25,775))
|
||||
detail.save(P/'cloud-textile-junction-details.png')
|
||||
# Preserve approved typography and nameplate geometry without claiming the
|
||||
# changed source/cloud pixels are identical to old artwork.
|
||||
assert sha(ART)==json.loads((REV/'card.json').read_text())['artSHA256']
|
||||
assert np.array_equal(np.asarray(Image.open(P/'text-preview.png')),np.asarray(Image.open(previous/'text-preview.png')))
|
||||
report['structuralChecks']['textPixelsIdenticalToCarriedForwardLayout']=True
|
||||
report['structuralChecks']['sourceMatchesV05CardSHA256']=True
|
||||
report['structuralChecks']['secondPostDrawn']=False
|
||||
report['template']['layoutReference']=str(previous.relative_to(REPO))
|
||||
report['template']['postPolicy']='Use the continuous entrance post already baked into v05 artwork; no new post layer'
|
||||
report['template']['leftTextileAnchor']=[340,2106]
|
||||
report['edgeRendering']['alphaEdge']='Premultiplied before downsampling; no transparent-RGB halo'
|
||||
(P/'preview-validation.json').write_text(json.dumps(report,indent=2)+'\n')
|
||||
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 7.6 MiB |
|
After Width: | Height: | Size: 692 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 678 KiB |
|
After Width: | Height: | Size: 456 KiB |
@@ -0,0 +1 @@
|
||||
<fontconfig><dir>/home/dkzver/docker/sanctification/fonts</dir><cachedir>/home/dkzver/docker/sanctification/in-progress/cards/BP-001-moses/revisions/v05/review/normal-prototype-v1/font-cache</cachedir></fontconfig>
|
||||
|
After Width: | Height: | Size: 2.5 MiB |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><defs>
|
||||
<linearGradient id="paper" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fbf3df" /><stop offset=".5" stop-color="#f4ead4" /><stop offset="1" stop-color="#e8d4b0" /></linearGradient>
|
||||
<linearGradient id="gold" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f0d47d" /><stop offset=".48" stop-color="#bd8b31" /><stop offset="1" stop-color="#71501e" /></linearGradient>
|
||||
<pattern id="weave" width="44" height="44" patternUnits="userSpaceOnUse"><path d="M0 11H44 M0 33H44 M11 0V44 M33 0V44" stroke="#9d783d" stroke-width="1" opacity=".045" /><path d="M22 7l7 15-7 15-7-15Z" fill="none" stroke="#806438" stroke-width="1" opacity=".07" /></pattern>
|
||||
<pattern id="lapis-band" width="34" height="34" patternUnits="userSpaceOnUse"><path d="M17 2L32 17 17 32 2 17Z" fill="#173e59" stroke="#d7b35a" stroke-width="3" /><circle cx="17" cy="17" r="4" fill="url(#gold)" /></pattern>
|
||||
<clipPath id="curtain-clip"><path d="M-10-10 H770 C734 204 694 395 650 585 C610 770 566 972 522 1170 C474 1388 426 1604 378 1818 C338 2000 302 2190 286 2380 C276 2510 304 2640 412 2810 H-10Z" /></clipPath>
|
||||
<linearGradient id="linen" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#f7edd4" /><stop offset=".55" stop-color="#f2e7cd" /><stop offset="1" stop-color="#e8d7b6" /></linearGradient><clipPath id="cloud-top"><path d="M1045 0 C1048 62 1012 102 971 105 C942 108 910 123 913 146 C916 166 952 176 967 209 C991 248 983 287 967 310 C956 325 971 347 959 371 C946 397 900 400 876 432 L780 490 H0 V0Z" /></clipPath><clipPath id="cloud-low"><path d="M0 400H370 V1780H765 C765 1920 738 2010 724 2080 C720 2160 692 2180 704 2230 C711 2265 694 2294 677 2322 C664 2345 672 2385 695 2410 C721 2440 722 2498 678 2538 C642 2543 622 2480 586 2480 C535 2480 536 2458 500 2444 C462 2421 438 2445 409 2424 C384 2408 370 2375 370 2340 L369 2640 Q286 2659 205 2635 Q101 2583 0 2612Z" /></clipPath><clipPath id="title-edge-zone"><path d="M700 62 H1900 Q1940 62 1940 102 V342 H700Z" /></clipPath></defs><g id="presence-foreground"><g clip-path="url(#cloud-top)"><image xlink:href="../../source/art-master.png" width="2000" height="2800" /></g><g clip-path="url(#cloud-low)"><image xlink:href="../../source/art-master.png" width="2000" height="2800" /></g><g fill="none" stroke="#2b2116" stroke-width="8" stroke-linejoin="round" stroke-linecap="round"><path d="M1045 0 C1048 62 1012 102 971 105 C942 108 910 123 913 146 C916 166 952 176 967 209 C991 248 983 287 967 310 C956 325 971 347 959 371 C946 397 900 400 876 432" clip-path="url(#title-edge-zone)" /><path d="M724 2080 C720 2160 692 2180 704 2230 C711 2265 694 2294 677 2322 C664 2345 672 2385 695 2410 C721 2440 722 2498 678 2538 C642 2543 622 2480 586 2480 C535 2480 536 2458 500 2444 C462 2421 438 2445 409 2424 C384 2408 370 2375 370 2340" /></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.5 MiB |
@@ -0,0 +1,193 @@
|
||||
{
|
||||
"status": "passed",
|
||||
"scope": "v05 Normal-only prototype \u2014 pearl cloud and suspended tent textile",
|
||||
"canvas": [
|
||||
2000,
|
||||
2800
|
||||
],
|
||||
"sourceArtwork": {
|
||||
"path": "in-progress/cards/BP-001-moses/revisions/v05/source/art-master.png",
|
||||
"sha256": "372fb8f9f5b398c943364a620c970fd81e000f3e0613f71eff84e087f6396bee",
|
||||
"unchanged": true
|
||||
},
|
||||
"template": {
|
||||
"governingIdea": "Nameplate unchanged; a suspended linen field tucks behind the pillar of cloud and ties to the right frame",
|
||||
"titleBackingPath": "M700 62 H1900 Q1940 62 1940 102 V342 H700Z",
|
||||
"verseBackingPath": "M340 2106 C445 2114 550 2118 655 2140 C960 2230 1435 2260 1960 2095 L1960 2720 C1535 2782 1045 2728 580 2750 C488 2755 437 2684 453 2592 C462 2506 520 2470 574 2404 C625 2341 641 2230 655 2140 L340 2106Z",
|
||||
"compositionOrder": [
|
||||
"base-art",
|
||||
"frame-and-backings",
|
||||
"attached-curtain-foreground",
|
||||
"deterministic-text"
|
||||
],
|
||||
"curtainContour": "foreground-curtain.svg",
|
||||
"cloudUpperContour": "M1045 0 C1048 62 1012 102 971 105 C942 108 910 123 913 146 C916 166 952 176 967 209 C991 248 983 287 967 310 C956 325 971 347 959 371 C946 397 900 400 876 432 L780 490 H0 V0Z",
|
||||
"cloudLowerContour": "M0 400H370 V1780H765 C765 1920 738 2010 724 2080 C720 2160 692 2180 704 2230 C711 2265 694 2294 677 2322 C664 2345 672 2385 695 2410 C721 2440 722 2498 678 2538 C642 2543 622 2480 586 2480 C535 2480 536 2458 500 2444 C462 2421 438 2445 409 2424 C384 2408 370 2375 370 2340 L369 2640 Q286 2659 205 2635 Q101 2583 0 2612Z",
|
||||
"curtainAlphaBounds": [
|
||||
0,
|
||||
0,
|
||||
1048,
|
||||
2651
|
||||
],
|
||||
"curtainFrameOrBackingOverlapPixels": 216058,
|
||||
"layoutReference": "in-progress/cards/BP-001-moses/revisions/v03/review/layout-preview-astra-v5",
|
||||
"postPolicy": "Use the continuous entrance post already baked into v05 artwork; no new post layer",
|
||||
"leftTextileAnchor": [
|
||||
340,
|
||||
2106
|
||||
]
|
||||
},
|
||||
"typography": {
|
||||
"fontManifestSHA256": "ef68ee6393981d54ca068a950f21c53b374d20e6e46c21c788f36380d3aae178",
|
||||
"checks": [
|
||||
{
|
||||
"id": "title",
|
||||
"text": "MOSES",
|
||||
"baseline": 244,
|
||||
"font": "P052",
|
||||
"weight": 700,
|
||||
"size": 142,
|
||||
"inkBounds": [
|
||||
1202,
|
||||
145,
|
||||
1711,
|
||||
247
|
||||
],
|
||||
"safeBounds": [
|
||||
1050,
|
||||
90,
|
||||
1905,
|
||||
330
|
||||
],
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"foregroundIntersectionPixels": 0
|
||||
},
|
||||
{
|
||||
"id": "verse-0",
|
||||
"text": "And there has not arisen a",
|
||||
"baseline": 2360,
|
||||
"font": "Sanctification P052",
|
||||
"weight": 500,
|
||||
"size": 74,
|
||||
"inkBounds": [
|
||||
890,
|
||||
2306,
|
||||
1749,
|
||||
2362
|
||||
],
|
||||
"safeBounds": [
|
||||
730,
|
||||
2275,
|
||||
1900,
|
||||
2730
|
||||
],
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"foregroundIntersectionPixels": 0
|
||||
},
|
||||
{
|
||||
"id": "verse-1",
|
||||
"text": "prophet since in Israel like Moses,",
|
||||
"baseline": 2468,
|
||||
"font": "Sanctification P052",
|
||||
"weight": 500,
|
||||
"size": 74,
|
||||
"inkBounds": [
|
||||
769,
|
||||
2414,
|
||||
1869,
|
||||
2490
|
||||
],
|
||||
"safeBounds": [
|
||||
730,
|
||||
2275,
|
||||
1900,
|
||||
2730
|
||||
],
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"foregroundIntersectionPixels": 0
|
||||
},
|
||||
{
|
||||
"id": "verse-2",
|
||||
"text": "whom the LORD knew face to face",
|
||||
"baseline": 2576,
|
||||
"font": "Sanctification P052",
|
||||
"weight": 500,
|
||||
"size": 74,
|
||||
"inkBounds": [
|
||||
752,
|
||||
2521,
|
||||
1886,
|
||||
2578
|
||||
],
|
||||
"safeBounds": [
|
||||
730,
|
||||
2275,
|
||||
1900,
|
||||
2730
|
||||
],
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"foregroundIntersectionPixels": 0
|
||||
},
|
||||
{
|
||||
"id": "reference",
|
||||
"text": "Deuteronomy 34:10 \u2022 ESV",
|
||||
"baseline": 2690,
|
||||
"font": "Sanctification P052",
|
||||
"weight": 500,
|
||||
"size": 57,
|
||||
"inkBounds": [
|
||||
991,
|
||||
2649,
|
||||
1649,
|
||||
2707
|
||||
],
|
||||
"safeBounds": [
|
||||
730,
|
||||
2275,
|
||||
1900,
|
||||
2730
|
||||
],
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"foregroundIntersectionPixels": 0
|
||||
}
|
||||
],
|
||||
"verseInkUnion": [
|
||||
752,
|
||||
2306,
|
||||
1886,
|
||||
2578
|
||||
],
|
||||
"upperFlourishToVerseInk": 66,
|
||||
"verseInkToReferenceLineBox": 71,
|
||||
"centeringDifference": 5,
|
||||
"lineBaselineGap": 108
|
||||
},
|
||||
"structuralChecks": {
|
||||
"curtainTextIntersectionPixels": 0,
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"artworkSHA256Unchanged": true,
|
||||
"fontGlyphCoverage": "passed",
|
||||
"fontManifestHashes": "passed",
|
||||
"textPixelsIdenticalToCarriedForwardLayout": true,
|
||||
"sourceMatchesV05CardSHA256": true,
|
||||
"secondPostDrawn": false
|
||||
},
|
||||
"notPerformed": [
|
||||
"Borderless/Textless/Boundless composition",
|
||||
"Production finish masks",
|
||||
"Production text masks",
|
||||
"Harness fixture installation",
|
||||
"GPU moving-light validation",
|
||||
"Final card approval"
|
||||
],
|
||||
"staticReview": "Provisional Normal-only prototype; static inspection at card size and enlarged junctions; pending user review",
|
||||
"edgeRendering": {
|
||||
"geometry": "source-traced cubic Bezier paths",
|
||||
"leadWidthMasterPixels": 8,
|
||||
"renderScale": 2,
|
||||
"downsample": "premultiplied RGBA LANCZOS",
|
||||
"blur": false,
|
||||
"alphaEdge": "Premultiplied before downsampling; no transparent-RGB halo"
|
||||
},
|
||||
"foregroundAdaptation": "Liked nameplate geometry and suspended linen layout retained. New v05 pearl/cool cloud uses authored wave-like cubic contours; lower closure stays above source ground occlusion. The continuous entrance post is reused from the baked source and never redrawn."
|
||||
}
|
||||
|
After Width: | Height: | Size: 46 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><text id="reference" x="1320" y="2690" font-family="Sanctification P052" font-weight="500" font-size="57" text-anchor="middle" fill="#263f50">Deuteronomy 34:10 • ESV</text></svg>
|
||||
|
After Width: | Height: | Size: 314 B |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 139 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><text id="title" x="1458" y="244" font-family="P052" font-weight="700" font-size="142" text-anchor="middle" fill="#263f50">MOSES</text><text id="verse-0" x="1320" y="2360" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">And there has not arisen a</text><text id="verse-1" x="1320" y="2468" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">prophet since in Israel like Moses,</text><text id="verse-2" x="1320" y="2576" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">whom the LORD knew face to face</text><text id="reference" x="1320" y="2690" font-family="Sanctification P052" font-weight="500" font-size="57" text-anchor="middle" fill="#263f50">Deuteronomy 34:10 • ESV</text></svg>
|
||||
|
After Width: | Height: | Size: 982 B |
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><text id="title" x="1458" y="244" font-family="P052" font-weight="700" font-size="142" text-anchor="middle" fill="#263f50">MOSES</text></svg>
|
||||
|
After Width: | Height: | Size: 275 B |
|
After Width: | Height: | Size: 48 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><text id="verse-0" x="1320" y="2360" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">And there has not arisen a</text></svg>
|
||||
|
After Width: | Height: | Size: 313 B |
|
After Width: | Height: | Size: 56 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><text id="verse-1" x="1320" y="2468" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">prophet since in Israel like Moses,</text></svg>
|
||||
|
After Width: | Height: | Size: 322 B |
|
After Width: | Height: | Size: 56 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><text id="verse-2" x="1320" y="2576" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">whom the LORD knew face to face</text></svg>
|
||||
|
After Width: | Height: | Size: 318 B |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 7.4 MiB |
|
After Width: | Height: | Size: 672 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><defs>
|
||||
<linearGradient id="paper" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fbf3df" /><stop offset=".5" stop-color="#f4ead4" /><stop offset="1" stop-color="#e8d4b0" /></linearGradient>
|
||||
<linearGradient id="gold" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f0d47d" /><stop offset=".48" stop-color="#bd8b31" /><stop offset="1" stop-color="#71501e" /></linearGradient>
|
||||
<pattern id="weave" width="44" height="44" patternUnits="userSpaceOnUse"><path d="M0 11H44 M0 33H44 M11 0V44 M33 0V44" stroke="#9d783d" stroke-width="1" opacity=".045" /><path d="M22 7l7 15-7 15-7-15Z" fill="none" stroke="#806438" stroke-width="1" opacity=".07" /></pattern>
|
||||
<pattern id="lapis-band" width="34" height="34" patternUnits="userSpaceOnUse"><path d="M17 2L32 17 17 32 2 17Z" fill="#173e59" stroke="#d7b35a" stroke-width="3" /><circle cx="17" cy="17" r="4" fill="url(#gold)" /></pattern>
|
||||
<clipPath id="curtain-clip"><path d="M-10-10 H770 C734 204 694 395 650 585 C610 770 566 972 522 1170 C474 1388 426 1604 378 1818 C338 2000 302 2190 286 2380 C276 2510 304 2640 412 2810 H-10Z" /></clipPath>
|
||||
<linearGradient id="linen" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#f7edd4" /><stop offset=".55" stop-color="#f2e7cd" /><stop offset="1" stop-color="#e8d7b6" /></linearGradient><clipPath id="cloud-top"><path d="M1045 0 C1048 62 1012 102 971 105 C942 108 910 123 913 146 C916 166 952 176 967 209 C991 248 983 287 967 310 C956 325 971 347 959 371 C946 397 900 400 876 432 L780 490 H0 V0Z" /></clipPath><clipPath id="cloud-low"><path d="M0 400H370 V1780H765 C765 1920 738 2010 724 2080 C720 2160 692 2180 704 2230 C711 2265 694 2294 677 2322 C664 2345 672 2385 695 2410 C721 2440 722 2498 678 2538 C642 2543 622 2480 586 2480 C535 2480 536 2458 500 2444 C462 2421 438 2445 409 2424 C384 2408 370 2375 370 2340 L369 2640 Q286 2659 205 2635 Q101 2583 0 2612Z" /></clipPath><clipPath id="title-edge-zone"><path d="M700 62 H1900 Q1940 62 1940 102 V342 H700Z" /></clipPath></defs><image xlink:href="../../source/art-master.png" width="2000" height="2800" /><g id="legendary-frame"><rect x="19" y="19" width="1962" height="2762" rx="28" fill="none" stroke="#251c15" stroke-width="21" /><rect x="23" y="23" width="1954" height="2754" rx="23" fill="none" stroke="url(#gold)" stroke-width="11" /><rect x="43" y="43" width="1914" height="2714" rx="15" fill="none" stroke="#e2bf6c" stroke-width="3" /></g><g id="title-backing"><path d="M700 62 H1900 Q1940 62 1940 102 V342 H700Z" fill="url(#paper)" stroke="#62421e" stroke-width="9" /><path d="M700 62 H1900 Q1940 62 1940 102 V342 H700Z" fill="url(#weave)" stroke="url(#gold)" stroke-width="4" /><path d="M710 82 H1898 Q1920 82 1920 104 V320 H710" fill="none" stroke="#bb9040" stroke-width="3" /><path d="M1070 301H1870" stroke="#c5a257" stroke-width="2" /></g><g id="verse-backing"><path d="M340 2106 C445 2114 550 2118 655 2140 C960 2230 1435 2260 1960 2095 L1960 2720 C1535 2782 1045 2728 580 2750 C488 2755 437 2684 453 2592 C462 2506 520 2470 574 2404 C625 2341 641 2230 655 2140 L340 2106Z" transform="translate(3 6)" fill="#211b15" opacity=".6" /><path d="M340 2106 C445 2114 550 2118 655 2140 C960 2230 1435 2260 1960 2095 L1960 2720 C1535 2782 1045 2728 580 2750 C488 2755 437 2684 453 2592 C462 2506 520 2470 574 2404 C625 2341 641 2230 655 2140 L340 2106Z" fill="url(#linen)" stroke="#79552b" stroke-width="4" /><path d="M340 2106 C445 2114 550 2118 655 2140 C960 2230 1435 2260 1960 2095 L1960 2720 C1535 2782 1045 2728 580 2750 C488 2755 437 2684 453 2592 C462 2506 520 2470 574 2404 C625 2341 641 2230 655 2140 L340 2106Z" fill="url(#weave)" /><path d="M690 2170 C1000 2250 1470 2250 1925 2138 M490 2677 C516 2726 551 2728 591 2725 C1060 2702 1530 2757 1925 2696" fill="none" stroke="#a17533" stroke-width="3" /><path d="M688 2182 C1000 2262 1470 2262 1923 2150 M489 2665 C516 2714 551 2716 591 2713 C1060 2690 1530 2745 1925 2684" fill="none" stroke="#8c3d3c" stroke-width="2" opacity=".65" /><path d="M1925 2140V2695" fill="none" stroke="#b7924a" stroke-width="3" stroke-dasharray="7 9" /></g><g fill="none" stroke-linecap="round"><path d="M1940 2119 C1950 2106 1964 2105 1977 2109" stroke="#362719" stroke-width="13" /><path d="M1940 2119 C1950 2106 1964 2105 1977 2109" stroke="#d6b260" stroke-width="7" /><path d="M1940 2709 C1950 2696 1964 2695 1977 2699" stroke="#362719" stroke-width="13" /><path d="M1940 2709 C1950 2696 1964 2695 1977 2699" stroke="#d6b260" stroke-width="7" /></g><text id="title" x="1458" y="244" font-family="P052" font-weight="700" font-size="142" text-anchor="middle" fill="#263f50">MOSES</text><text id="verse-0" x="1320" y="2360" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">And there has not arisen a</text><text id="verse-1" x="1320" y="2468" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">prophet since in Israel like Moses,</text><text id="verse-2" x="1320" y="2576" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">whom the LORD knew face to face</text><text id="reference" x="1320" y="2690" font-family="Sanctification P052" font-weight="500" font-size="57" text-anchor="middle" fill="#263f50">Deuteronomy 34:10 • ESV</text></svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
@@ -0,0 +1,24 @@
|
||||
# Moses v05 — Normal prototype v2
|
||||
|
||||
Targeted refinement of [v1](../normal-prototype-v1/README.md), which remains intact.
|
||||
|
||||
- [Full preview](composed-preview-1000.png)
|
||||
- [500 px preview](composed-preview-500.png)
|
||||
- [V1 / V2 full comparison](v1-v2-full-comparison.png)
|
||||
- [V1 / V2 lower junction comparison](v1-v2-junction-comparison.png)
|
||||
- [Enlarged cloud/textile details](cloud-textile-junction-details.png)
|
||||
- [Source/composed comparison](source-composed-comparison.png)
|
||||
- [Builder](build-preview.py)
|
||||
- [Validation](preview-validation.json)
|
||||
|
||||
The foreground now follows the actual curtain hem through its full lower edge, hiding the frame behind the fabric. The old shortened extraction exposed a gold frame rule inside the hem. A dedicated check verifies zero exposed frame pixels within the affected cloth region.
|
||||
|
||||
The lowest cloud overflow is now a broader rounded roll. Continuous cubic tangents replace the previous pointed closure; its pearl and cool glass interior remains registered to the source. No blur, new post or source repaint is introduced.
|
||||
|
||||
All text and backing pixels match v1 exactly. The upper composition, including the liked nameplate and cloud crown, is preserved. Checks cover exact text, font hashes/glyph coverage, safe glyph bounds, opaque backing, zero foreground/text intersection, source SHA256 and targeted v1/v2 preservation. Rendering uses 2× resolution and premultiplied-alpha downsampling.
|
||||
|
||||
Source: ../../source/art-master.png, unchanged SHA256 372fb8f9f5b398c943364a620c970fd81e000f3e0613f71eff84e087f6396bee. The continuous entrance post is already in this source and is not drawn again.
|
||||
|
||||
Rebuild: python3 in-progress/cards/BP-001-moses/revisions/v05/review/normal-prototype-v2/build-preview.py. Requires Inkscape, Fontconfig, Pillow and NumPy.
|
||||
|
||||
Normal review prototype only. No other printing types, finish masks, production exports, harness fixture, acceptance or promotion.
|
||||
|
After Width: | Height: | Size: 274 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><defs>
|
||||
<linearGradient id="paper" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fbf3df"/><stop offset=".5" stop-color="#f4ead4"/><stop offset="1" stop-color="#e8d4b0"/></linearGradient>
|
||||
<linearGradient id="gold" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f0d47d"/><stop offset=".48" stop-color="#bd8b31"/><stop offset="1" stop-color="#71501e"/></linearGradient>
|
||||
<pattern id="weave" width="44" height="44" patternUnits="userSpaceOnUse"><path d="M0 11H44 M0 33H44 M11 0V44 M33 0V44" stroke="#9d783d" stroke-width="1" opacity=".045"/><path d="M22 7l7 15-7 15-7-15Z" fill="none" stroke="#806438" stroke-width="1" opacity=".07"/></pattern>
|
||||
<pattern id="lapis-band" width="34" height="34" patternUnits="userSpaceOnUse"><path d="M17 2L32 17 17 32 2 17Z" fill="#173e59" stroke="#d7b35a" stroke-width="3"/><circle cx="17" cy="17" r="4" fill="url(#gold)"/></pattern>
|
||||
<clipPath id="curtain-clip"><path d="M-10-10 H770 C734 204 694 395 650 585 C610 770 566 972 522 1170 C474 1388 426 1604 378 1818 C338 2000 302 2190 286 2380 C276 2510 304 2640 412 2810 H-10Z"/></clipPath>
|
||||
<linearGradient id="linen" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#f7edd4"/><stop offset=".55" stop-color="#f2e7cd"/><stop offset="1" stop-color="#e8d7b6"/></linearGradient><clipPath id="cloud-top"><path d="M1045 0 C1048 62 1012 102 971 105 C942 108 910 123 913 146 C916 166 952 176 967 209 C991 248 983 287 967 310 C956 325 971 347 959 371 C946 397 900 400 876 432 L780 490 H0 V0Z"/></clipPath><clipPath id="cloud-low"><path d="M0 400H370 V1780H765 C765 1920 738 2010 724 2080 C720 2160 692 2180 704 2230 C711 2265 694 2294 677 2322 C664 2345 672 2385 695 2410 C721 2440 722 2478 701 2501 C680 2524 647 2527 619 2509 C594 2492 566 2496 537 2485 C510 2474 499 2459 471 2455 C436 2450 413 2442 393 2423 C377 2408 370 2375 370 2340 L369 2650 C318 2651 271 2644 224 2641 C173 2638 122 2650 76 2646 C45 2640 24 2634 0 2640Z"/></clipPath><clipPath id="title-edge-zone"><path d="M700 62 H1900 Q1940 62 1940 102 V342 H700Z"/></clipPath></defs><g id="legendary-frame"><rect x="19" y="19" width="1962" height="2762" rx="28" fill="none" stroke="#251c15" stroke-width="21"/><rect x="23" y="23" width="1954" height="2754" rx="23" fill="none" stroke="url(#gold)" stroke-width="11"/><rect x="43" y="43" width="1914" height="2714" rx="15" fill="none" stroke="#e2bf6c" stroke-width="3"/></g><g id="title-backing"><path d="M700 62 H1900 Q1940 62 1940 102 V342 H700Z" fill="url(#paper)" stroke="#62421e" stroke-width="9"/><path d="M700 62 H1900 Q1940 62 1940 102 V342 H700Z" fill="url(#weave)" stroke="url(#gold)" stroke-width="4"/><path d="M710 82 H1898 Q1920 82 1920 104 V320 H710" fill="none" stroke="#bb9040" stroke-width="3"/><path d="M1070 301H1870" stroke="#c5a257" stroke-width="2"/></g><g id="verse-backing"><path d="M340 2106 C445 2114 550 2118 655 2140 C960 2230 1435 2260 1960 2095 L1960 2720 C1535 2782 1045 2728 580 2750 C488 2755 437 2684 453 2592 C462 2506 520 2470 574 2404 C625 2341 641 2230 655 2140 L340 2106Z" transform="translate(3 6)" fill="#211b15" opacity=".6"/><path d="M340 2106 C445 2114 550 2118 655 2140 C960 2230 1435 2260 1960 2095 L1960 2720 C1535 2782 1045 2728 580 2750 C488 2755 437 2684 453 2592 C462 2506 520 2470 574 2404 C625 2341 641 2230 655 2140 L340 2106Z" fill="url(#linen)" stroke="#79552b" stroke-width="4"/><path d="M340 2106 C445 2114 550 2118 655 2140 C960 2230 1435 2260 1960 2095 L1960 2720 C1535 2782 1045 2728 580 2750 C488 2755 437 2684 453 2592 C462 2506 520 2470 574 2404 C625 2341 641 2230 655 2140 L340 2106Z" fill="url(#weave)"/><path d="M690 2170 C1000 2250 1470 2250 1925 2138 M490 2677 C516 2726 551 2728 591 2725 C1060 2702 1530 2757 1925 2696" fill="none" stroke="#a17533" stroke-width="3"/><path d="M688 2182 C1000 2262 1470 2262 1923 2150 M489 2665 C516 2714 551 2716 591 2713 C1060 2690 1530 2745 1925 2684" fill="none" stroke="#8c3d3c" stroke-width="2" opacity=".65"/><path d="M1925 2140V2695" fill="none" stroke="#b7924a" stroke-width="3" stroke-dasharray="7 9"/></g><g fill="none" stroke-linecap="round"><path d="M1940 2119 C1950 2106 1964 2105 1977 2109" stroke="#362719" stroke-width="13"/><path d="M1940 2119 C1950 2106 1964 2105 1977 2109" stroke="#d6b260" stroke-width="7"/><path d="M1940 2709 C1950 2696 1964 2695 1977 2699" stroke="#362719" stroke-width="13"/><path d="M1940 2709 C1950 2696 1964 2695 1977 2699" stroke="#d6b260" stroke-width="7"/></g></svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Provisional BP-001 Moses Legendary composition; no production assembly."""
|
||||
from pathlib import Path
|
||||
import hashlib,json,os,subprocess
|
||||
from xml.sax.saxutils import escape
|
||||
import numpy as np
|
||||
from PIL import Image,ImageDraw,ImageFont
|
||||
|
||||
P=Path(__file__).resolve().parent
|
||||
REV=P.parents[1]
|
||||
REPO=next(p for p in P.parents if (p/'docs/card-layer-pipeline.md').is_file())
|
||||
ART=REV/'source/art-master.png'
|
||||
FONTS=REPO/'fonts'
|
||||
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
art_hash=sha(ART)
|
||||
font_manifest=json.loads((FONTS/'manifest.json').read_text())
|
||||
font_files={'title':'P052-Bold.otf','verse':'SanctificationP052-Medium.otf','reference':'SanctificationP052-Medium.otf'}
|
||||
for name in set(font_files.values()):
|
||||
assert sha(FONTS/name)==font_manifest['files'][name]['sha256']
|
||||
(P/'font-cache').mkdir(exist_ok=True)
|
||||
(P/'fonts.conf').write_text(f'<fontconfig><dir>{FONTS}</dir><cachedir>{P/"font-cache"}</cachedir></fontconfig>')
|
||||
env=dict(os.environ,FONTCONFIG_FILE=str(P/'fonts.conf'))
|
||||
def ink(*args):return subprocess.check_output(['inkscape',*map(str,args)],env=env,text=True,stderr=subprocess.PIPE).strip()
|
||||
def render(src,dst):
|
||||
# Supersample only geometry-heavy composite/foreground outputs; text measurements
|
||||
# remain native. LANCZOS uses premultiplied RGBA via Pillow's RGBa path.
|
||||
factor=2 if src.stem in {'foreground-curtain','composed-preview','without-curtain'} else 1
|
||||
ink(src,'--export-type=png',f'--export-filename={dst}',f'--export-width={2000*factor}',f'--export-height={2800*factor}')
|
||||
if factor>1:
|
||||
im=Image.open(dst).convert('RGBA')
|
||||
im.convert('RGBa').resize((2000,2800),Image.Resampling.LANCZOS).convert('RGBA').save(dst)
|
||||
|
||||
def resolve(family,style):return Path(subprocess.check_output(['fc-match','-f','%{file}',f'{family}:style={style}'],env=env,text=True).strip()).resolve()
|
||||
assert resolve('P052','Bold')==(FONTS/'P052-Bold.otf').resolve()
|
||||
assert resolve('Sanctification P052','Medium')==(FONTS/'SanctificationP052-Medium.otf').resolve()
|
||||
|
||||
head='<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800">'
|
||||
defs='''<defs>
|
||||
<linearGradient id="paper" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fbf3df"/><stop offset=".5" stop-color="#f4ead4"/><stop offset="1" stop-color="#e8d4b0"/></linearGradient>
|
||||
<linearGradient id="gold" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f0d47d"/><stop offset=".48" stop-color="#bd8b31"/><stop offset="1" stop-color="#71501e"/></linearGradient>
|
||||
<pattern id="weave" width="44" height="44" patternUnits="userSpaceOnUse"><path d="M0 11H44 M0 33H44 M11 0V44 M33 0V44" stroke="#9d783d" stroke-width="1" opacity=".045"/><path d="M22 7l7 15-7 15-7-15Z" fill="none" stroke="#806438" stroke-width="1" opacity=".07"/></pattern>
|
||||
<pattern id="lapis-band" width="34" height="34" patternUnits="userSpaceOnUse"><path d="M17 2L32 17 17 32 2 17Z" fill="#173e59" stroke="#d7b35a" stroke-width="3"/><circle cx="17" cy="17" r="4" fill="url(#gold)"/></pattern>
|
||||
<clipPath id="curtain-clip"><path d="M-10-10 H770 C734 204 694 395 650 585 C610 770 566 972 522 1170 C474 1388 426 1604 378 1818 C338 2000 302 2190 286 2380 C276 2510 304 2640 412 2810 H-10Z"/></clipPath>
|
||||
</defs>'''
|
||||
base='<image xlink:href="../../source/art-master.png" width="2000" height="2800"/>'
|
||||
frame='''<g id="legendary-frame">
|
||||
<rect x="14" y="14" width="1972" height="2772" rx="34" fill="none" stroke="#2c2115" stroke-width="28"/>
|
||||
<rect x="25" y="25" width="1950" height="2750" rx="27" fill="none" stroke="url(#gold)" stroke-width="14"/>
|
||||
<rect x="43" y="43" width="1914" height="2714" rx="20" fill="none" stroke="#173e59" stroke-width="10"/>
|
||||
<rect x="55" y="55" width="1890" height="2690" rx="15" fill="none" stroke="#d7b35a" stroke-width="4"/>
|
||||
<path d="M68 260V68H260 M1740 68H1932V260 M68 2540V2732H260 M1740 2732H1932V2540" fill="none" stroke="#9b6d26" stroke-width="6"/>
|
||||
<g fill="url(#gold)" stroke="#533810" stroke-width="3"><path d="M68 68l14 14-14 14-14-14Z"/><path d="M1932 68l14 14-14 14-14-14Z"/><path d="M68 2732l14 14-14 14-14-14Z"/><path d="M1932 2732l14 14-14 14-14-14Z"/></g>
|
||||
<rect x="1906" y="430" width="27" height="255" fill="url(#lapis-band)" opacity=".95"/><rect x="1906" y="2115" width="27" height="255" fill="url(#lapis-band)" opacity=".95"/>
|
||||
</g>'''
|
||||
# The panels tuck behind the original tent and the pillar of cloud.
|
||||
# No alteration, retouch, rescaling or regeneration of the selected source.
|
||||
title_path='M700 62 H1900 Q1940 62 1940 102 V342 H700Z'
|
||||
verse_path='M340 2106 C445 2114 550 2118 655 2140 C960 2230 1435 2260 1960 2095 L1960 2720 C1535 2782 1045 2728 580 2750 C488 2755 437 2684 453 2592 C462 2506 520 2470 574 2404 C625 2341 641 2230 655 2140 L340 2106Z'
|
||||
title_inner='<path d="M710 82 H1898 Q1920 82 1920 104 V320 H710" fill="none" stroke="#bb9040" stroke-width="3"/><path d="M1070 301H1870" stroke="#c5a257" stroke-width="2"/>'
|
||||
verse_inner='''<path d="M690 2170 C1000 2250 1470 2250 1925 2138 M490 2677 C516 2726 551 2728 591 2725 C1060 2702 1530 2757 1925 2696" fill="none" stroke="#a17533" stroke-width="3"/><path d="M688 2182 C1000 2262 1470 2262 1923 2150 M489 2665 C516 2714 551 2716 591 2713 C1060 2690 1530 2745 1925 2684" fill="none" stroke="#8c3d3c" stroke-width="2" opacity=".65"/><path d="M1925 2140V2695" fill="none" stroke="#b7924a" stroke-width="3" stroke-dasharray="7 9"/>'''
|
||||
def panel(path,inner,ident):
|
||||
if ident=='verse-backing':
|
||||
# A linen field with a woven seam, rather than a metallic plaque.
|
||||
return f'<g id="{ident}"><path d="{path}" transform="translate(3 6)" fill="#211b15" opacity=".6"/><path d="{path}" fill="url(#linen)" stroke="#79552b" stroke-width="4"/><path d="{path}" fill="url(#weave)"/>{inner}</g>'
|
||||
return f'<g id="{ident}"><path d="{path}" fill="url(#paper)" stroke="#62421e" stroke-width="9"/><path d="{path}" fill="url(#weave)" stroke="url(#gold)" stroke-width="4"/>{inner}</g>'
|
||||
defs=defs.replace('</defs>','<linearGradient id="linen" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#f7edd4"/><stop offset=".55" stop-color="#f2e7cd"/><stop offset="1" stop-color="#e8d7b6"/></linearGradient></defs>')
|
||||
# Ties sit at the existing right rail and visibly support the hanging field.
|
||||
ties='<g fill="none" stroke-linecap="round">'+''.join(f'<path d="M1940 {y+9} C1950 {y-4} 1964 {y-5} 1977 {y-1}" stroke="#362719" stroke-width="13"/><path d="M1940 {y+9} C1950 {y-4} 1964 {y-5} 1977 {y-1}" stroke="#d6b260" stroke-width="7"/>' for y in [2110,2700])+'</g>'
|
||||
panels=panel(title_path,title_inner,'title-backing')+panel(verse_path,verse_inner,'verse-backing')+ties
|
||||
# Trace the visible source silhouette, including the whole cloud lower lobe.
|
||||
# Upper crown is separately registered; both layers stay at source coordinates.
|
||||
# Cubic contours follow the source lead, not the rough v3 clipping guesses.
|
||||
# The lower closing arc ends above the old terrain occlusion.
|
||||
top_edge='M1045 0 C1048 62 1012 102 971 105 C942 108 910 123 913 146 C916 166 952 176 967 209 C991 248 983 287 967 310 C956 325 971 347 959 371 C946 397 900 400 876 432'
|
||||
lower_edge='M724 2080 C720 2160 692 2180 704 2230 C711 2265 694 2294 677 2322 C664 2345 672 2385 695 2410 C721 2440 722 2478 701 2501 C680 2524 647 2527 619 2509 C594 2492 566 2496 537 2485 C510 2474 499 2459 471 2455 C436 2450 413 2442 393 2423 C377 2408 370 2375 370 2340'
|
||||
cloud_top=top_edge+' L780 490 H0 V0Z'
|
||||
cloud_low='M0 400H370 V1780H765 C765 1920 738 2010 724 2080 '+lower_edge.removeprefix('M724 2080 ')+' L369 2650 C318 2651 271 2644 224 2641 C173 2638 122 2650 76 2646 C45 2640 24 2634 0 2640Z'
|
||||
defs=defs.replace('</defs>',f'<clipPath id="cloud-top"><path d="{cloud_top}"/></clipPath><clipPath id="cloud-low"><path d="{cloud_low}"/></clipPath><clipPath id="title-edge-zone"><path d="{title_path}"/></clipPath></defs>')
|
||||
# Quiet fine-gold enclosure: motifs reside in the original curtain and cloud.
|
||||
frame='''<g id="legendary-frame"><rect x="19" y="19" width="1962" height="2762" rx="28" fill="none" stroke="#251c15" stroke-width="21"/><rect x="23" y="23" width="1954" height="2754" rx="23" fill="none" stroke="url(#gold)" stroke-width="11"/><rect x="43" y="43" width="1914" height="2714" rx="15" fill="none" stroke="#e2bf6c" stroke-width="3"/></g>'''
|
||||
|
||||
# A narrow opaque lead edge removes blue/terrain fringe without softening glass.
|
||||
lead=f'<g fill="none" stroke="#2b2116" stroke-width="8" stroke-linejoin="round" stroke-linecap="round"><path d="{top_edge}" clip-path="url(#title-edge-zone)"/><path d="{lower_edge}"/></g>'
|
||||
curtain='<g id="presence-foreground">'+''.join(f'<g clip-path="url(#{clip})"><image xlink:href="../../source/art-master.png" width="2000" height="2800"/></g>' for clip in ['cloud-top','cloud-low'])+lead+'</g>'
|
||||
# The continuous near post already belongs to v05 art. The registered source
|
||||
# foreground retains it; no second geometry, gradient, or post overlay is drawn.
|
||||
(P/'foreground-curtain.svg').write_text(head+defs+curtain+'</svg>');render(P/'foreground-curtain.svg',P/'foreground-curtain.png')
|
||||
fg=Image.open(P/'foreground-curtain.png').convert('RGBA')
|
||||
fa=np.asarray(fg.getchannel('A'))
|
||||
assert fa.max()==255 and fa.min()==0
|
||||
|
||||
rows=[
|
||||
('title','MOSES',1458,244,142,'P052',700),
|
||||
('verse-0','And there has not arisen a',1320,2360,74,'Sanctification P052',500),
|
||||
('verse-1','prophet since in Israel like Moses,',1320,2468,74,'Sanctification P052',500),
|
||||
('verse-2','whom the LORD knew face to face',1320,2576,74,'Sanctification P052',500),
|
||||
('reference','Deuteronomy 34:10 • ESV',1320,2690,57,'Sanctification P052',500),
|
||||
]
|
||||
for ident,content,*_ in rows:
|
||||
fontfile=font_files['title' if ident=='title' else 'reference' if ident=='reference' else 'verse']
|
||||
charset=subprocess.check_output(['fc-query','--format=%{charset}',str(FONTS/fontfile)],text=True).split()
|
||||
ranges=[tuple(int(v,16) for v in token.split('-')) for token in charset]
|
||||
assert all(any(r[0]<=ord(char)<=r[-1] for r in ranges) for char in content), (ident, 'missing glyph')
|
||||
|
||||
texts=''.join(f'<text id="{ident}" x="{x}" y="{y}" font-family="{family}" font-weight="{weight}" font-size="{size}" text-anchor="middle" fill="#263f50">{escape(text)}</text>' for ident,text,x,y,size,family,weight in rows)
|
||||
(P/'text-preview.svg').write_text(head+texts+'</svg>');render(P/'text-preview.svg',P/'text-preview.png')
|
||||
text_image=Image.open(P/'text-preview.png').convert('RGBA');ta=np.asarray(text_image.getchannel('A'))
|
||||
assert not np.any((ta>0)&(fa>0))
|
||||
|
||||
(P/'border-backing-preview.svg').write_text(head+defs+frame+panels+'</svg>');render(P/'border-backing-preview.svg',P/'border-backing-preview.png')
|
||||
backing=np.asarray(Image.open(P/'border-backing-preview.png').convert('RGBA').getchannel('A'))
|
||||
checks=[];verse_bounds=[]
|
||||
for ident,text,x,y,size,family,weight in rows:
|
||||
node=next(v for v in texts.split('</text>') if f'id="{ident}"' in v)+'</text>'
|
||||
src=P/(ident+'-ink.svg');dst=P/(ident+'-ink.png');src.write_text(head+node+'</svg>');render(src,dst)
|
||||
alpha=np.asarray(Image.open(dst).convert('RGBA').getchannel('A'));ys,xs=np.where(alpha>0);assert len(xs)
|
||||
bounds=[int(xs.min()),int(ys.min()),int(xs.max()+1),int(ys.max()+1)]
|
||||
safe=[1050,90,1905,330] if ident=='title' else [730,2275,1900,2730]
|
||||
assert safe[0]<=bounds[0] and safe[1]<=bounds[1] and bounds[2]<=safe[2] and bounds[3]<=safe[3],(ident,bounds,safe)
|
||||
assert np.all(backing[alpha>0]==255),ident
|
||||
assert not np.any((alpha>0)&(fa>0)),ident
|
||||
checks.append({'id':ident,'text':text,'baseline':y,'font':family,'weight':weight,'size':size,'inkBounds':bounds,'safeBounds':safe,'glyphPixelsOutsideOpaqueBacking':0,'foregroundIntersectionPixels':0})
|
||||
if ident.startswith('verse-'):verse_bounds.append(bounds)
|
||||
|
||||
assert ' '.join(c['text'] for c in checks if c['id'].startswith('verse-'))==json.loads((REV/'card.json').read_text())['excerpt']
|
||||
assert checks[-1]['text']==json.loads((REV/'card.json').read_text())['referenceDisplay']
|
||||
union=[min(b[0] for b in verse_bounds),min(b[1] for b in verse_bounds),max(b[2] for b in verse_bounds),max(b[3] for b in verse_bounds)]
|
||||
upper=2240;ref_top=checks[-1]['inkBounds'][1];above=union[1]-upper;below=ref_top-union[3]
|
||||
assert min(above,below)>=35 and abs(above-below)<=20,(above,below)
|
||||
|
||||
for name,include_curtain in [('composed-preview',True),('without-curtain',False)]:
|
||||
body=base+frame+panels+(curtain if include_curtain else '')+texts
|
||||
src=P/(name+'.svg');src.write_text(head+defs+body+'</svg>');render(src,P/(name+'-2000.png'))
|
||||
master=Image.open(P/(name+'-2000.png')).convert('RGB')
|
||||
master.resize((1000,1400),Image.Resampling.LANCZOS).save(P/(name+'-1000.png'))
|
||||
master.resize((500,700),Image.Resampling.LANCZOS).save(P/(name+'-500.png'))
|
||||
|
||||
# Compare the structural effect of the attached curtain.
|
||||
board=Image.new('RGB',(1000,750),'#11161c');draw=ImageDraw.Draw(board)
|
||||
for i,(label,path) in enumerate([('Panels before foreground',P/'without-curtain-1000.png'),('Pillar of cloud over panels',P/'composed-preview-1000.png')]):
|
||||
draw.text((i*500+12,12),label,fill='#f4ead4')
|
||||
image=Image.open(path).convert('RGB');image.thumbnail((480,672));board.paste(image,(i*500+10,45))
|
||||
board.save(P/'curtain-comparison.png')
|
||||
|
||||
# A focused junction comparison at master crop.
|
||||
detail=Image.new('RGB',(1200,620),'#11161c');draw=ImageDraw.Draw(detail)
|
||||
for i,(label,path) in enumerate([('Without foreground',P/'without-curtain-2000.png'),('Cloud and curtain over backing',P/'composed-preview-2000.png')]):
|
||||
draw.text((i*600+12,12),label,fill='#f4ead4')
|
||||
image=Image.open(path).convert('RGB').crop((0,1780,900,2800));image.thumbnail((575,545));detail.paste(image,(i*600+12,52))
|
||||
detail.save(P/'curtain-junction-detail.png')
|
||||
|
||||
frame_alpha=np.asarray(Image.open(P/'border-backing-preview.png').convert('RGBA').getchannel('A'))
|
||||
foreground_frame_overlap=int(np.count_nonzero((fa>0)&(frame_alpha>0)))
|
||||
report={
|
||||
'status':'passed',
|
||||
'scope':'v05 Normal-only prototype — pearl cloud and suspended tent textile',
|
||||
'canvas':[2000,2800],
|
||||
'sourceArtwork':{'path':str(ART.relative_to(REPO)),'sha256':art_hash,'unchanged':sha(ART)==art_hash},
|
||||
'template':{
|
||||
'governingIdea':'Nameplate unchanged; a suspended linen field tucks behind the pillar of cloud and ties to the right frame',
|
||||
'titleBackingPath':title_path,
|
||||
'verseBackingPath':verse_path,
|
||||
'compositionOrder':['base-art','frame-and-backings','attached-curtain-foreground','deterministic-text'],
|
||||
'curtainContour':'foreground-curtain.svg',
|
||||
'cloudUpperContour':cloud_top,
|
||||
'cloudLowerContour':cloud_low,
|
||||
'curtainAlphaBounds':list(Image.fromarray(fa).getbbox()),
|
||||
'curtainFrameOrBackingOverlapPixels':foreground_frame_overlap,
|
||||
},
|
||||
'typography':{
|
||||
'fontManifestSHA256':sha(FONTS/'manifest.json'),
|
||||
'checks':checks,
|
||||
'verseInkUnion':union,
|
||||
'upperFlourishToVerseInk':above,
|
||||
'verseInkToReferenceLineBox':below,
|
||||
'centeringDifference':abs(above-below),
|
||||
'lineBaselineGap':108,
|
||||
},
|
||||
'structuralChecks':{
|
||||
'curtainTextIntersectionPixels':int(np.count_nonzero((ta>0)&(fa>0))),
|
||||
'glyphPixelsOutsideOpaqueBacking':0,
|
||||
'artworkSHA256Unchanged':True,
|
||||
'fontGlyphCoverage':'passed',
|
||||
'fontManifestHashes':'passed',
|
||||
},
|
||||
'notPerformed':['Borderless/Textless/Boundless composition','Production finish masks','Production text masks','Harness fixture installation','GPU moving-light validation','Final card approval'],
|
||||
'staticReview':'Provisional Normal-only prototype; static inspection at card size and enlarged junctions; pending user review',
|
||||
'edgeRendering':{'geometry':'source-traced cubic Bezier paths','leadWidthMasterPixels':8,'renderScale':2,'downsample':'premultiplied RGBA LANCZOS','blur':False},
|
||||
'foregroundAdaptation':'Liked nameplate geometry and suspended linen layout retained. New v05 pearl/cool cloud uses authored wave-like cubic contours; lower closure stays above source ground occlusion. The continuous entrance post is reused from the baked source and never redrawn.'
|
||||
}
|
||||
(P/'preview-validation.json').write_text(json.dumps(report,indent=2)+'\n')
|
||||
assert sha(ART)==art_hash
|
||||
print(json.dumps({'status':'passed','preview':str((P/'composed-preview-1000.png').relative_to(REPO)),'curtainOverlapPixels':foreground_frame_overlap,'spacing':[above,below]},indent=2))
|
||||
|
||||
# Source/composed review and comparison to the carried-forward layout.
|
||||
label_font=ImageFont.truetype(str(FONTS/'P052-Bold.otf'),26)
|
||||
previous=REPO/'in-progress/cards/BP-001-moses/revisions/v03/review/layout-preview-astra-v5'
|
||||
for name,items in [
|
||||
('source-composed-comparison',[('Selected v05 source',ART),('v05 Normal prototype',P/'composed-preview-1000.png')]),
|
||||
('layout-lineage-comparison',[('Prior v03 layout / prior art',previous/'composed-preview-1000.png'),('v05 layout / current art',P/'composed-preview-1000.png')]),
|
||||
]:
|
||||
board=Image.new('RGB',(1500,1100),'#11161c');d=ImageDraw.Draw(board)
|
||||
for i,(label,path) in enumerate(items):
|
||||
d.text((i*750+24,18),label,font=label_font,fill='#f4ead4')
|
||||
im=Image.open(path).convert('RGB');im.thumbnail((730,1022));board.paste(im,(i*750+10,63))
|
||||
board.save(P/(name+'.png'))
|
||||
master=Image.open(P/'composed-preview-2000.png').convert('RGB')
|
||||
detail=Image.new('RGB',(1500,1300),'#11161c');d=ImageDraw.Draw(detail)
|
||||
for label,box,origin,size in [
|
||||
('Pearl cloud / nameplate',(620,0,1140,480),(22,65),(700,550)),
|
||||
('Cloud / textile / existing post',(295,2020,800,2660),(795,65),(660,690)),
|
||||
]:
|
||||
d.text((origin[0],20),label,font=label_font,fill='#f4ead4')
|
||||
im=master.crop(box);im=im.resize((round(im.width*1.15),round(im.height*1.15)),Image.Resampling.LANCZOS);im.thumbnail(size);detail.paste(im,origin)
|
||||
im=master.crop((290,2070,2000,2800));im.thumbnail((1450,520));detail.paste(im,(25,775))
|
||||
detail.save(P/'cloud-textile-junction-details.png')
|
||||
# Preserve approved typography and nameplate geometry without claiming the
|
||||
# changed source/cloud pixels are identical to old artwork.
|
||||
assert sha(ART)==json.loads((REV/'card.json').read_text())['artSHA256']
|
||||
assert np.array_equal(np.asarray(Image.open(P/'text-preview.png')),np.asarray(Image.open(previous/'text-preview.png')))
|
||||
report['structuralChecks']['textPixelsIdenticalToCarriedForwardLayout']=True
|
||||
report['structuralChecks']['sourceMatchesV05CardSHA256']=True
|
||||
report['structuralChecks']['secondPostDrawn']=False
|
||||
report['template']['layoutReference']=str(previous.relative_to(REPO))
|
||||
report['template']['postPolicy']='Use the continuous entrance post already baked into v05 artwork; no new post layer'
|
||||
report['template']['leftTextileAnchor']=[340,2106]
|
||||
report['edgeRendering']['alphaEdge']='Premultiplied before downsampling; no transparent-RGB halo'
|
||||
(P/'preview-validation.json').write_text(json.dumps(report,indent=2)+'\n')
|
||||
|
||||
# Targeted v1/v2 regression checks and comparison assets.
|
||||
v1=P.parent/'normal-prototype-v1'
|
||||
assert np.array_equal(np.asarray(Image.open(P/'text-preview.png')),np.asarray(Image.open(v1/'text-preview.png')))
|
||||
assert np.array_equal(np.asarray(Image.open(P/'border-backing-preview.png')),np.asarray(Image.open(v1/'border-backing-preview.png')))
|
||||
current=np.asarray(Image.open(P/'composed-preview-2000.png'))
|
||||
prior=np.asarray(Image.open(v1/'composed-preview-2000.png'))
|
||||
assert np.array_equal(current[:2300],prior[:2300])
|
||||
hem_frame=(frame_alpha[2460:2630,:65]>0)
|
||||
hem_foreground=fa[2460:2630,:65]
|
||||
assert np.all(hem_foreground[hem_frame]==255)
|
||||
report['structuralChecks']['textAndBackingPixelsIdenticalToV1']=True
|
||||
report['structuralChecks']['upper2300RowsIdenticalToV1']=True
|
||||
report['structuralChecks']['framePixelsExposedInsideCurtainHem']=int(np.count_nonzero(hem_frame & (hem_foreground<255)))
|
||||
report['template']['v2Refinement']='Registered curtain hem occludes the frame through its actual lower edge; broad rounded cloud roll replaces the pointed tail, with continuous cubic tangents.'
|
||||
(P/'preview-validation.json').write_text(json.dumps(report,indent=2)+'\n')
|
||||
for name,box,size,canvas in [
|
||||
('v1-v2-full-comparison',(0,0,2000,2800),(700,980),(1460,1060)),
|
||||
('v1-v2-junction-comparison',(0,2310,900,2780),(700,600),(1460,450)),
|
||||
]:
|
||||
board=Image.new('RGB',canvas,'#11161c');d=ImageDraw.Draw(board)
|
||||
for col,(label,folder) in enumerate([('V1 — prior junction',v1),('V2 — hem and cloud roll',P)]):
|
||||
d.text((col*730+20,18),label,font=label_font,fill='#f4ead4')
|
||||
im=Image.open(folder/'composed-preview-2000.png').convert('RGB').crop(box);im.thumbnail(size);board.paste(im,(col*730+15,65))
|
||||
board.save(P/(name+'.png'))
|
||||
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 7.6 MiB |
|
After Width: | Height: | Size: 692 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 679 KiB |
|
After Width: | Height: | Size: 458 KiB |
@@ -0,0 +1 @@
|
||||
<fontconfig><dir>/home/dkzver/docker/sanctification/fonts</dir><cachedir>/home/dkzver/docker/sanctification/in-progress/cards/BP-001-moses/revisions/v05/review/normal-prototype-v2/font-cache</cachedir></fontconfig>
|
||||
|
After Width: | Height: | Size: 2.6 MiB |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><defs>
|
||||
<linearGradient id="paper" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fbf3df" /><stop offset=".5" stop-color="#f4ead4" /><stop offset="1" stop-color="#e8d4b0" /></linearGradient>
|
||||
<linearGradient id="gold" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f0d47d" /><stop offset=".48" stop-color="#bd8b31" /><stop offset="1" stop-color="#71501e" /></linearGradient>
|
||||
<pattern id="weave" width="44" height="44" patternUnits="userSpaceOnUse"><path d="M0 11H44 M0 33H44 M11 0V44 M33 0V44" stroke="#9d783d" stroke-width="1" opacity=".045" /><path d="M22 7l7 15-7 15-7-15Z" fill="none" stroke="#806438" stroke-width="1" opacity=".07" /></pattern>
|
||||
<pattern id="lapis-band" width="34" height="34" patternUnits="userSpaceOnUse"><path d="M17 2L32 17 17 32 2 17Z" fill="#173e59" stroke="#d7b35a" stroke-width="3" /><circle cx="17" cy="17" r="4" fill="url(#gold)" /></pattern>
|
||||
<clipPath id="curtain-clip"><path d="M-10-10 H770 C734 204 694 395 650 585 C610 770 566 972 522 1170 C474 1388 426 1604 378 1818 C338 2000 302 2190 286 2380 C276 2510 304 2640 412 2810 H-10Z" /></clipPath>
|
||||
<linearGradient id="linen" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#f7edd4" /><stop offset=".55" stop-color="#f2e7cd" /><stop offset="1" stop-color="#e8d7b6" /></linearGradient><clipPath id="cloud-top"><path d="M1045 0 C1048 62 1012 102 971 105 C942 108 910 123 913 146 C916 166 952 176 967 209 C991 248 983 287 967 310 C956 325 971 347 959 371 C946 397 900 400 876 432 L780 490 H0 V0Z" /></clipPath><clipPath id="cloud-low"><path d="M0 400H370 V1780H765 C765 1920 738 2010 724 2080 C720 2160 692 2180 704 2230 C711 2265 694 2294 677 2322 C664 2345 672 2385 695 2410 C721 2440 722 2478 701 2501 C680 2524 647 2527 619 2509 C594 2492 566 2496 537 2485 C510 2474 499 2459 471 2455 C436 2450 413 2442 393 2423 C377 2408 370 2375 370 2340 L369 2650 C318 2651 271 2644 224 2641 C173 2638 122 2650 76 2646 C45 2640 24 2634 0 2640Z" /></clipPath><clipPath id="title-edge-zone"><path d="M700 62 H1900 Q1940 62 1940 102 V342 H700Z" /></clipPath></defs><g id="presence-foreground"><g clip-path="url(#cloud-top)"><image xlink:href="../../source/art-master.png" width="2000" height="2800" /></g><g clip-path="url(#cloud-low)"><image xlink:href="../../source/art-master.png" width="2000" height="2800" /></g><g fill="none" stroke="#2b2116" stroke-width="8" stroke-linejoin="round" stroke-linecap="round"><path d="M1045 0 C1048 62 1012 102 971 105 C942 108 910 123 913 146 C916 166 952 176 967 209 C991 248 983 287 967 310 C956 325 971 347 959 371 C946 397 900 400 876 432" clip-path="url(#title-edge-zone)" /><path d="M724 2080 C720 2160 692 2180 704 2230 C711 2265 694 2294 677 2322 C664 2345 672 2385 695 2410 C721 2440 722 2478 701 2501 C680 2524 647 2527 619 2509 C594 2492 566 2496 537 2485 C510 2474 499 2459 471 2455 C436 2450 413 2442 393 2423 C377 2408 370 2375 370 2340" /></g></g></svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.5 MiB |
@@ -0,0 +1,197 @@
|
||||
{
|
||||
"status": "passed",
|
||||
"scope": "v05 Normal-only prototype \u2014 pearl cloud and suspended tent textile",
|
||||
"canvas": [
|
||||
2000,
|
||||
2800
|
||||
],
|
||||
"sourceArtwork": {
|
||||
"path": "in-progress/cards/BP-001-moses/revisions/v05/source/art-master.png",
|
||||
"sha256": "372fb8f9f5b398c943364a620c970fd81e000f3e0613f71eff84e087f6396bee",
|
||||
"unchanged": true
|
||||
},
|
||||
"template": {
|
||||
"governingIdea": "Nameplate unchanged; a suspended linen field tucks behind the pillar of cloud and ties to the right frame",
|
||||
"titleBackingPath": "M700 62 H1900 Q1940 62 1940 102 V342 H700Z",
|
||||
"verseBackingPath": "M340 2106 C445 2114 550 2118 655 2140 C960 2230 1435 2260 1960 2095 L1960 2720 C1535 2782 1045 2728 580 2750 C488 2755 437 2684 453 2592 C462 2506 520 2470 574 2404 C625 2341 641 2230 655 2140 L340 2106Z",
|
||||
"compositionOrder": [
|
||||
"base-art",
|
||||
"frame-and-backings",
|
||||
"attached-curtain-foreground",
|
||||
"deterministic-text"
|
||||
],
|
||||
"curtainContour": "foreground-curtain.svg",
|
||||
"cloudUpperContour": "M1045 0 C1048 62 1012 102 971 105 C942 108 910 123 913 146 C916 166 952 176 967 209 C991 248 983 287 967 310 C956 325 971 347 959 371 C946 397 900 400 876 432 L780 490 H0 V0Z",
|
||||
"cloudLowerContour": "M0 400H370 V1780H765 C765 1920 738 2010 724 2080 C720 2160 692 2180 704 2230 C711 2265 694 2294 677 2322 C664 2345 672 2385 695 2410 C721 2440 722 2478 701 2501 C680 2524 647 2527 619 2509 C594 2492 566 2496 537 2485 C510 2474 499 2459 471 2455 C436 2450 413 2442 393 2423 C377 2408 370 2375 370 2340 L369 2650 C318 2651 271 2644 224 2641 C173 2638 122 2650 76 2646 C45 2640 24 2634 0 2640Z",
|
||||
"curtainAlphaBounds": [
|
||||
0,
|
||||
0,
|
||||
1048,
|
||||
2653
|
||||
],
|
||||
"curtainFrameOrBackingOverlapPixels": 217818,
|
||||
"layoutReference": "in-progress/cards/BP-001-moses/revisions/v03/review/layout-preview-astra-v5",
|
||||
"postPolicy": "Use the continuous entrance post already baked into v05 artwork; no new post layer",
|
||||
"leftTextileAnchor": [
|
||||
340,
|
||||
2106
|
||||
],
|
||||
"v2Refinement": "Registered curtain hem occludes the frame through its actual lower edge; broad rounded cloud roll replaces the pointed tail, with continuous cubic tangents."
|
||||
},
|
||||
"typography": {
|
||||
"fontManifestSHA256": "ef68ee6393981d54ca068a950f21c53b374d20e6e46c21c788f36380d3aae178",
|
||||
"checks": [
|
||||
{
|
||||
"id": "title",
|
||||
"text": "MOSES",
|
||||
"baseline": 244,
|
||||
"font": "P052",
|
||||
"weight": 700,
|
||||
"size": 142,
|
||||
"inkBounds": [
|
||||
1202,
|
||||
145,
|
||||
1711,
|
||||
247
|
||||
],
|
||||
"safeBounds": [
|
||||
1050,
|
||||
90,
|
||||
1905,
|
||||
330
|
||||
],
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"foregroundIntersectionPixels": 0
|
||||
},
|
||||
{
|
||||
"id": "verse-0",
|
||||
"text": "And there has not arisen a",
|
||||
"baseline": 2360,
|
||||
"font": "Sanctification P052",
|
||||
"weight": 500,
|
||||
"size": 74,
|
||||
"inkBounds": [
|
||||
890,
|
||||
2306,
|
||||
1749,
|
||||
2362
|
||||
],
|
||||
"safeBounds": [
|
||||
730,
|
||||
2275,
|
||||
1900,
|
||||
2730
|
||||
],
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"foregroundIntersectionPixels": 0
|
||||
},
|
||||
{
|
||||
"id": "verse-1",
|
||||
"text": "prophet since in Israel like Moses,",
|
||||
"baseline": 2468,
|
||||
"font": "Sanctification P052",
|
||||
"weight": 500,
|
||||
"size": 74,
|
||||
"inkBounds": [
|
||||
769,
|
||||
2414,
|
||||
1869,
|
||||
2490
|
||||
],
|
||||
"safeBounds": [
|
||||
730,
|
||||
2275,
|
||||
1900,
|
||||
2730
|
||||
],
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"foregroundIntersectionPixels": 0
|
||||
},
|
||||
{
|
||||
"id": "verse-2",
|
||||
"text": "whom the LORD knew face to face",
|
||||
"baseline": 2576,
|
||||
"font": "Sanctification P052",
|
||||
"weight": 500,
|
||||
"size": 74,
|
||||
"inkBounds": [
|
||||
752,
|
||||
2521,
|
||||
1886,
|
||||
2578
|
||||
],
|
||||
"safeBounds": [
|
||||
730,
|
||||
2275,
|
||||
1900,
|
||||
2730
|
||||
],
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"foregroundIntersectionPixels": 0
|
||||
},
|
||||
{
|
||||
"id": "reference",
|
||||
"text": "Deuteronomy 34:10 \u2022 ESV",
|
||||
"baseline": 2690,
|
||||
"font": "Sanctification P052",
|
||||
"weight": 500,
|
||||
"size": 57,
|
||||
"inkBounds": [
|
||||
991,
|
||||
2649,
|
||||
1649,
|
||||
2707
|
||||
],
|
||||
"safeBounds": [
|
||||
730,
|
||||
2275,
|
||||
1900,
|
||||
2730
|
||||
],
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"foregroundIntersectionPixels": 0
|
||||
}
|
||||
],
|
||||
"verseInkUnion": [
|
||||
752,
|
||||
2306,
|
||||
1886,
|
||||
2578
|
||||
],
|
||||
"upperFlourishToVerseInk": 66,
|
||||
"verseInkToReferenceLineBox": 71,
|
||||
"centeringDifference": 5,
|
||||
"lineBaselineGap": 108
|
||||
},
|
||||
"structuralChecks": {
|
||||
"curtainTextIntersectionPixels": 0,
|
||||
"glyphPixelsOutsideOpaqueBacking": 0,
|
||||
"artworkSHA256Unchanged": true,
|
||||
"fontGlyphCoverage": "passed",
|
||||
"fontManifestHashes": "passed",
|
||||
"textPixelsIdenticalToCarriedForwardLayout": true,
|
||||
"sourceMatchesV05CardSHA256": true,
|
||||
"secondPostDrawn": false,
|
||||
"textAndBackingPixelsIdenticalToV1": true,
|
||||
"upper2300RowsIdenticalToV1": true,
|
||||
"framePixelsExposedInsideCurtainHem": 0
|
||||
},
|
||||
"notPerformed": [
|
||||
"Borderless/Textless/Boundless composition",
|
||||
"Production finish masks",
|
||||
"Production text masks",
|
||||
"Harness fixture installation",
|
||||
"GPU moving-light validation",
|
||||
"Final card approval"
|
||||
],
|
||||
"staticReview": "Provisional Normal-only prototype; static inspection at card size and enlarged junctions; pending user review",
|
||||
"edgeRendering": {
|
||||
"geometry": "source-traced cubic Bezier paths",
|
||||
"leadWidthMasterPixels": 8,
|
||||
"renderScale": 2,
|
||||
"downsample": "premultiplied RGBA LANCZOS",
|
||||
"blur": false,
|
||||
"alphaEdge": "Premultiplied before downsampling; no transparent-RGB halo"
|
||||
},
|
||||
"foregroundAdaptation": "Liked nameplate geometry and suspended linen layout retained. New v05 pearl/cool cloud uses authored wave-like cubic contours; lower closure stays above source ground occlusion. The continuous entrance post is reused from the baked source and never redrawn."
|
||||
}
|
||||
|
After Width: | Height: | Size: 46 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><text id="reference" x="1320" y="2690" font-family="Sanctification P052" font-weight="500" font-size="57" text-anchor="middle" fill="#263f50">Deuteronomy 34:10 • ESV</text></svg>
|
||||
|
After Width: | Height: | Size: 314 B |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 139 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><text id="title" x="1458" y="244" font-family="P052" font-weight="700" font-size="142" text-anchor="middle" fill="#263f50">MOSES</text><text id="verse-0" x="1320" y="2360" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">And there has not arisen a</text><text id="verse-1" x="1320" y="2468" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">prophet since in Israel like Moses,</text><text id="verse-2" x="1320" y="2576" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">whom the LORD knew face to face</text><text id="reference" x="1320" y="2690" font-family="Sanctification P052" font-weight="500" font-size="57" text-anchor="middle" fill="#263f50">Deuteronomy 34:10 • ESV</text></svg>
|
||||
|
After Width: | Height: | Size: 982 B |
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><text id="title" x="1458" y="244" font-family="P052" font-weight="700" font-size="142" text-anchor="middle" fill="#263f50">MOSES</text></svg>
|
||||
|
After Width: | Height: | Size: 275 B |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 362 KiB |
|
After Width: | Height: | Size: 48 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><text id="verse-0" x="1320" y="2360" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">And there has not arisen a</text></svg>
|
||||
|
After Width: | Height: | Size: 313 B |
|
After Width: | Height: | Size: 56 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><text id="verse-1" x="1320" y="2468" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">prophet since in Israel like Moses,</text></svg>
|
||||
|
After Width: | Height: | Size: 322 B |
|
After Width: | Height: | Size: 56 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><text id="verse-2" x="1320" y="2576" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">whom the LORD knew face to face</text></svg>
|
||||
|
After Width: | Height: | Size: 318 B |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 7.4 MiB |
|
After Width: | Height: | Size: 672 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><defs>
|
||||
<linearGradient id="paper" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fbf3df" /><stop offset=".5" stop-color="#f4ead4" /><stop offset="1" stop-color="#e8d4b0" /></linearGradient>
|
||||
<linearGradient id="gold" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f0d47d" /><stop offset=".48" stop-color="#bd8b31" /><stop offset="1" stop-color="#71501e" /></linearGradient>
|
||||
<pattern id="weave" width="44" height="44" patternUnits="userSpaceOnUse"><path d="M0 11H44 M0 33H44 M11 0V44 M33 0V44" stroke="#9d783d" stroke-width="1" opacity=".045" /><path d="M22 7l7 15-7 15-7-15Z" fill="none" stroke="#806438" stroke-width="1" opacity=".07" /></pattern>
|
||||
<pattern id="lapis-band" width="34" height="34" patternUnits="userSpaceOnUse"><path d="M17 2L32 17 17 32 2 17Z" fill="#173e59" stroke="#d7b35a" stroke-width="3" /><circle cx="17" cy="17" r="4" fill="url(#gold)" /></pattern>
|
||||
<clipPath id="curtain-clip"><path d="M-10-10 H770 C734 204 694 395 650 585 C610 770 566 972 522 1170 C474 1388 426 1604 378 1818 C338 2000 302 2190 286 2380 C276 2510 304 2640 412 2810 H-10Z" /></clipPath>
|
||||
<linearGradient id="linen" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#f7edd4" /><stop offset=".55" stop-color="#f2e7cd" /><stop offset="1" stop-color="#e8d7b6" /></linearGradient><clipPath id="cloud-top"><path d="M1045 0 C1048 62 1012 102 971 105 C942 108 910 123 913 146 C916 166 952 176 967 209 C991 248 983 287 967 310 C956 325 971 347 959 371 C946 397 900 400 876 432 L780 490 H0 V0Z" /></clipPath><clipPath id="cloud-low"><path d="M0 400H370 V1780H765 C765 1920 738 2010 724 2080 C720 2160 692 2180 704 2230 C711 2265 694 2294 677 2322 C664 2345 672 2385 695 2410 C721 2440 722 2478 701 2501 C680 2524 647 2527 619 2509 C594 2492 566 2496 537 2485 C510 2474 499 2459 471 2455 C436 2450 413 2442 393 2423 C377 2408 370 2375 370 2340 L369 2650 C318 2651 271 2644 224 2641 C173 2638 122 2650 76 2646 C45 2640 24 2634 0 2640Z" /></clipPath><clipPath id="title-edge-zone"><path d="M700 62 H1900 Q1940 62 1940 102 V342 H700Z" /></clipPath></defs><image xlink:href="../../source/art-master.png" width="2000" height="2800" /><g id="legendary-frame"><rect x="19" y="19" width="1962" height="2762" rx="28" fill="none" stroke="#251c15" stroke-width="21" /><rect x="23" y="23" width="1954" height="2754" rx="23" fill="none" stroke="url(#gold)" stroke-width="11" /><rect x="43" y="43" width="1914" height="2714" rx="15" fill="none" stroke="#e2bf6c" stroke-width="3" /></g><g id="title-backing"><path d="M700 62 H1900 Q1940 62 1940 102 V342 H700Z" fill="url(#paper)" stroke="#62421e" stroke-width="9" /><path d="M700 62 H1900 Q1940 62 1940 102 V342 H700Z" fill="url(#weave)" stroke="url(#gold)" stroke-width="4" /><path d="M710 82 H1898 Q1920 82 1920 104 V320 H710" fill="none" stroke="#bb9040" stroke-width="3" /><path d="M1070 301H1870" stroke="#c5a257" stroke-width="2" /></g><g id="verse-backing"><path d="M340 2106 C445 2114 550 2118 655 2140 C960 2230 1435 2260 1960 2095 L1960 2720 C1535 2782 1045 2728 580 2750 C488 2755 437 2684 453 2592 C462 2506 520 2470 574 2404 C625 2341 641 2230 655 2140 L340 2106Z" transform="translate(3 6)" fill="#211b15" opacity=".6" /><path d="M340 2106 C445 2114 550 2118 655 2140 C960 2230 1435 2260 1960 2095 L1960 2720 C1535 2782 1045 2728 580 2750 C488 2755 437 2684 453 2592 C462 2506 520 2470 574 2404 C625 2341 641 2230 655 2140 L340 2106Z" fill="url(#linen)" stroke="#79552b" stroke-width="4" /><path d="M340 2106 C445 2114 550 2118 655 2140 C960 2230 1435 2260 1960 2095 L1960 2720 C1535 2782 1045 2728 580 2750 C488 2755 437 2684 453 2592 C462 2506 520 2470 574 2404 C625 2341 641 2230 655 2140 L340 2106Z" fill="url(#weave)" /><path d="M690 2170 C1000 2250 1470 2250 1925 2138 M490 2677 C516 2726 551 2728 591 2725 C1060 2702 1530 2757 1925 2696" fill="none" stroke="#a17533" stroke-width="3" /><path d="M688 2182 C1000 2262 1470 2262 1923 2150 M489 2665 C516 2714 551 2716 591 2713 C1060 2690 1530 2745 1925 2684" fill="none" stroke="#8c3d3c" stroke-width="2" opacity=".65" /><path d="M1925 2140V2695" fill="none" stroke="#b7924a" stroke-width="3" stroke-dasharray="7 9" /></g><g fill="none" stroke-linecap="round"><path d="M1940 2119 C1950 2106 1964 2105 1977 2109" stroke="#362719" stroke-width="13" /><path d="M1940 2119 C1950 2106 1964 2105 1977 2109" stroke="#d6b260" stroke-width="7" /><path d="M1940 2709 C1950 2696 1964 2695 1977 2699" stroke="#362719" stroke-width="13" /><path d="M1940 2709 C1950 2696 1964 2695 1977 2699" stroke="#d6b260" stroke-width="7" /></g><text id="title" x="1458" y="244" font-family="P052" font-weight="700" font-size="142" text-anchor="middle" fill="#263f50">MOSES</text><text id="verse-0" x="1320" y="2360" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">And there has not arisen a</text><text id="verse-1" x="1320" y="2468" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">prophet since in Israel like Moses,</text><text id="verse-2" x="1320" y="2576" font-family="Sanctification P052" font-weight="500" font-size="74" text-anchor="middle" fill="#263f50">whom the LORD knew face to face</text><text id="reference" x="1320" y="2690" font-family="Sanctification P052" font-weight="500" font-size="57" text-anchor="middle" fill="#263f50">Deuteronomy 34:10 • ESV</text></svg>
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
BIN
artifacts/cards/BP-001-moses/review/normal-reconstructed.png
Normal file
|
After Width: | Height: | Size: 7.3 MiB |
BIN
artifacts/cards/BP-001-moses/review/post-before-after.png
Normal file
|
After Width: | Height: | Size: 211 KiB |
BIN
artifacts/cards/BP-001-moses/review/post-join-detail.png
Normal file
|
After Width: | Height: | Size: 153 KiB |
BIN
artifacts/cards/BP-001-moses/review/printings-comparison.png
Normal file
|
After Width: | Height: | Size: 942 KiB |
29
artifacts/cards/BP-001-moses/review/source-validation.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"status": "pass",
|
||||
"baseSHA256": "da9624d7f5c897fbcaaada9774b2fa8c11db65288b7885040e17ad16a71d925e",
|
||||
"artSHA256": "372fb8f9f5b398c943364a620c970fd81e000f3e0613f71eff84e087f6396bee",
|
||||
"dimensions": [
|
||||
2000,
|
||||
2800
|
||||
],
|
||||
"changedPixels": 72357,
|
||||
"changedFraction": 0.012920892857142856,
|
||||
"differenceBoundsExclusive": [
|
||||
318,
|
||||
994,
|
||||
377,
|
||||
2647
|
||||
],
|
||||
"authorizedRegion": [
|
||||
310,
|
||||
994,
|
||||
379,
|
||||
2652
|
||||
],
|
||||
"allPixelsOutsidePostRegionIdentical": true,
|
||||
"method": "Deterministic authored overlay, antialiased at 4x; RGB compositing only inside overlay alpha",
|
||||
"scope": "Art-only, no card assembly",
|
||||
"repeatableOutputs": true,
|
||||
"repeatableFiles": 7,
|
||||
"v04FilesUnchanged": 21
|
||||
}
|
||||
26
artifacts/cards/BP-001-moses/review/static-review.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"status": "passed-structural-user-visual-review-pending",
|
||||
"cardId": "BP-001",
|
||||
"revision": "v05",
|
||||
"checks": {
|
||||
"selectedNormalPreservedByteForByte": true,
|
||||
"fourPrintingComposition": "passed",
|
||||
"layerRegistration": "passed",
|
||||
"opaqueFaces": "passed",
|
||||
"textOnOpaqueBacking": "passed",
|
||||
"foregroundTextIntersectionPixels": 0,
|
||||
"depthResolvedFinishCoverage": "passed",
|
||||
"textlessTentCloudOccludesTopLeftRail": "passed",
|
||||
"textlessTopRailResumesOverOpenSky": "passed",
|
||||
"lowMedHighExports": "passed"
|
||||
},
|
||||
"reviewAssets": [
|
||||
"printings-comparison.png",
|
||||
"layers-comparison.png",
|
||||
"finish-masks-comparison.png",
|
||||
"textile-finish-detail.png"
|
||||
],
|
||||
"staticVisualAssessment": "Pending user review of the four production printings and masks.",
|
||||
"movingLightReview": "Not performed.",
|
||||
"finalCardApproval": null
|
||||
}
|
||||
BIN
artifacts/cards/BP-001-moses/review/textile-finish-detail.png
Normal file
|
After Width: | Height: | Size: 601 KiB |
@@ -0,0 +1,24 @@
|
||||
# Moses v05 — vertical text textile prototype
|
||||
|
||||
A separate Normal-only interpretation of the clarified lower-left layout. The horizontal footer is removed. A tall cream textile attaches to the outer left frame from mid-card to near the bottom, with a broad curved inner edge beside Moses and burgundy, lapis and gold woven bands.
|
||||
|
||||
- [Full preview](composed-preview-1000.png)
|
||||
- [Card-size preview](composed-preview-500.png)
|
||||
- [Horizontal / vertical comparison](horizontal-vertical-comparison.png)
|
||||
- [Panel / robe junction details](panel-robe-junction-detail.png)
|
||||
- [Source / composed comparison](source-composed-comparison.png)
|
||||
- [Editable composition](composed-preview.svg)
|
||||
- [Builder](build-preview.py)
|
||||
- [Validation](preview-validation.json)
|
||||
|
||||
The tent, cloud and curtain stay behind the lower text backing. No lower illustrated foreground is drawn, and no cloud crosses the lower panel or Moses. The upper nameplate/cloud-crown direction is retained. The curtain still occludes the outer rail, but that occlusion is composited beneath the lower panel. The continuous post remains part of the unmodified v05 source; no duplicate post is authored.
|
||||
|
||||
The verse uses six lines at 68 master pixels, with a single-line reference at 42 pixels. Both use shared Sanctification P052 Medium; MOSES remains shared P052 Bold. Verse placement uses actual glyph bounds, with equal 154-master-pixel gaps from its upper ornament and reference ink. Exact narrator content remains unquoted and unchanged.
|
||||
|
||||
Validation checks exact text, font manifest hashes, glyph coverage, safe bounds, opaque backing under every glyph, zero foreground/text intersection, zero lower foreground pixels, the unchanged source hash, and pixel preservation of the upper composition. The source remains registered at 2000 × 2800. Geometry-heavy outputs render at 2× and downsample in premultiplied alpha.
|
||||
|
||||
Source SHA256: 372fb8f9f5b398c943364a620c970fd81e000f3e0613f71eff84e087f6396bee.
|
||||
|
||||
Rebuild: python3 in-progress/cards/BP-001-moses/revisions/v05/review/vertical-text-prototype-v1/build-preview.py. Requires Inkscape, Fontconfig, Pillow and NumPy.
|
||||
|
||||
Previous horizontal prototypes are preserved. No other printing types, finish masks, production layers, harness installation, acceptance or promotion are included.
|
||||
|
After Width: | Height: | Size: 264 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800"><defs>
|
||||
<linearGradient id="paper" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fbf3df"/><stop offset=".5" stop-color="#f4ead4"/><stop offset="1" stop-color="#e8d4b0"/></linearGradient>
|
||||
<linearGradient id="gold" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f0d47d"/><stop offset=".48" stop-color="#bd8b31"/><stop offset="1" stop-color="#71501e"/></linearGradient>
|
||||
<pattern id="weave" width="44" height="44" patternUnits="userSpaceOnUse"><path d="M0 11H44 M0 33H44 M11 0V44 M33 0V44" stroke="#9d783d" stroke-width="1" opacity=".045"/><path d="M22 7l7 15-7 15-7-15Z" fill="none" stroke="#806438" stroke-width="1" opacity=".07"/></pattern>
|
||||
<pattern id="lapis-band" width="34" height="34" patternUnits="userSpaceOnUse"><path d="M17 2L32 17 17 32 2 17Z" fill="#173e59" stroke="#d7b35a" stroke-width="3"/><circle cx="17" cy="17" r="4" fill="url(#gold)"/></pattern>
|
||||
<clipPath id="curtain-clip"><path d="M-10-10 H770 C734 204 694 395 650 585 C610 770 566 972 522 1170 C474 1388 426 1604 378 1818 C338 2000 302 2190 286 2380 C276 2510 304 2640 412 2810 H-10Z"/></clipPath>
|
||||
<linearGradient id="linen" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#f7edd4"/><stop offset=".55" stop-color="#f2e7cd"/><stop offset="1" stop-color="#e8d7b6"/></linearGradient><clipPath id="cloud-top"><path d="M1045 0 C1048 62 1012 102 971 105 C942 108 910 123 913 146 C916 166 952 176 967 209 C991 248 983 287 967 310 C956 325 971 347 959 371 C946 397 900 400 876 432 L780 490 H0 V0Z"/></clipPath><clipPath id="title-edge-zone"><path d="M700 62 H1900 Q1940 62 1940 102 V342 H700Z"/></clipPath><clipPath id="curtain-over-rail"><path d="M0 0H80 V2646 C45 2640 24 2634 0 2640Z"/></clipPath></defs><g id="legendary-frame"><rect x="19" y="19" width="1962" height="2762" rx="28" fill="none" stroke="#251c15" stroke-width="21"/><rect x="23" y="23" width="1954" height="2754" rx="23" fill="none" stroke="url(#gold)" stroke-width="11"/><rect x="43" y="43" width="1914" height="2714" rx="15" fill="none" stroke="#e2bf6c" stroke-width="3"/></g><g id="title-backing"><path d="M700 62 H1900 Q1940 62 1940 102 V342 H700Z" fill="url(#paper)" stroke="#62421e" stroke-width="9"/><path d="M700 62 H1900 Q1940 62 1940 102 V342 H700Z" fill="url(#weave)" stroke="url(#gold)" stroke-width="4"/><path d="M710 82 H1898 Q1920 82 1920 104 V320 H710" fill="none" stroke="#bb9040" stroke-width="3"/><path d="M1070 301H1870" stroke="#c5a257" stroke-width="2"/></g><g id="verse-backing"><path d="M23 1400 H510 C592 1400 639 1610 653 1880 C668 2180 627 2490 585 2745 H23Z" transform="translate(3 6)" fill="#211b15" opacity=".6"/><path d="M23 1400 H510 C592 1400 639 1610 653 1880 C668 2180 627 2490 585 2745 H23Z" fill="url(#linen)" stroke="#79552b" stroke-width="4"/><path d="M23 1400 H510 C592 1400 639 1610 653 1880 C668 2180 627 2490 585 2745 H23Z" fill="url(#weave)"/><path d="M61 1436H503 C558 1436 599 1570 622 1800 C650 2130 609 2460 553 2710H61Z" fill="none" stroke="#bd944c" stroke-width="3"/><path d="M83 1513H533 M83 2650H545" stroke="#743443" stroke-width="7"/><path d="M83 1526H538 M83 2663H542" stroke="#244961" stroke-width="6"/><path d="M83 1536H542 M83 2673H540" stroke="#c39b4c" stroke-width="3"/><path d="M74 1580V2600" stroke="#b9914a" stroke-width="2" stroke-dasharray="4 12"/><path d="M125 1507l8 12-8 12-8-12Z M125 2644l8 12-8 12-8-12Z" fill="#c5a45f" stroke="#705328" stroke-width="1"/><path d="M215 1507l8 12-8 12-8-12Z M215 2644l8 12-8 12-8-12Z" fill="#c5a45f" stroke="#705328" stroke-width="1"/><path d="M305 1507l8 12-8 12-8-12Z M305 2644l8 12-8 12-8-12Z" fill="#c5a45f" stroke="#705328" stroke-width="1"/><path d="M395 1507l8 12-8 12-8-12Z M395 2644l8 12-8 12-8-12Z" fill="#c5a45f" stroke="#705328" stroke-width="1"/><path d="M485 1507l8 12-8 12-8-12Z M485 2644l8 12-8 12-8-12Z" fill="#c5a45f" stroke="#705328" stroke-width="1"/></g><g fill="none" stroke-linecap="round"><path d="M17 1444 C35 1432 46 1436 58 1448" stroke="#342619" stroke-width="11"/><path d="M17 1444 C35 1432 46 1436 58 1448" stroke="#d8b56d" stroke-width="6"/><path d="M17 2695 C35 2683 46 2687 58 2699" stroke="#342619" stroke-width="11"/><path d="M17 2695 C35 2683 46 2687 58 2699" stroke="#d8b56d" stroke-width="6"/></g></svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,224 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Provisional BP-001 Moses Legendary composition; no production assembly."""
|
||||
from pathlib import Path
|
||||
import hashlib,json,os,subprocess
|
||||
from xml.sax.saxutils import escape
|
||||
import numpy as np
|
||||
from PIL import Image,ImageDraw,ImageFont
|
||||
|
||||
P=Path(__file__).resolve().parent
|
||||
REV=P.parents[1]
|
||||
REPO=next(p for p in P.parents if (p/'docs/card-layer-pipeline.md').is_file())
|
||||
ART=REV/'source/art-master.png'
|
||||
FONTS=REPO/'fonts'
|
||||
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
art_hash=sha(ART)
|
||||
font_manifest=json.loads((FONTS/'manifest.json').read_text())
|
||||
font_files={'title':'P052-Bold.otf','verse':'SanctificationP052-Medium.otf','reference':'SanctificationP052-Medium.otf'}
|
||||
for name in set(font_files.values()):
|
||||
assert sha(FONTS/name)==font_manifest['files'][name]['sha256']
|
||||
(P/'font-cache').mkdir(exist_ok=True)
|
||||
(P/'fonts.conf').write_text(f'<fontconfig><dir>{FONTS}</dir><cachedir>{P/"font-cache"}</cachedir></fontconfig>')
|
||||
env=dict(os.environ,FONTCONFIG_FILE=str(P/'fonts.conf'))
|
||||
def ink(*args):return subprocess.check_output(['inkscape',*map(str,args)],env=env,text=True,stderr=subprocess.PIPE).strip()
|
||||
def render(src,dst):
|
||||
# Supersample only geometry-heavy composite/foreground outputs; text measurements
|
||||
# remain native. LANCZOS uses premultiplied RGBA via Pillow's RGBa path.
|
||||
factor=2 if src.stem in {'foreground-curtain','composed-preview','without-curtain'} else 1
|
||||
ink(src,'--export-type=png',f'--export-filename={dst}',f'--export-width={2000*factor}',f'--export-height={2800*factor}')
|
||||
if factor>1:
|
||||
im=Image.open(dst).convert('RGBA')
|
||||
im.convert('RGBa').resize((2000,2800),Image.Resampling.LANCZOS).convert('RGBA').save(dst)
|
||||
|
||||
def resolve(family,style):return Path(subprocess.check_output(['fc-match','-f','%{file}',f'{family}:style={style}'],env=env,text=True).strip()).resolve()
|
||||
assert resolve('P052','Bold')==(FONTS/'P052-Bold.otf').resolve()
|
||||
assert resolve('Sanctification P052','Medium')==(FONTS/'SanctificationP052-Medium.otf').resolve()
|
||||
|
||||
head='<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2800" viewBox="0 0 2000 2800">'
|
||||
defs='''<defs>
|
||||
<linearGradient id="paper" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fbf3df"/><stop offset=".5" stop-color="#f4ead4"/><stop offset="1" stop-color="#e8d4b0"/></linearGradient>
|
||||
<linearGradient id="gold" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f0d47d"/><stop offset=".48" stop-color="#bd8b31"/><stop offset="1" stop-color="#71501e"/></linearGradient>
|
||||
<pattern id="weave" width="44" height="44" patternUnits="userSpaceOnUse"><path d="M0 11H44 M0 33H44 M11 0V44 M33 0V44" stroke="#9d783d" stroke-width="1" opacity=".045"/><path d="M22 7l7 15-7 15-7-15Z" fill="none" stroke="#806438" stroke-width="1" opacity=".07"/></pattern>
|
||||
<pattern id="lapis-band" width="34" height="34" patternUnits="userSpaceOnUse"><path d="M17 2L32 17 17 32 2 17Z" fill="#173e59" stroke="#d7b35a" stroke-width="3"/><circle cx="17" cy="17" r="4" fill="url(#gold)"/></pattern>
|
||||
<clipPath id="curtain-clip"><path d="M-10-10 H770 C734 204 694 395 650 585 C610 770 566 972 522 1170 C474 1388 426 1604 378 1818 C338 2000 302 2190 286 2380 C276 2510 304 2640 412 2810 H-10Z"/></clipPath>
|
||||
</defs>'''
|
||||
base='<image xlink:href="../../source/art-master.png" width="2000" height="2800"/>'
|
||||
frame='''<g id="legendary-frame">
|
||||
<rect x="14" y="14" width="1972" height="2772" rx="34" fill="none" stroke="#2c2115" stroke-width="28"/>
|
||||
<rect x="25" y="25" width="1950" height="2750" rx="27" fill="none" stroke="url(#gold)" stroke-width="14"/>
|
||||
<rect x="43" y="43" width="1914" height="2714" rx="20" fill="none" stroke="#173e59" stroke-width="10"/>
|
||||
<rect x="55" y="55" width="1890" height="2690" rx="15" fill="none" stroke="#d7b35a" stroke-width="4"/>
|
||||
<path d="M68 260V68H260 M1740 68H1932V260 M68 2540V2732H260 M1740 2732H1932V2540" fill="none" stroke="#9b6d26" stroke-width="6"/>
|
||||
<g fill="url(#gold)" stroke="#533810" stroke-width="3"><path d="M68 68l14 14-14 14-14-14Z"/><path d="M1932 68l14 14-14 14-14-14Z"/><path d="M68 2732l14 14-14 14-14-14Z"/><path d="M1932 2732l14 14-14 14-14-14Z"/></g>
|
||||
<rect x="1906" y="430" width="27" height="255" fill="url(#lapis-band)" opacity=".95"/><rect x="1906" y="2115" width="27" height="255" fill="url(#lapis-band)" opacity=".95"/>
|
||||
</g>'''
|
||||
# The panels tuck behind the original tent and the pillar of cloud.
|
||||
# No alteration, retouch, rescaling or regeneration of the selected source.
|
||||
title_path='M700 62 H1900 Q1940 62 1940 102 V342 H700Z'
|
||||
verse_path='M23 1400 H510 C592 1400 639 1610 653 1880 C668 2180 627 2490 585 2745 H23Z'
|
||||
title_inner='<path d="M710 82 H1898 Q1920 82 1920 104 V320 H710" fill="none" stroke="#bb9040" stroke-width="3"/><path d="M1070 301H1870" stroke="#c5a257" stroke-width="2"/>'
|
||||
verse_inner='''<path d="M61 1436H503 C558 1436 599 1570 622 1800 C650 2130 609 2460 553 2710H61Z" fill="none" stroke="#bd944c" stroke-width="3"/><path d="M83 1513H533 M83 2650H545" stroke="#743443" stroke-width="7"/><path d="M83 1526H538 M83 2663H542" stroke="#244961" stroke-width="6"/><path d="M83 1536H542 M83 2673H540" stroke="#c39b4c" stroke-width="3"/><path d="M74 1580V2600" stroke="#b9914a" stroke-width="2" stroke-dasharray="4 12"/>'''
|
||||
for x in [125,215,305,395,485]:
|
||||
verse_inner+=f'<path d="M{x} 1507l8 12-8 12-8-12Z M{x} 2644l8 12-8 12-8-12Z" fill="#c5a45f" stroke="#705328" stroke-width="1"/>'
|
||||
def panel(path,inner,ident):
|
||||
if ident=='verse-backing':
|
||||
# A linen field with a woven seam, rather than a metallic plaque.
|
||||
return f'<g id="{ident}"><path d="{path}" transform="translate(3 6)" fill="#211b15" opacity=".6"/><path d="{path}" fill="url(#linen)" stroke="#79552b" stroke-width="4"/><path d="{path}" fill="url(#weave)"/>{inner}</g>'
|
||||
return f'<g id="{ident}"><path d="{path}" fill="url(#paper)" stroke="#62421e" stroke-width="9"/><path d="{path}" fill="url(#weave)" stroke="url(#gold)" stroke-width="4"/>{inner}</g>'
|
||||
defs=defs.replace('</defs>','<linearGradient id="linen" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#f7edd4"/><stop offset=".55" stop-color="#f2e7cd"/><stop offset="1" stop-color="#e8d7b6"/></linearGradient></defs>')
|
||||
# The tall textile is fixed to the outer left rail, not the background tent.
|
||||
ties='<g fill="none" stroke-linecap="round">'+''.join(f'<path d="M17 {y} C35 {y-12} 46 {y-8} 58 {y+4}" stroke="#342619" stroke-width="11"/><path d="M17 {y} C35 {y-12} 46 {y-8} 58 {y+4}" stroke="#d8b56d" stroke-width="6"/>' for y in [1444,2695])+'</g>'
|
||||
panels=panel(title_path,title_inner,'title-backing')+panel(verse_path,verse_inner,'verse-backing')+ties
|
||||
# Trace the visible source silhouette, including the whole cloud lower lobe.
|
||||
# Upper crown is separately registered; both layers stay at source coordinates.
|
||||
# Cubic contours follow the source lead, not the rough v3 clipping guesses.
|
||||
# The lower closing arc ends above the old terrain occlusion.
|
||||
top_edge='M1045 0 C1048 62 1012 102 971 105 C942 108 910 123 913 146 C916 166 952 176 967 209 C991 248 983 287 967 310 C956 325 971 347 959 371 C946 397 900 400 876 432'
|
||||
lower_edge=''
|
||||
cloud_low=''
|
||||
cloud_top=top_edge+' L780 490 H0 V0Z'
|
||||
defs=defs.replace('</defs>',f'<clipPath id="cloud-top"><path d="{cloud_top}"/></clipPath><clipPath id="title-edge-zone"><path d="{title_path}"/></clipPath></defs>')
|
||||
# Quiet fine-gold enclosure: motifs reside in the original curtain and cloud.
|
||||
frame='''<g id="legendary-frame"><rect x="19" y="19" width="1962" height="2762" rx="28" fill="none" stroke="#251c15" stroke-width="21"/><rect x="23" y="23" width="1954" height="2754" rx="23" fill="none" stroke="url(#gold)" stroke-width="11"/><rect x="43" y="43" width="1914" height="2714" rx="15" fill="none" stroke="#e2bf6c" stroke-width="3"/></g>'''
|
||||
|
||||
# Frame occlusion is below the textile, not a lower foreground overlap.
|
||||
# Preserve the existing curtain in front of the outer rail without putting it
|
||||
# in front of the new lower backing or Moses.
|
||||
defs=defs.replace('</defs>','<clipPath id="curtain-over-rail"><path d="M0 0H80 V2646 C45 2640 24 2634 0 2640Z"/></clipPath></defs>')
|
||||
curtain_frame_occlusion='<g clip-path="url(#curtain-over-rail)"><image xlink:href="../../source/art-master.png" width="2000" height="2800"/></g>'
|
||||
# A narrow opaque lead edge removes blue/terrain fringe without softening glass.
|
||||
lead=f'<g fill="none" stroke="#2b2116" stroke-width="8" stroke-linejoin="round" stroke-linecap="round"><path d="{top_edge}" clip-path="url(#title-edge-zone)"/></g>'
|
||||
curtain='<g id="presence-foreground">'+''.join(f'<g clip-path="url(#{clip})"><image xlink:href="../../source/art-master.png" width="2000" height="2800"/></g>' for clip in ['cloud-top'])+lead+'</g>'
|
||||
# The continuous near post already belongs to v05 art. The registered source
|
||||
# foreground retains it; no second geometry, gradient, or post overlay is drawn.
|
||||
(P/'foreground-curtain.svg').write_text(head+defs+curtain+'</svg>');render(P/'foreground-curtain.svg',P/'foreground-curtain.png')
|
||||
fg=Image.open(P/'foreground-curtain.png').convert('RGBA')
|
||||
fa=np.asarray(fg.getchannel('A'))
|
||||
assert fa.max()==255 and fa.min()==0
|
||||
|
||||
rows=[
|
||||
('title','MOSES',1458,244,142,'P052',700),
|
||||
('verse-0','And there has not',350,1764,68,'Sanctification P052',500),
|
||||
('verse-1','arisen a prophet',350,1884,68,'Sanctification P052',500),
|
||||
('verse-2','since in Israel',350,2004,68,'Sanctification P052',500),
|
||||
('verse-3','like Moses, whom',350,2124,68,'Sanctification P052',500),
|
||||
('verse-4','the LORD knew',350,2244,68,'Sanctification P052',500),
|
||||
('verse-5','face to face',350,2364,68,'Sanctification P052',500),
|
||||
('reference','Deuteronomy 34:10 • ESV',350,2550,42,'Sanctification P052',500),
|
||||
]
|
||||
for ident,content,*_ in rows:
|
||||
fontfile=font_files['title' if ident=='title' else 'reference' if ident=='reference' else 'verse']
|
||||
charset=subprocess.check_output(['fc-query','--format=%{charset}',str(FONTS/fontfile)],text=True).split()
|
||||
ranges=[tuple(int(v,16) for v in token.split('-')) for token in charset]
|
||||
assert all(any(r[0]<=ord(char)<=r[-1] for r in ranges) for char in content), (ident, 'missing glyph')
|
||||
|
||||
texts=''.join(f'<text id="{ident}" x="{x}" y="{y}" font-family="{family}" font-weight="{weight}" font-size="{size}" text-anchor="middle" fill="#263f50">{escape(text)}</text>' for ident,text,x,y,size,family,weight in rows)
|
||||
(P/'text-preview.svg').write_text(head+texts+'</svg>');render(P/'text-preview.svg',P/'text-preview.png')
|
||||
text_image=Image.open(P/'text-preview.png').convert('RGBA');ta=np.asarray(text_image.getchannel('A'))
|
||||
assert not np.any((ta>0)&(fa>0))
|
||||
|
||||
(P/'border-backing-preview.svg').write_text(head+defs+frame+panels+'</svg>');render(P/'border-backing-preview.svg',P/'border-backing-preview.png')
|
||||
backing=np.asarray(Image.open(P/'border-backing-preview.png').convert('RGBA').getchannel('A'))
|
||||
checks=[];verse_bounds=[]
|
||||
for ident,text,x,y,size,family,weight in rows:
|
||||
node=next(v for v in texts.split('</text>') if f'id="{ident}"' in v)+'</text>'
|
||||
src=P/(ident+'-ink.svg');dst=P/(ident+'-ink.png');src.write_text(head+node+'</svg>');render(src,dst)
|
||||
alpha=np.asarray(Image.open(dst).convert('RGBA').getchannel('A'));ys,xs=np.where(alpha>0);assert len(xs)
|
||||
bounds=[int(xs.min()),int(ys.min()),int(xs.max()+1),int(ys.max()+1)]
|
||||
safe=[1050,90,1905,330] if ident=='title' else [80,2460,610,2600] if ident=='reference' else [65,1630,635,2410]
|
||||
assert safe[0]<=bounds[0] and safe[1]<=bounds[1] and bounds[2]<=safe[2] and bounds[3]<=safe[3],(ident,bounds,safe)
|
||||
assert np.all(backing[alpha>0]==255),ident
|
||||
assert not np.any((alpha>0)&(fa>0)),ident
|
||||
checks.append({'id':ident,'text':text,'baseline':y,'font':family,'weight':weight,'size':size,'inkBounds':bounds,'safeBounds':safe,'glyphPixelsOutsideOpaqueBacking':0,'foregroundIntersectionPixels':0})
|
||||
if ident.startswith('verse-'):verse_bounds.append(bounds)
|
||||
|
||||
assert ' '.join(c['text'] for c in checks if c['id'].startswith('verse-'))==json.loads((REV/'card.json').read_text())['excerpt']
|
||||
assert checks[-1]['text']==json.loads((REV/'card.json').read_text())['referenceDisplay']
|
||||
union=[min(b[0] for b in verse_bounds),min(b[1] for b in verse_bounds),max(b[2] for b in verse_bounds),max(b[3] for b in verse_bounds)]
|
||||
upper=1560;ref_top=checks[-1]['inkBounds'][1];above=union[1]-upper;below=ref_top-union[3]
|
||||
assert min(above,below)>=35 and abs(above-below)<=2,(above,below)
|
||||
|
||||
for name,include_curtain in [('composed-preview',True),('without-curtain',False)]:
|
||||
body=base+frame+curtain_frame_occlusion+panels+(curtain if include_curtain else '')+texts
|
||||
src=P/(name+'.svg');src.write_text(head+defs+body+'</svg>');render(src,P/(name+'-2000.png'))
|
||||
master=Image.open(P/(name+'-2000.png')).convert('RGB')
|
||||
master.resize((1000,1400),Image.Resampling.LANCZOS).save(P/(name+'-1000.png'))
|
||||
master.resize((500,700),Image.Resampling.LANCZOS).save(P/(name+'-500.png'))
|
||||
|
||||
frame_alpha=np.asarray(Image.open(P/'border-backing-preview.png').convert('RGBA').getchannel('A'))
|
||||
foreground_frame_overlap=int(np.count_nonzero((fa>0)&(frame_alpha>0)))
|
||||
report={
|
||||
'status':'passed',
|
||||
'scope':'v05 Normal-only vertical lower-left textile prototype',
|
||||
'canvas':[2000,2800],
|
||||
'sourceArtwork':{'path':str(ART.relative_to(REPO)),'sha256':art_hash,'unchanged':sha(ART)==art_hash},
|
||||
'template':{
|
||||
'governingIdea':'Tall lower-left textile attached to the outer frame; tent and cloud stay behind it; broad inner curve leaves Moses readable',
|
||||
'titleBackingPath':title_path,
|
||||
'verseBackingPath':verse_path,
|
||||
'compositionOrder':['source-art-with-background-tent-and-cloud','frame','source-curtain-occludes-outer-rail','opaque-vertical-backing','upper-nameplate-cloud-only','deterministic-text'],
|
||||
'curtainContour':'foreground-curtain.svg',
|
||||
'cloudUpperContour':cloud_top,
|
||||
'cloudLowerContour':cloud_low,
|
||||
'curtainAlphaBounds':list(Image.fromarray(fa).getbbox()),
|
||||
'curtainFrameOrBackingOverlapPixels':foreground_frame_overlap,
|
||||
},
|
||||
'typography':{
|
||||
'fontManifestSHA256':sha(FONTS/'manifest.json'),
|
||||
'checks':checks,
|
||||
'verseInkUnion':union,
|
||||
'upperFlourishToVerseInk':above,
|
||||
'verseInkToReferenceLineBox':below,
|
||||
'centeringDifference':abs(above-below),
|
||||
'lineBaselineGap':120,
|
||||
},
|
||||
'structuralChecks':{
|
||||
'curtainTextIntersectionPixels':int(np.count_nonzero((ta>0)&(fa>0))),
|
||||
'glyphPixelsOutsideOpaqueBacking':0,
|
||||
'artworkSHA256Unchanged':True,
|
||||
'fontGlyphCoverage':'passed',
|
||||
'fontManifestHashes':'passed',
|
||||
},
|
||||
'notPerformed':['Borderless/Textless/Boundless composition','Production finish masks','Production text masks','Harness fixture installation','GPU moving-light validation','Final card approval'],
|
||||
'staticReview':'Provisional Normal-only prototype; static inspection at card size and enlarged junctions; pending user review',
|
||||
'edgeRendering':{'geometry':'source-traced cubic Bezier paths','leadWidthMasterPixels':8,'renderScale':2,'downsample':'premultiplied RGBA LANCZOS','blur':False},
|
||||
'foregroundAdaptation':'Upper nameplate/cloud crown retained. No lower cloud, tent, curtain or Moses foreground overlay; lower depth follows source art behind the opaque vertical textile.'
|
||||
}
|
||||
(P/'preview-validation.json').write_text(json.dumps(report,indent=2)+'\n')
|
||||
assert sha(ART)==art_hash
|
||||
print(json.dumps({'status':'passed','preview':str((P/'composed-preview-1000.png').relative_to(REPO)),'curtainOverlapPixels':foreground_frame_overlap,'spacing':[above,below]},indent=2))
|
||||
|
||||
# Current source, prior horizontal layout, and the new vertical composition.
|
||||
label_font=ImageFont.truetype(str(FONTS/'P052-Bold.otf'),26)
|
||||
previous=P.parent/'normal-prototype-v2'
|
||||
for name,items in [
|
||||
('source-composed-comparison',[('Selected v05 source',ART),('Vertical textile prototype',P/'composed-preview-1000.png')]),
|
||||
('horizontal-vertical-comparison',[('Prior horizontal backing',previous/'composed-preview-1000.png'),('Tall lower-left backing',P/'composed-preview-1000.png')]),
|
||||
]:
|
||||
board=Image.new('RGB',(1500,1100),'#11161c');d=ImageDraw.Draw(board)
|
||||
for i,(label,path) in enumerate(items):
|
||||
d.text((i*750+24,18),label,font=label_font,fill='#f4ead4')
|
||||
im=Image.open(path).convert('RGB');im.thumbnail((730,1022));board.paste(im,(i*750+10,63))
|
||||
board.save(P/(name+'.png'))
|
||||
master=Image.open(P/'composed-preview-2000.png').convert('RGB')
|
||||
detail=Image.new('RGB',(1400,1450),'#11161c');d=ImageDraw.Draw(detail)
|
||||
for label,box,origin,size in [
|
||||
('Textile, lettering and inner curve',(0,1340,820,2800),(20,65),(730,1300)),
|
||||
('Panel / robe separation',(490,1380,890,2500),(845,65),(520,1250)),
|
||||
]:
|
||||
d.text((origin[0],20),label,font=label_font,fill='#f4ead4')
|
||||
im=master.crop(box);im.thumbnail(size);detail.paste(im,origin)
|
||||
detail.save(P/'panel-robe-junction-detail.png')
|
||||
assert sha(ART)==json.loads((REV/'card.json').read_text())['artSHA256']
|
||||
assert not np.any(fa[600:])
|
||||
prior=np.asarray(Image.open(previous/'composed-preview-2000.png'))
|
||||
current=np.asarray(Image.open(P/'composed-preview-2000.png'))
|
||||
assert np.array_equal(current[:1300],prior[:1300])
|
||||
report['structuralChecks']['upper1300RowsIdenticalToPrior']=True
|
||||
report['structuralChecks']['lowerForegroundPixels']=int(np.count_nonzero(fa[600:]))
|
||||
report['structuralChecks']['sourceMatchesV05CardSHA256']=True
|
||||
report['structuralChecks']['secondPostDrawn']=False
|
||||
report['template']['layoutReference']=str(previous.relative_to(REPO))
|
||||
report['template']['lowerDepth']='Tent, cloud and curtain remain source background behind the opaque vertical textile; no lower illustrated overflow'
|
||||
report['template']['lowerTextLayout']='Six verse lines plus one reference line, centered by actual glyph bounds and measured vertical gaps'
|
||||
(P/'preview-validation.json').write_text(json.dumps(report,indent=2)+'\n')
|
||||
|
After Width: | Height: | Size: 2.2 MiB |