make-portraits: draw the body plan, not the species

portraitSvg read `spec.species` for both the silhouette and the hit-location
table. Every creature with a non-human body carries `species: "baseline"` and
says what it is in `bodyPlan`, so the guildhall gryphon's own generated
portrait described it as "baseline, 7 hit locations" and drew a human figure
for an eagle-lion with wings. 48 creatures were affected — 32 quadrupeds and
16 winged.

It is the same defect, in the same shape, as the anatomy clause in
mj-queue.mjs: that one read `species` where the shape lives in `bodyPlan`,
and drew a wingless gryphon. I fixed it there earlier tonight and did not
look one file over, in the other tool that draws a creature from its
statblock. R-264 settled this rule for the harness — `bodyPlan ?? species` —
and build-packs:294 writes it to the actor's speciesProfile.

Only two files change on disk, because 46 of the 48 already have hand-drawn
plates and are correctly skipped. Those two are martlet and moor_cat, whose
Midjourney art has now failed twice, so they are exactly the creatures
falling back on the generated figure: martlet is winged with 10 locations and
moor_cat quadruped with 8, where both were human before.

The other 139 rewrote byte-identically, which is what a deterministic
generator should do and is worth knowing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
slaguru666
2026-09-23 01:13:30 +01:00
co-authored by Claude Opus 5
parent e7a584cc60
commit 45df79d712
7 changed files with 47 additions and 31 deletions
+13 -10
View File
@@ -1,17 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256">
<title>The martlet</title>
<desc>The martlet — baseline, SIZ 3, 7 hit locations, natural armour 0, antique. Generated by tools/make-portraits.mjs; do not edit by hand.</desc>
<desc>The martlet — winged, SIZ 3, 10 hit locations, natural armour 0, antique. Generated by tools/make-portraits.mjs; do not edit by hand.</desc>
<rect width="256" height="256" rx="30" fill="hsl(22 38% 27%)"/>
<path d="M0 0h52L0 52z" fill="hsl(22 44% 17%)"/>
<ellipse cx="128.0" cy="230.4" rx="30.3" ry="4.2" fill="hsl(22 44% 17%)" opacity="0.8"/>
<g transform="translate(98.87 132.66) scale(0.4855 0.4987)">
<ellipse cx="60" cy="21" rx="15" ry="17" fill="hsl(22 50% 78%)" stroke="hsl(22 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="40" y="42" width="40" height="40" rx="9" fill="hsl(22 50% 78%)" stroke="hsl(22 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="44" y="84" width="32" height="30" rx="8" fill="hsl(22 50% 78%)" stroke="hsl(22 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="18" y="45" width="17" height="64" rx="8" fill="hsl(22 50% 78%)" stroke="hsl(22 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="85" y="45" width="17" height="64" rx="8" fill="hsl(22 50% 78%)" stroke="hsl(22 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="43" y="117" width="15" height="72" rx="7" fill="hsl(22 50% 78%)" stroke="hsl(22 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="62" y="117" width="15" height="72" rx="7" fill="hsl(22 50% 78%)" stroke="hsl(22 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<ellipse cx="128.0" cy="230.4" rx="78.0" ry="10.9" fill="hsl(22 44% 17%)" opacity="0.8"/>
<g transform="translate(53.03 132.66) scale(0.7209 0.7405)">
<path d="M96 40 L44 6 L18 26 L74 52 Z" fill="hsl(22 50% 78%)" stroke="hsl(22 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<path d="M100 44 L58 92 L82 116 L114 62 Z" fill="hsl(22 50% 78%)" stroke="hsl(22 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="34" y="44" width="56" height="40" rx="13" fill="hsl(22 50% 78%)" stroke="hsl(22 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="86" y="40" width="60" height="46" rx="13" fill="hsl(22 50% 78%)" stroke="hsl(22 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<path d="M138 46 L172 26 L184 42 L150 64 Z" fill="hsl(22 50% 78%)" stroke="hsl(22 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<ellipse cx="184" cy="30" rx="18" ry="13" fill="hsl(22 50% 78%)" stroke="hsl(22 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="42" y="82" width="12" height="40" rx="6" fill="hsl(22 50% 78%)" stroke="hsl(22 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="60" y="82" width="12" height="40" rx="6" fill="hsl(22 50% 78%)" stroke="hsl(22 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="102" y="84" width="12" height="38" rx="6" fill="hsl(22 50% 78%)" stroke="hsl(22 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="120" y="84" width="12" height="38" rx="6" fill="hsl(22 50% 78%)" stroke="hsl(22 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
</g>
<g transform="translate(194 198) scale(1.15)" opacity="0.92"><path d="M6 8c8 6 12 16 12 30M18 6c8 6 12 16 12 30M30 8c6 6 9 14 9 26" fill="none" stroke="hsl(22 38% 48%)" stroke-width="4" stroke-linecap="round"/></g>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

+19 -17
View File
@@ -1,24 +1,26 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256">
<title>The moor cat</title>
<desc>The moor cat — baseline, SIZ 14, 7 hit locations, natural armour 2, antique. Generated by tools/make-portraits.mjs; do not edit by hand.</desc>
<desc>The moor cat — quadruped, SIZ 14, 8 hit locations, natural armour 2, antique. Generated by tools/make-portraits.mjs; do not edit by hand.</desc>
<rect width="256" height="256" rx="30" fill="hsl(33 38% 27%)"/>
<path d="M0 0h52L0 52z" fill="hsl(33 44% 17%)"/>
<ellipse cx="128.0" cy="230.4" rx="57.2" ry="8.0" fill="hsl(33 44% 17%)" opacity="0.8"/>
<g transform="translate(72.97 64.43) scale(0.9172 0.8468)">
<ellipse cx="60" cy="21" rx="15" ry="17" fill="hsl(33 50% 78%)" stroke="hsl(33 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="40" y="42" width="40" height="40" rx="9" fill="hsl(33 50% 78%)" stroke="hsl(33 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="44" y="84" width="32" height="30" rx="8" fill="hsl(33 50% 78%)" stroke="hsl(33 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="18" y="45" width="17" height="64" rx="8" fill="hsl(33 50% 78%)" stroke="hsl(33 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="85" y="45" width="17" height="64" rx="8" fill="hsl(33 50% 78%)" stroke="hsl(33 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="43" y="117" width="15" height="72" rx="7" fill="hsl(33 50% 78%)" stroke="hsl(33 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="62" y="117" width="15" height="72" rx="7" fill="hsl(33 50% 78%)" stroke="hsl(33 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<ellipse cx="60" cy="21" rx="15" ry="17" fill="none" stroke="hsl(33 78% 62%)" stroke-width="3.70" opacity="0.46" stroke-linejoin="round"/>
<rect x="40" y="42" width="40" height="40" rx="9" fill="none" stroke="hsl(33 78% 62%)" stroke-width="3.70" opacity="0.46" stroke-linejoin="round"/>
<rect x="44" y="84" width="32" height="30" rx="8" fill="none" stroke="hsl(33 78% 62%)" stroke-width="3.70" opacity="0.46" stroke-linejoin="round"/>
<rect x="18" y="45" width="17" height="64" rx="8" fill="none" stroke="hsl(33 78% 62%)" stroke-width="3.70" opacity="0.46" stroke-linejoin="round"/>
<rect x="85" y="45" width="17" height="64" rx="8" fill="none" stroke="hsl(33 78% 62%)" stroke-width="3.70" opacity="0.46" stroke-linejoin="round"/>
<rect x="43" y="117" width="15" height="72" rx="7" fill="none" stroke="hsl(33 78% 62%)" stroke-width="3.70" opacity="0.46" stroke-linejoin="round"/>
<rect x="62" y="117" width="15" height="72" rx="7" fill="none" stroke="hsl(33 78% 62%)" stroke-width="3.70" opacity="0.46" stroke-linejoin="round"/>
<ellipse cx="128.0" cy="230.4" rx="147.3" ry="20.6" fill="hsl(33 44% 17%)" opacity="0.8"/>
<g transform="translate(-13.64 64.43) scale(1.3620 1.2573)">
<rect x="34" y="42" width="58" height="44" rx="14" fill="hsl(33 50% 78%)" stroke="hsl(33 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="88" y="38" width="62" height="50" rx="14" fill="hsl(33 50% 78%)" stroke="hsl(33 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<path d="M142 44 L176 24 L188 40 L154 62 Z" fill="hsl(33 50% 78%)" stroke="hsl(33 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<ellipse cx="186" cy="28" rx="19" ry="14" fill="hsl(33 50% 78%)" stroke="hsl(33 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="40" y="84" width="13" height="44" rx="6" fill="hsl(33 50% 78%)" stroke="hsl(33 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="60" y="84" width="13" height="44" rx="6" fill="hsl(33 50% 78%)" stroke="hsl(33 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="104" y="86" width="13" height="42" rx="6" fill="hsl(33 50% 78%)" stroke="hsl(33 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="124" y="86" width="13" height="42" rx="6" fill="hsl(33 50% 78%)" stroke="hsl(33 44% 17%)" stroke-width="3" stroke-linejoin="round"/>
<rect x="34" y="42" width="58" height="44" rx="14" fill="none" stroke="hsl(33 78% 62%)" stroke-width="3.70" opacity="0.46" stroke-linejoin="round"/>
<rect x="88" y="38" width="62" height="50" rx="14" fill="none" stroke="hsl(33 78% 62%)" stroke-width="3.70" opacity="0.46" stroke-linejoin="round"/>
<path d="M142 44 L176 24 L188 40 L154 62 Z" fill="none" stroke="hsl(33 78% 62%)" stroke-width="3.70" opacity="0.46" stroke-linejoin="round"/>
<ellipse cx="186" cy="28" rx="19" ry="14" fill="none" stroke="hsl(33 78% 62%)" stroke-width="3.70" opacity="0.46" stroke-linejoin="round"/>
<rect x="40" y="84" width="13" height="44" rx="6" fill="none" stroke="hsl(33 78% 62%)" stroke-width="3.70" opacity="0.46" stroke-linejoin="round"/>
<rect x="60" y="84" width="13" height="44" rx="6" fill="none" stroke="hsl(33 78% 62%)" stroke-width="3.70" opacity="0.46" stroke-linejoin="round"/>
<rect x="104" y="86" width="13" height="42" rx="6" fill="none" stroke="hsl(33 78% 62%)" stroke-width="3.70" opacity="0.46" stroke-linejoin="round"/>
<rect x="124" y="86" width="13" height="42" rx="6" fill="none" stroke="hsl(33 78% 62%)" stroke-width="3.70" opacity="0.46" stroke-linejoin="round"/>
</g>
<g transform="translate(194 198) scale(1.15)" opacity="0.92"><path d="M6 8c8 6 12 16 12 30M18 6c8 6 12 16 12 30M30 8c6 6 9 14 9 26" fill="none" stroke="hsl(33 38% 48%)" stroke-width="4" stroke-linecap="round"/></g>
<rect x="6" y="6" width="244" height="244" rx="26" fill="none" stroke="hsl(33 78% 62%)" stroke-width="5"/>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

+1 -1
View File
@@ -1 +1 @@
MANIFEST-000013
MANIFEST-000004
Binary file not shown.
Binary file not shown.
+14 -3
View File
@@ -250,9 +250,20 @@ function part(shape, attrs) {
}
export function portraitSvg(spec, kind = "npc") {
/* THE BODY PLAN, not the species — `spec.bodyPlan ?? spec.species`, which is the rule
R-264 settled for the harness and build-packs:294 writes to the actor's
speciesProfile. This read `species` alone, and every creature with a non-baseline
body has `species: "baseline"`: the guildhall gryphon's own generated portrait
described it as "baseline, 7 hit locations" and drew a human silhouette for an
eagle-lion with wings. 48 creatures were affected, 32 quadrupeds and 16 winged.
The same defect, in the same shape, as the one in mj-queue's anatomy clause — fixed
there earlier and not looked for here, one file away, in the other tool that draws a
creature from its statblock. */
const species = spec.species ?? "baseline";
const art = BODY_ART[species] ?? BODY_ART.baseline;
const table = LOCATION_TABLES[species] ?? LOCATION_TABLES.baseline;
const plan = spec.bodyPlan ?? species;
const art = BODY_ART[plan] ?? BODY_ART[species] ?? BODY_ART.baseline;
const table = LOCATION_TABLES[plan] ?? LOCATION_TABLES[species] ?? LOCATION_TABLES.baseline;
const era = eraOf(spec);
const muted = era === "antique";
@@ -329,7 +340,7 @@ export function portraitSvg(spec, kind = "npc") {
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${S} ${S}" width="${S}" height="${S}">
<title>${esc(spec.name)}</title>
<desc>${esc(spec.name)} — ${species}, SIZ ${spec.ch?.siz ?? "?"}, `
<desc>${esc(spec.name)} — ${plan}, SIZ ${spec.ch?.siz ?? "?"}, `
+ `${table.locations.length} hit locations, natural armour ${nat}, ${era}. `
+ `Generated by tools/make-portraits.mjs; do not edit by hand.</desc>
<rect width="${S}" height="${S}" rx="30" fill="${bg1}"/>