885 lines
54 KiB
JavaScript
885 lines
54 KiB
JavaScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import vm from "node:vm";
|
|
|
|
const root = process.cwd();
|
|
const mainPath = path.join(root, "main.js");
|
|
const cardsRoot = path.join(root, "assets", "cards");
|
|
const promptsDir = path.join(cardsRoot, "prompts");
|
|
|
|
function makeElement() {
|
|
return {
|
|
className: "",
|
|
dataset: {},
|
|
style: { setProperty() {} },
|
|
children: [],
|
|
textContent: "",
|
|
innerHTML: "",
|
|
addEventListener() {},
|
|
replaceChildren(...nodes) {
|
|
this.children = nodes;
|
|
},
|
|
prepend(node) {
|
|
this.children.unshift(node);
|
|
},
|
|
remove() {},
|
|
};
|
|
}
|
|
|
|
function buildDomStub() {
|
|
const nodes = new Map();
|
|
return {
|
|
querySelector(selector) {
|
|
if (!nodes.has(selector)) nodes.set(selector, makeElement());
|
|
if (selector === "#emptyCardTemplate") {
|
|
nodes.get(selector).content = { cloneNode: () => makeElement() };
|
|
}
|
|
return nodes.get(selector);
|
|
},
|
|
createElement: makeElement,
|
|
};
|
|
}
|
|
|
|
function damageSpec(stage) {
|
|
const specs = [
|
|
{
|
|
label: "normal",
|
|
visual: "intact armor and clothing, pristine battle-ready silhouette",
|
|
impactIntensity:
|
|
"no impact damage yet; the card should look fully prepared before combat",
|
|
exposure:
|
|
"no battle-damage exposure yet; mature silhouette is visible through intact adult outfit design",
|
|
poseNote: "confident ready stance",
|
|
expression:
|
|
"controlled confident expression, eyes focused, no distress",
|
|
missingArmor:
|
|
"no missing armor or clothing; all equipment is intact",
|
|
destructionFloor:
|
|
"0% armor/equipment destruction: the baseline card must be fully intact before combat",
|
|
damageCoverage:
|
|
"all outfit and armor zones are intact and clean; no damage distribution is needed at this stage",
|
|
protectedZoneDamage:
|
|
"no protected-zone damage yet; chest armor or bodice, hip armor, rear skirt or coat panels, and seat armor are intact before combat",
|
|
baseLayerDamage:
|
|
"no base-layer damage yet; support underlayers, bodysuit panels, and lower coverage panels are intact before combat",
|
|
camera:
|
|
"low-angle side-front or three-quarter side-forward view for clear character identity without flat frontal dominance",
|
|
cameraConstraint:
|
|
"camera choice should include side/rear/low/high rotations across the deck so not every card uses direct frontal framing.",
|
|
},
|
|
{
|
|
label: "light-damage",
|
|
visual:
|
|
"light battle damage from a clear opening hit: scratched and lightly dented armor or modern gear, torn jacket or cloak edges, scuffed gloves, one shoulder strap loosened, cracked small accessory or visor edge, small visible exposure around collarbone, shoulder edge, forearm, waistline, or upper thigh",
|
|
impactIntensity:
|
|
"light damage must still look like a real strike landed: include visible scuffs, dents, loosened straps, and at least one broken small accessory, while keeping the character mostly combat-ready",
|
|
materialPacing:
|
|
"material-aware opening damage: cloth, ribbons, capes, thin bodysuit edges, and leather straps may tear or fray early; metal armor, hard sci-fi plates, helmets, and mechanical gear should mostly dent, scratch, crack at fasteners, or bend slightly instead of vanishing all at once",
|
|
exposure:
|
|
"low but noticeable battle-damage exposure: collarbone, shoulder edge, forearm, waistline, or upper thigh should be visible from damaged outer layers",
|
|
poseNote: "still composed but reacting to an opening exchange",
|
|
expression:
|
|
"annoyed grimace, narrowed eyes, clenched jaw, clearly frustrated by the first hit",
|
|
missingArmor:
|
|
"minor loss of outer accessories only: torn glove edge, loosened shoulder strap, scuffed boot armor",
|
|
destructionFloor:
|
|
"opening-hit destruction only: keep the character mostly combat-ready, but make the first visible armor/equipment failures clear enough to establish later cumulative damage",
|
|
damageCoverage:
|
|
"light damage should be distributed across at least three visible zones, such as a shoulder strap, sleeve or glove, waist edge, thigh guard, boot guard, visor, or small accessory, so no single isolated tear carries the whole stage",
|
|
protectedZoneDamage:
|
|
"light damage may include small scuffs, dents, or loosened fasteners on chest armor or bodice edges, hip armor, rear skirt or coat panels, or seat armor, while preserving opaque coverage and keeping the adult pinup combat context clear",
|
|
baseLayerDamage:
|
|
"light damage may scuff or loosen visible support underlayers, bodysuit edges, or lower coverage seams, but they mostly remain intact at this stage",
|
|
camera:
|
|
"low/high/side mixed angles, enough to show both face and first armor damage without repeated frontal read",
|
|
cameraConstraint:
|
|
"avoid pure straight-on front framing when possible; prioritize side, rear, low-angle, or high-angle with frontal only if readability is essential.",
|
|
},
|
|
{
|
|
label: "medium-damage",
|
|
visual:
|
|
"moderate battle damage from repeated heavy hits: cracked shoulder armor or protective gear, bent plates, torn outer sleeves, loosened or snapped belts, damaged protective plates, exposed forearms, bare shoulder, torn neckline or side seam, clearly exposed abdomen and side torso, exposed upper thigh and hip line through broken outer layers, adult pinup silhouette already unmistakable, and no clean replacement underlayer hiding the damage",
|
|
impactIntensity:
|
|
"medium damage must read stronger than cosmetic tearing: show bent metal, snapped straps, cracked guards, separated seams, bruising-free impact marks on gear, and a posture that clearly reacts to forceful combat impact",
|
|
materialPacing:
|
|
"material-aware medium damage: compared with the damage-0 baseline, cloth layers, capes, skirts, ribbons, straps, and soft leather should show broad cut-open panels, snapped seams, and missing sections; metal or rigid armor should survive longer but must show real failures such as bent plates, cracked joins, broken rivets, detached sections, crushed edges, and disabled mechanisms",
|
|
exposure:
|
|
"clear adult damaged-costume presentation: large opened panels should reveal non-sensitive areas such as shoulder, side torso, abdomen, upper thigh, hip line, back angle, and mature upper-body silhouette together, not hidden by intact modest underlayers or new replacement coverage panels",
|
|
poseNote: "strained but still fighting with focus",
|
|
expression:
|
|
"painful frown, wet eyes, anger and disbelief, trying not to cry while staying in the fight; if hit in the abdomen, show a gasping open mouth and strained breathing",
|
|
missingArmor:
|
|
"visible loss around hands, forearms, one shoulder, boot guards, and side-abdomen armor; damage suggests repeated heavy impacts",
|
|
destructionFloor:
|
|
"minimum 50% visible armor/equipment destruction relative to damage-0: at least half of the original outfit, armor, straps, plates, weapons, and equipment must no longer look intact. Dirt, soot, scratches, thin crack lines, background debris, pose changes, and floating fragments do not count toward this 50% unless the original worn gear itself is visibly torn away, bent, detached, disabled, or hanging loose across the body",
|
|
damageCoverage:
|
|
"medium damage should visibly affect upper body, arms, abdomen or waist, hips or thighs, and at least one boot or leg guard; do not leave a major clothing or armor region looking untouched",
|
|
protectedZoneDamage:
|
|
"medium damage must not avoid torso or rear-body gear: chest armor or bodice panels, side armor edges, hip plates, rear waist plates, seat armor, back skirt panels, or coat tails should show cracks, torn seams, dents, missing outer trim, or broken straps, while opaque remnants preserve required coverage",
|
|
baseLayerDamage:
|
|
"medium damage must not leave support layers, suit liners, lower wraps, or other coverage underlayers looking pristine; show scuffed fabric, torn edges, stretched seams, snapped trim, soot, dents in reinforced liners, and uneven deformation while opaque coverage remains. If the image reads like clean replacement coverage or teen-safe re-covering after the outer gear breaks, it fails",
|
|
camera:
|
|
"side view, strong three-quarter, or low-angle side frame to keep shoulder and abdomen damage visible",
|
|
cameraConstraint:
|
|
"must not be primarily straight front. Keep side/rear/low/high angles dominant; frontal only when continuity breaks otherwise.",
|
|
},
|
|
{
|
|
label: "heavy-damage",
|
|
visual:
|
|
"heavy battle damage after a brutal exchange: broken and warped outer armor or modern gear, large cut-open mantle/jacket/stage outfit panels, fractured bracers, missing glove armor, crushed or split waist plates, damaged boots or unarmored ankle on one side, adult-only non-explicit damaged-costume pinup presentation with opened shoulders, back, abdomen/midriff, hips, upper thighs, mature torso silhouette, and only battered opaque costume remnants through destroyed outer armor",
|
|
impactIntensity:
|
|
"heavy damage should look severe and unmistakable: armor plates bent out of shape, straps snapped, seams split open, bracers fractured, boots damaged, weapon accessories partly broken, and the character's balance visibly failing from repeated impacts",
|
|
materialPacing:
|
|
"material-aware heavy damage: soft materials should be opened by broad weapon cuts or missing as large panels by now; leather should split in long directional cuts, curl, and lose straps; metal armor should not simply disappear but should show multiple large plates cracked, warped, unlatched, hanging by broken fasteners, or fallen as readable chunks near the character",
|
|
exposure:
|
|
"push to adult-only non-explicit damaged-costume presentation: opened shoulders, back, torso silhouette, abdomen, hips, upper thighs, and a few large battered outfit remnants should make the impact feel severe and adult-only, with no clean replacement coverage layer, censorship effect, hair curtain, or pristine fabric used to hide the damage",
|
|
poseNote: "wounded, guarded, barely keeping balance after a brutal exchange",
|
|
expression:
|
|
"tearful angry grimace, trembling lower lip, brows pinched, resentful eyes that show she hates losing; abdominal-hit reaction such as open mouth, gasping, hunched posture, and a small combat-pain saliva strand is allowed",
|
|
missingArmor:
|
|
"major loss around hands, feet, shoulders, abdomen, waist plates, and bracers; broken pieces scattered nearby",
|
|
destructionFloor:
|
|
"minimum 70% visible armor/equipment destruction relative to damage-0: most of the original protective outfit and carried equipment must be broken, missing, warped, unlatched, disabled, or hanging loose. Large intact suit areas, pristine torso/hip panels, clean boots, or mostly complete armor silhouettes fail this stage even if there are many scratches, dirt marks, or background fragments",
|
|
damageCoverage:
|
|
"heavy damage must be full-body and balanced: headgear or visor, shoulders, chest armor or bodice, back or cloak, sleeves, gloves, abdomen, waist, hips, thighs, knees, boots, and weapon accessories should all show cracks, tears, missing outer plates, scuffs, or deformation; avoid leaving any major region pristine while another region is over-destroyed",
|
|
protectedZoneDamage:
|
|
"heavy damage must visibly affect torso and rear-body equipment too: dented or split chest armor, torn bodice seams, damaged torso straps, cracked side armor edges, broken hip plates, torn rear skirt or coat panels, crushed seat armor, and snapped rear waist fasteners should be present as combat damage with adult pinup appeal; never leave torso or rear/hip regions unusually pristine compared with the rest of the body",
|
|
baseLayerDamage:
|
|
"heavy damage must visibly affect support underlayers and coverage panels too: support pieces, suit liners, lower wraps, and coverage panels should be torn, scuffed, warped, soot-marked, strap-broken, or partially covered by broken armor remnants. In this stage they should read as battered opaque remnants, not clean replacement coverage; they must still preserve non-explicit opaque coverage",
|
|
camera:
|
|
"dramatic low-angle side/rear combination, high-angle recoil moments, or over-the-shoulder brutality while keeping face partially visible",
|
|
cameraConstraint:
|
|
"intentionally use low-angle or side/rear framing for brutality; limit frontal composition unless needed for continuity.",
|
|
},
|
|
{
|
|
label: "defeated",
|
|
visual:
|
|
"defeated state after overwhelming combat: kneeling, collapsed, or sitting back after battle, armor system barely holding together, broken armor pieces around them, torn outer garment layers, missing hand and foot armor, shattered shoulder or waist plates, maximum non-explicit adult damaged-costume battle pinup presentation with opened shoulders, back or side-back, abdomen/midriff, hips, upper thighs, mature torso silhouette, and only battered opaque remnants through destroyed outer armor",
|
|
impactIntensity:
|
|
"defeated damage should be the strongest non-gore stage: the outfit and armor should look comprehensively wrecked by combat, with collapsed straps, broken fasteners, crushed guards, missing outer plates, disabled weapon pieces, and a posture that clearly communicates she can no longer keep fighting",
|
|
materialPacing:
|
|
"material-aware defeated damage: cloth, capes, skirts, ribbons, and soft suit layers should remain only as battered opaque remnants; leather should be split and strap-broken; metal and mechanical armor should be comprehensively ruined through crushed sections, torn hinges, missing plates, disabled joints, broken weapons, and scattered large pieces rather than cosmetic scuffs",
|
|
exposure:
|
|
"maximum non-explicit adult damaged-costume battle presentation for the defeated card: broad opened shoulder/back/side torso/abdomen/hip/thigh panels at once, a few dramatic large battered costume remnants, adult defeated pinup body language, and emphasized mature silhouette while staying framed as combat defeat rather than an intimate act; do not add clean replacement coverage layers, censorship effects, hair curtains, or pristine replacement panels",
|
|
poseNote: "battle-worn, emotionally overwhelmed, clearly defeated, no gore",
|
|
expression:
|
|
"frustrated tears, unfair-loss expression, clenched teeth or trembling open mouth, tear tracks, angry shame rather than calm dignity; defeated abdominal-hit pain reaction with open mouth, drool/saliva at the lip, and labored breathing is allowed when framed as combat pain",
|
|
missingArmor:
|
|
"the most intense equipment loss: shattered shoulder plates, missing bracers and glove armor, broken boot guards, torn waist and abdomen armor, scattered fragments",
|
|
destructionFloor:
|
|
"minimum 85% visible armor/equipment destruction relative to damage-0: the original armor, outfit, straps, weapons, and equipment system should be comprehensively wrecked, with only battered opaque remnants and broken supports still covering intimate areas. If more than a small minority of the original suit or armor reads as clean, continuous, or functional, the defeated card fails",
|
|
damageCoverage:
|
|
"defeated damage must read across the entire outfit and armor system: damaged headgear, shoulders, chest armor or bodice, back or cloak, sleeves, gloves, abdomen, waist, hips, thighs, knees, boots, and weapon accessories; every major zone should show battle damage while opaque remnants still cover intimate areas",
|
|
protectedZoneDamage:
|
|
"defeated damage must include the same destruction level on torso and rear-body gear as everywhere else: warped chest armor or torn bodice panels, broken support straps, damaged torso-edge armor without explicit closeup focus, fractured hip plates, torn rear skirt or coat panels, crushed seat armor, and broken rear waist fasteners; these areas must not look selectively protected or repaired, while opaque remnants preserve required coverage",
|
|
baseLayerDamage:
|
|
"defeated damage must make support underlayers and coverage panels look battle-wrecked too: support pieces, suit liners, lower wraps, and coverage panels should be torn at edges, dirtied, deformed, strap-broken, soot-marked, and partly overlapped by shattered armor fragments. Keep only battered opaque remnants where coverage is required; never leave pristine replacement coverage",
|
|
camera:
|
|
"rear three-quarter, side-back, or high/low side view; show back/shoulder/cloak damage while keeping enough face profile to read pain",
|
|
cameraConstraint:
|
|
"avoid direct front as much as possible. Use side/rear, low, or high framing to show defeat collapse and exposed damage zones.",
|
|
},
|
|
];
|
|
return specs[stage];
|
|
}
|
|
|
|
const CAMERA_ORIENTATION_PLAN = {
|
|
normal: ["low", "side", "high", "low", "rear", "high", "side", "low", "rear", "high"],
|
|
"light-damage": ["low", "high", "side", "rear", "high", "low", "side", "rear", "high", "low", "side"],
|
|
"medium-damage": ["side", "low", "high", "rear", "side", "high", "low", "rear", "high", "side", "low", "rear"],
|
|
"heavy-damage": ["low", "rear", "high", "side", "high", "low", "rear", "low", "high", "side", "rear", "high"],
|
|
defeated: ["rear", "low", "high", "side", "high", "rear", "low", "side", "high", "low", "rear", "side"],
|
|
};
|
|
|
|
const CAMERA_VARIANTS = {
|
|
normal: {
|
|
front: [
|
|
"low side-three-quarter composition with a shoulder twist that keeps the body angled",
|
|
"three-quarter profile composition with one foot forward and hips leading the movement",
|
|
"slanted side-front composition where the face stays readable through a short turn",
|
|
],
|
|
side: [
|
|
"strong profile side shot with one step forward and hips leading the movement",
|
|
"45-degree profile turn with the leading shoulder pushed toward camera",
|
|
"three-quarter side stance with left foot forward and shoulders angled",
|
|
],
|
|
low: [
|
|
"low-angle side-knee view with explosive weight shift and open shoulder line",
|
|
"low side angle showing bent knee, shoulder hit, and torn details",
|
|
"extreme low-angle ground-lean with bent knees and body driving forward",
|
|
],
|
|
high: [
|
|
"high-angle three-quarter top-down look, knees and torso readable while keeping face and hands visible",
|
|
"high-angle forward lean with body weight dropping toward the lens",
|
|
"wide high-angle look over the shoulder showing posture and opening damage paths",
|
|
],
|
|
rear: [
|
|
"rearward shoulder-lean three-quarter composition with chest and hips still readable",
|
|
"rearward quarter-angle from behind the shoulder with face still readable",
|
|
"low side-rear shot with hips turning into the lens",
|
|
],
|
|
},
|
|
"light-damage": {
|
|
front: [
|
|
"front-canted three-quarter read with shoulders turned against impact and one side stepping in",
|
|
"low-front slant with torso dropping into counter step and side-shifted hips",
|
|
"eye-level angled guard frame that keeps the strike angle visible in the shoulder and waist",
|
|
],
|
|
side: [
|
|
"side pivot camera with shoulder turn and counter-step body shift",
|
|
"off-center side-frame with one foot forward and hips leading",
|
|
"low-angle side shot with bent knee, shoulder hit, and torn cloth edge",
|
|
],
|
|
low: [
|
|
"low-angle side composition with character descending into impact posture",
|
|
"low side angle with a defensive crouch and torn cuff reveal",
|
|
"low-side recoil angle with split-leg recovery and body pullback",
|
|
],
|
|
high: [
|
|
"high-angle diagonal from above, revealing shoulder and torso recoil details",
|
|
"high side-angle read of first armor loss and leaning recovery",
|
|
"high front shoulder line view with hands reacting to first strike",
|
|
],
|
|
rear: [
|
|
"rearward quarter-angle from behind the shoulder with face still readable",
|
|
"shoulder-lean rear shot with first torn skirt edge opening toward camera",
|
|
"three-quarter rear-left angle revealing first back-side seam stress",
|
|
],
|
|
},
|
|
"medium-damage": {
|
|
front: [
|
|
"front-angled three-quarter combat lock with shoulder and hip crossing against a force",
|
|
"low-front recovery turn with one leg absorbing impact and waist twisted for balance",
|
|
"angled forward body-lock pose as the torso rotates into a side-facing response",
|
|
],
|
|
side: [
|
|
"strong side profile, body twisted from force so shoulder, torso, and abdomen damage are obvious",
|
|
"rearward three-quarter side-forward action frame while keeping enough face profile",
|
|
"low-stance three-quarter side view that reveals back-side torso damage and damaged hips",
|
|
],
|
|
low: [
|
|
"high-impact low-side body twist at knee level, impact showing heavy skirt edge tear",
|
|
"dramatic low-angle body twist shot with one leg braced, one leg carrying weight",
|
|
"low side frame while balance collapses backward",
|
|
],
|
|
high: [
|
|
"high-angle side shot with visible hip turn, split seams, and unstable footing",
|
|
"overhead side-back angle showing weapon arc and open midriff damage path",
|
|
"high three-quarter side frame with torso recoil and exposed panels",
|
|
],
|
|
rear: [
|
|
"rearward three-quarter action angle while keeping enough face profile and impact direction",
|
|
"side-back turn showing cracked side armor and cloth tearing around hips",
|
|
"rear three-quarter frame with shoulder edge and back damage visibility",
|
|
],
|
|
},
|
|
"heavy-damage": {
|
|
front: [
|
|
"low-front collapse angle where one shoulder drops while body rotates around a failing knee",
|
|
"aggressive forward-lean collapse with side weight shift and heavy gear drag",
|
|
"frontal-to-side rebound pose with fractured guard and broken upper-body support",
|
|
],
|
|
side: [
|
|
"dramatic side view from near ground, torn shoulder and torso damage clearly visible",
|
|
"wide extreme-side camera with body falling forward and equipment flying",
|
|
"over-the-shoulder angle with bent lower body and exposed damaged limbs",
|
|
],
|
|
low: [
|
|
"low-side angle with broken gear dragging across lower body and back",
|
|
"dramatic low side shot with leg dropped, shoulders and hips broken open",
|
|
"ground-skimming low angle on a heavy counter-fall pose",
|
|
],
|
|
high: [
|
|
"high-angle rear-three-quarter frame to reveal damaged back, cloak, and hip areas",
|
|
"high-angle side-rear read with torso folding and open armor seams",
|
|
"overhead side-back angle showing collapse direction and large damage arcs",
|
|
],
|
|
rear: [
|
|
"rear three-quarter side-back angle from below, showing opened back and waist zones",
|
|
"rear shoulder-drop shot with armor plates displaced backward",
|
|
"back-twisted combat frame exposing rear armor and collapsed side waist",
|
|
],
|
|
},
|
|
defeated: {
|
|
front: [
|
|
"front-canted defeated collapse with one knee down and torso dropping into recoil",
|
|
"kneeling recoil frame with hip-heavy fall and waist opening",
|
|
"collapsed chest-forward posture with profile drift and controlled face read",
|
|
],
|
|
side: [
|
|
"low angle side-back shot with fallen armor pieces and defeated body posture",
|
|
"rear three-quarter, side-back, or very low side view; show damaged back/shoulder/cloak with readable face profile",
|
|
"wide side collapse showing opened torso and collapsed lower guard",
|
|
],
|
|
low: [
|
|
"ground-level shot looking up past bent knees and broken limbs",
|
|
"extreme low side sweep showing the full body giving out of balance",
|
|
"low side collapse with one leg pinned back and armor stripped near hips",
|
|
],
|
|
high: [
|
|
"high side angle while collapsing onto a knee, exposing abdomen and hip-line destruction",
|
|
"overhead-to-side hybrid angle showing broken stance, torn outfit remnants, and pain posture",
|
|
"high-angle side sweep showing full-body give-up posture",
|
|
],
|
|
rear: [
|
|
"rear three-quarter, side-back, or very low side view showing damaged back/shoulder/cloak with readable face profile",
|
|
"side-back defeated fall with broken armor panels and limp arms",
|
|
"rear collapse frame with shoulder line and torso giving way",
|
|
],
|
|
},
|
|
};
|
|
|
|
const CAMERA_ORIENTATION_RULES = {
|
|
normal:
|
|
"Use only side/rear/low/high framing. Never use a pure front-on head-on lock. Across one character's 5 stages, make sure at least one low-angle and one high-angle shot appear.",
|
|
"light-damage":
|
|
"Prioritize low-angle, rear, side, and high-angle framing. Frontal reads are disallowed unless strict identity continuity would otherwise fail.",
|
|
"medium-damage":
|
|
"Use energetic side/rear/low/high framing; force low-angle and high-angle alternation so adjacent stages do not share the same vertical framing.",
|
|
"heavy-damage":
|
|
"Prioritize low-angle, side, high-angle, and rear framing to emphasize impact chaos; high/low angles should dominate this stage.",
|
|
defeated:
|
|
"Prioritize side/rear/back/low/high framing for collapse and pain continuity; frontal composition only if face continuity cannot be preserved.",
|
|
};
|
|
|
|
const POSE_ORIENTATION_VARIANTS = {
|
|
normal: {
|
|
front: [
|
|
"forward slash-dodge blend with hips pivoting and back foot digging for balance",
|
|
"low split-step into guarded lunge while shoulders drift around the impact angle",
|
|
"angled stride with upper body opening and hips torqued toward counter line",
|
|
],
|
|
side: [
|
|
"side-reverse guard stance with hips and elbow dropping into a counter line",
|
|
"three-quarter sidestep guard with shoulder cut and heel shift",
|
|
"profile side-lean into a counter setup with hip twist",
|
|
"sideward launch stance while back arm sweeps for balance",
|
|
],
|
|
rear: [
|
|
"rear-open dodge with shoulder twist and hand reposition",
|
|
"half-turn recovery where the torso shifts into back-side guard",
|
|
"body twisted into reverse profile as the support leg checks inward",
|
|
"single-foot reverse step with shoulder cut and hand recoil",
|
|
],
|
|
low: [
|
|
"low crouch into short strike setup with lead knee weighting",
|
|
"deep squat guard with chest thrust and shoulder roll",
|
|
"dropping squat recovery with torso forward and hips stabilized",
|
|
"knee-planted drop-step into low profile defense",
|
|
],
|
|
high: [
|
|
"vertical pull-up arc with chest open and one knee lifted",
|
|
"high-angle recovery stance while the weapon arcs above eye level",
|
|
"raised-angle block with chin tucked and shoulder pivot",
|
|
"high-lift defensive reset with twisted hips and backward shoulder pull",
|
|
],
|
|
},
|
|
"light-damage": {
|
|
front: [
|
|
"quick forward guard split with one shoulder collapsing and re-centering",
|
|
"controlled recoil then short dash that reclaims forward ground",
|
|
"recovery stride where arms and hips overcorrect while turning away from impact",
|
|
],
|
|
side: [
|
|
"side recoil plus hip turn into defensive counter line",
|
|
"angled cross step with hand crossing in pain-block recovery",
|
|
"defensive sidestep with torso twisted to absorb impact",
|
|
"off-angle side recoil with knee bend and heel snap backward",
|
|
],
|
|
rear: [
|
|
"rear guard with shoulders turned while stepping backward",
|
|
"half-turn backward kick and shoulder brace after impact",
|
|
"backward diagonal lurch into off-center survival stance",
|
|
],
|
|
low: [
|
|
"single-knee drop into defensive crouch after recoil",
|
|
"low-angle recoil where knee bends to absorb a glancing blow",
|
|
"weight drops to lower center with foot drag recovery",
|
|
"grounding low guard with bent knees and shoulder drop",
|
|
],
|
|
high: [
|
|
"leaning recovery shot with head lowered and shoulders lifting",
|
|
"upper-body pull-up after impact while one leg pushes backward",
|
|
"rear shoulder guard raised from above and angled backward",
|
|
"high-arc recoil with hip pivot and weapon reset",
|
|
],
|
|
},
|
|
"medium-damage": {
|
|
front: [
|
|
"recoil burst then forced counter as hip and rib line rotate",
|
|
"forward drive with one arm absorbing shock and the other checking distance while hips pivot",
|
|
"angry step-in with chest thrust while lower leg braces through an angled stance",
|
|
],
|
|
side: [
|
|
"twisted side-collapse into half-dodge and weapon re-latch",
|
|
"violent side pivot with hips dropping for recovery",
|
|
"broadside recoil with shoulders and waist taking force",
|
|
"explosive side dodge into a broken counter-stance",
|
|
],
|
|
rear: [
|
|
"rear-lean dodge, one shoulder clipped and weapon hand recovering",
|
|
"backward rotation with torso and hips pulled off rhythm",
|
|
"cross-body drag pose after turning into the blow",
|
|
],
|
|
low: [
|
|
"deep low crouch with one knee buckling and one arm reaching upward",
|
|
"forced fall-to-knee pose with shoulder and thigh locked in strain",
|
|
"full-bent-leg recovery where hips torque to stop momentum",
|
|
"low side-crawl recovery with one knee dragging to regain control",
|
|
],
|
|
high: [
|
|
"high-arc dodge with upper body flaring open from pain",
|
|
"upward chest-raise recovery where weapon follows steep line",
|
|
"torso recovery angle showing damage across chest and abdomen",
|
|
"high-side rebound where chest turns toward the damage vector",
|
|
],
|
|
},
|
|
"heavy-damage": {
|
|
front: [
|
|
"desperate front counter while dropping into fractured posture",
|
|
"broken-forward guard, hands and feet split to catch falling weight",
|
|
"aggressive rebound with shoulder collapsing inward and hips dragging back",
|
|
],
|
|
side: [
|
|
"violent side-fall pivot with one leg giving and one hand bracing",
|
|
"off-axis side collision posture, knees flexed and spine twisted",
|
|
"angry side-block stance with torso collapsing then pushing up",
|
|
"twisted side-lunge collapse with weapon trailing and heel scrape",
|
|
],
|
|
rear: [
|
|
"rear collapse step while torso twists to keep balance",
|
|
"backwards stagger with arms crossing over torn shoulders",
|
|
"fallen side-back push-off with boots dragging backward",
|
|
],
|
|
low: [
|
|
"floor-level collapse with one hand clawing forward and one foot extended",
|
|
"kneeling recoil pose where upper body drops first",
|
|
"low recovery attempt with bent knees and hips dragged sideways",
|
|
"knee-drop recovery with one leg sliding backward under pain",
|
|
],
|
|
high: [
|
|
"upward shake-out motion while keeping hips off-line from injury",
|
|
"high-angle pain-shift where torso rises briefly then sinks",
|
|
"upper-body lift against collapsed armor and ground pull",
|
|
"high recoil recovery while shoulder and hip line are clearly broken open",
|
|
],
|
|
},
|
|
defeated: {
|
|
front: [
|
|
"collapsed side-heavy breath pose with one knee low and body folded into recoil",
|
|
"kneeling sideward collapse while torso twists and shoulders drop",
|
|
"final effort reach-forward pose with one hand searching for footing while upper body falls",
|
|
"side-forward defeated hold while the lower body cannot stabilize",
|
|
],
|
|
side: [
|
|
"sideways collapse with legs apart and shoulders dropping across chest line",
|
|
"swayed side collapse showing hips opening for impact visibility",
|
|
"falling side-lean against broken footing with one arm stretched",
|
|
],
|
|
rear: [
|
|
"rear slump with shoulder collapse and waist open to the shot",
|
|
"turning-over collapse while body folds toward the damage side",
|
|
"back-lean seated defeat where torso twists and legs separate",
|
|
],
|
|
low: [
|
|
"ground-level collapse where back leg is dragged forward and torso sinks",
|
|
"deep knee-drop surrender posture with shoulders gone inward",
|
|
"knees-first give-up frame with one hand on fractured hip area",
|
|
],
|
|
high: [
|
|
"high-angle pinned defeat, body folded and centerline visible",
|
|
"raised camera angle collapse showing hips and abdomen exposed",
|
|
"full-body give-way with shoulders dropping and torso leaning down",
|
|
],
|
|
},
|
|
};
|
|
|
|
const POSE_VARIANTS = {
|
|
normal: [
|
|
"combat-ready neutral launch stance with even weight and a controlled torso twist",
|
|
"alert guard pose with one hand forward, weapon ready at half-step",
|
|
"forward-bent readiness pose, shoulders aligned for immediate counterattack",
|
|
"side-breath stance with hip push and guard hand slightly low",
|
|
"calm pre-strike step, left foot forward and shoulders coiled for counter move",
|
|
"guarding pose while shifting weight through the back foot",
|
|
"dynamic sidestep lean with weapon hand slicing outward",
|
|
"single-step dodge into counter position with raised elbow and twisted waist",
|
|
],
|
|
light: [
|
|
"reaction pose after first impact, one shoulder dropping and feet rebalancing",
|
|
"counter-step pose with hip pivot and hand reposition around weapon",
|
|
"upper-body twist and short recoil, keeping face and weapon in view",
|
|
"rapid dodge frame with front shoulder dropping and back hip pivoting",
|
|
"partial block and recoil with one knee lowering into recovery",
|
|
"one-hand brace while weapon hand reaches for a quick reset",
|
|
"half-step backward check with shoulder dip and waist coil",
|
|
"lateral recoil pose, foot split and hand crossing low guard",
|
|
],
|
|
medium: [
|
|
"strained mid-combat pose with bent knee, waist coil, and heavy shoulder impact",
|
|
"arm-blocking, torso-rotating hit reaction showing exposed flank",
|
|
"charging recovery pose while the damaged arm pushes and the hips shift backward",
|
|
"kicking recoil pose with one heel up and torso spiraling to absorb impact",
|
|
"counter-rotation with cross-armed defense and split-step footwork",
|
|
"mid-air dodge frame followed by sharp landing, damaged sleeve catching air",
|
|
"elbow-heavy crash block with one shoulder dropped and one arm sweeping back",
|
|
"reverse pivot dodge with back heel drag and hip torque",
|
|
],
|
|
heavy: [
|
|
"collapsed combat pose, torso over one leg and shoulders catching recoil",
|
|
"weapon-gripped recovery pose with broken stance and side body collapse",
|
|
"wounded counterpose, upper body twisted, one shoulder dropped low from impact",
|
|
"half-kneel counter stance, one knee down while other foot braces the spin",
|
|
"twisted shoulder-check pose with weapon trailing and body pulled diagonally back",
|
|
"falling stagger pose, back arched while hands scramble to hold broken gear",
|
|
"violent side-fall recovery with broken arm brace and one leg dragging backward",
|
|
"desperation block posture with knees bent, torso bent inward, and wide arm swing",
|
|
],
|
|
defeated: [
|
|
"kneeling collapse with one hand low and one hand gripping broken gear",
|
|
"half-fallen arched-back pose showing loss of balance and pain",
|
|
"seated back-leaning defeat pose with heavy breathing and exposed impact line",
|
|
"one-knee collapse while both hands brace on fractured shoulder or arm",
|
|
"full collapse into bent-leg rest pose with torso open and head angled down",
|
|
"sideways seated defeat pose, legs apart, torso twisted toward the camera",
|
|
"swaying seated collapse on bent knees, one hand clutching torn belt",
|
|
"full knee-drop giving-up frame with limp forearm and heavy torso slump",
|
|
],
|
|
};
|
|
|
|
const LOW_HIGH_CAMERA_PHRASES = {
|
|
low: [
|
|
"low-angle ground-lean with one knee absorbing impact and hips thrust backward",
|
|
"low-side dramatic weight shift with torn cloth and visible foot torque",
|
|
"ultra low perspective from knee height showing stagger and impact on armor seams",
|
|
],
|
|
high: [
|
|
"high-angle top-down recoil shot while torso and hips remain readable",
|
|
"elevated overhead angle with character weight dropping toward ground and torn panels opening",
|
|
"steep high-angle cut to show back-foot recovery, impact direction, and body distortion",
|
|
],
|
|
rear: [
|
|
"high-tension rear-side sweep showing opened back panel and collapsing shoulder",
|
|
"side-rear pivot with torn coat and one leg stepping into empty air",
|
|
"rear three-quarter collapse angle showing exposed back and waist line",
|
|
],
|
|
};
|
|
|
|
function charSeed(text) {
|
|
let hash = 2166136261;
|
|
for (let i = 0; i < text.length; i += 1) {
|
|
hash ^= text.charCodeAt(i);
|
|
hash = Math.imul(hash, 16777619);
|
|
}
|
|
return hash >>> 0;
|
|
}
|
|
|
|
function pickDiverseOption(list, seed, stage, step) {
|
|
const baseIndex = (seed + stage * step) % list.length;
|
|
const prevIndex = stage > 0 ? (seed + (stage - 1) * step) % list.length : -1;
|
|
const index = stage > 0 && baseIndex === prevIndex ? (baseIndex + 1) % list.length : baseIndex;
|
|
return list[index];
|
|
}
|
|
|
|
function normalizeToDirectionDistance(length, from, to) {
|
|
return (to - from + length) % length;
|
|
}
|
|
|
|
function firstIndexForOrientation(plan, wanted) {
|
|
for (let i = 0; i < plan.length; i += 1) {
|
|
if (normalizeOrientation(plan[i]) === wanted) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
function nextDifferentIndex(plan, startIndex, bannedOrientation) {
|
|
for (let i = 1; i <= plan.length; i += 1) {
|
|
const index = (startIndex + i) % plan.length;
|
|
const candidate = normalizeOrientation(plan[index]);
|
|
if (candidate !== bannedOrientation && candidate !== "front") {
|
|
return index;
|
|
}
|
|
}
|
|
return startIndex;
|
|
}
|
|
|
|
function normalizeOrientation(orientation) {
|
|
return orientation === "front" ? "side" : orientation;
|
|
}
|
|
|
|
const cameraHistoryByCharacter = new Map();
|
|
const poseHistoryByCharacter = new Map();
|
|
|
|
function pickCameraForStage(spec, character, stage) {
|
|
const orientationPlan = CAMERA_ORIENTATION_PLAN[spec.label] ?? CAMERA_ORIENTATION_PLAN.normal;
|
|
const orientationSeed = charSeed(`${character.id}-${character.scene}-${spec.label}`);
|
|
const previousOrientationIndex = stage > 0 ? (orientationSeed + stage - 1) % orientationPlan.length : -1;
|
|
const previousOrientation = previousOrientationIndex === -1 ? null : orientationPlan[previousOrientationIndex];
|
|
let resolvedIndex = (orientationSeed + stage) % orientationPlan.length;
|
|
for (let attempt = 0; attempt < orientationPlan.length; attempt += 1) {
|
|
const orientation = orientationPlan[resolvedIndex];
|
|
if (orientation !== previousOrientation) {
|
|
break;
|
|
}
|
|
resolvedIndex = (resolvedIndex + 1) % orientationPlan.length;
|
|
}
|
|
const historyKey = character.id;
|
|
const history = cameraHistoryByCharacter.get(historyKey) ?? [];
|
|
const needLow = stage >= 3 && !history.includes("low");
|
|
const needHigh = stage >= 4 && !history.includes("high");
|
|
if (needLow) {
|
|
const lowIndex = firstIndexForOrientation(orientationPlan, "low");
|
|
if (lowIndex !== -1) {
|
|
resolvedIndex = (resolvedIndex + normalizeToDirectionDistance(orientationPlan.length, resolvedIndex, lowIndex)) % orientationPlan.length;
|
|
}
|
|
}
|
|
if (needHigh) {
|
|
const highIndex = firstIndexForOrientation(orientationPlan, "high");
|
|
if (highIndex !== -1) {
|
|
resolvedIndex = (resolvedIndex + normalizeToDirectionDistance(orientationPlan.length, resolvedIndex, highIndex)) % orientationPlan.length;
|
|
}
|
|
}
|
|
if (history.length > 0) {
|
|
resolvedIndex = nextDifferentIndex(orientationPlan, resolvedIndex, history[history.length - 1]);
|
|
}
|
|
|
|
const finalOrientation = normalizeOrientation(orientationPlan[resolvedIndex]);
|
|
history.push(finalOrientation);
|
|
if (history.length > 8) {
|
|
history.shift();
|
|
}
|
|
cameraHistoryByCharacter.set(historyKey, history);
|
|
|
|
const stagePool =
|
|
(CAMERA_VARIANTS[spec.label] ?? CAMERA_VARIANTS.normal)[finalOrientation] ??
|
|
CAMERA_VARIANTS.normal[finalOrientation] ??
|
|
CAMERA_VARIANTS.normal.side;
|
|
const phrase = pickDiverseOption(
|
|
stagePool,
|
|
charSeed(`${character.id}-${character.scene}`),
|
|
stage,
|
|
17,
|
|
);
|
|
const angleAddonPool = LOW_HIGH_CAMERA_PHRASES[finalOrientation];
|
|
const extraPhrase = angleAddonPool
|
|
? pickDiverseOption(
|
|
angleAddonPool,
|
|
charSeed(`${character.id}-${character.affinity}-${character.scene}`),
|
|
stage,
|
|
29,
|
|
)
|
|
: null;
|
|
const anglePhrase = extraPhrase === null ? phrase : `${phrase} ${extraPhrase}`;
|
|
return {
|
|
orientation: finalOrientation,
|
|
phrase: anglePhrase,
|
|
};
|
|
}
|
|
|
|
function pickPoseModifier(spec, character, stage, orientation) {
|
|
const normalizedOrientation = normalizeOrientation(orientation);
|
|
const list = POSE_VARIANTS[spec.label] ?? POSE_VARIANTS.normal;
|
|
const orientationBonus =
|
|
POSE_ORIENTATION_VARIANTS[spec.label]?.[normalizedOrientation] ??
|
|
POSE_ORIENTATION_VARIANTS.normal?.[normalizedOrientation] ??
|
|
[];
|
|
const posePool = orientationBonus.concat(list);
|
|
const pick = pickDiverseOption(
|
|
posePool,
|
|
charSeed(`${character.id}-${character.pose}-${character.weapon}-${orientation}`),
|
|
stage,
|
|
19,
|
|
);
|
|
const historyKey = character.id;
|
|
const history = poseHistoryByCharacter.get(historyKey) ?? [];
|
|
let selected = pick;
|
|
if (history.length > 0 && selected === history[history.length - 1]) {
|
|
selected = posePool[(posePool.indexOf(pick) + 1) % posePool.length];
|
|
}
|
|
history.push(selected);
|
|
if (history.length > 12) {
|
|
history.shift();
|
|
}
|
|
poseHistoryByCharacter.set(historyKey, history);
|
|
return selected;
|
|
}
|
|
|
|
function promptFor(character, stage) {
|
|
const spec = damageSpec(stage);
|
|
const elementName = {
|
|
water: "water/수",
|
|
metal: "metal/금",
|
|
earth: "earth/지",
|
|
fire: "fire/화",
|
|
wood: "wood/목",
|
|
}[character.affinity] ?? character.affinity;
|
|
const adultAppeal =
|
|
"adult female collectible-card appeal: emphasize a mature curvy silhouette through the armor, uniform, stage outfit, or gear silhouette; mature body proportions; elegant adult costume design; adult pinup body language is encouraged when framed as battle damage or defeat; high-damage cards should read adult-only and not teen-safe";
|
|
const weaponImpactReadability =
|
|
"weapon-impact damage readability: destruction should look caused by readable combat impacts, not vines, webs, thread nets, or lace-like shredding. Prefer a few large directional cuts, diagonal slash paths, sheared armor plates, split seams following the weapon angle, missing costume panels, broken buckles, crushed plate zones, and big hanging cloth sections. Avoid dozens of tiny parallel rips, uniformly shredded edges across the whole outfit, wire tangles, vine-shaped cuts, spiderweb cracks, glittering filaments, and random micro-tears. A blade hit should create one or two coherent slash directions across nearby cloth, straps, and armor edges; a blunt hit should bend or crush plates around one impact zone; an energy hit should burn away broad panels.";
|
|
const broadDamageGate =
|
|
"broad-damage gate: if the damaged outfit mostly reads as many fine cracks, tiny scratches, lace frays, thorny threads, or vine-like tendrils, the card fails even when the character is exposed. Each major damaged zone should include at least one large missing panel, wide split seam, snapped fastener, sheared plate, or heavy hanging remnant that can be understood at card size.";
|
|
const camera = pickCameraForStage(spec, character, stage);
|
|
const angleControl =
|
|
"Angle strictness: this render must use side/rear/low/high framing first; direct frontal composition is disallowed unless the model absolutely cannot preserve face identity.";
|
|
const angleConstraint =
|
|
"Do not output front-facing framing for default action shots. Use side/rear/low/high angles as primary composition, and rotate the view family between shots so the same character does not repeat the exact same camera axis.";
|
|
const poseModifier = pickPoseModifier(spec, character, stage, camera.orientation);
|
|
return [
|
|
"Use case: stylized-concept",
|
|
"Asset type: high-resolution individual trading card illustration",
|
|
`Primary request: Create a unique full-card illustration for ${character.id} ${character.name}, damage stage ${stage} (${spec.label}).`,
|
|
`Subject: adult ${character.gender} ${character.race}, ${character.body}, ${character.title}, ${character.role}, weapon/equipment ${character.weapon}.`,
|
|
`World/era concept: ${character.world}; ${character.era}. This roster is not limited to medieval fantasy; characters can come from modern campuses, idol stages, cyberpunk streets, space colonies, sports arenas, mythic fashion worlds, or other time periods.`,
|
|
`Lighting/time/weather: ${character.lighting}. Do not default every battle to night; use daytime, afternoon, morning, cloudy, indoor, or night lighting only when this field calls for it.`,
|
|
`Signature costume/equipment: ${character.costume}.`,
|
|
`Unique appearance DNA: ${character.appearance.faceShape}; ${character.appearance.eyeStyle}; ${character.appearance.noseStyle}; ${character.appearance.lipStyle}; ${character.appearance.skinTone}; ${character.appearance.hairStyle}; ${character.appearance.bodyDetail}; ${character.appearance.bustShape}; ${character.appearance.presence}. Make this face and body clearly different from other cards.`,
|
|
`Identity lock across stages: preserve the exact same person in every damage stage--same race/species, ear shape, face structure, eye shape, nose, lips, skin tone, hair color, hair length, hairstyle, body proportions, bust shape, and mature presence. Damage may change clothing, armor, pose, expression, dirt, sweat, and injury reaction, but must never change a human into an elf, change ear shape, change hair color, change age, change ethnicity, or replace the character with a different-looking person.`,
|
|
`Personality and pose: ${character.personality}; ${character.pose}; ${spec.poseNote}; ${poseModifier}.`,
|
|
`Facial expression: ${spec.expression}.`,
|
|
`Camera/view variation: ${camera.orientation} angle. Composition directive: ${camera.phrase}.`,
|
|
angleControl,
|
|
angleConstraint,
|
|
"Across the character's five damage stages, rotate between side, rear, low-angle, and high-angle views as the primary framing, and only use frontal composition when the identity cannot be preserved.",
|
|
`Camera constraint: ${CAMERA_ORIENTATION_RULES[spec.label] ?? CAMERA_ORIENTATION_RULES.normal}`,
|
|
`Adult visual appeal: ${character.appealFocus}; ${adultAppeal}.`,
|
|
`Scene/backdrop: ${character.scene}; ${elementName} elemental atmosphere; no other characters.`,
|
|
`Style/medium: ${character.style}, portrait card composition.`,
|
|
"Manager-preferred clean card style: prioritize the clear, calm readability of the C0007 baseline approach--large silhouette, clean focal areas, broad elemental or gravity shapes, crisp costume/equipment detail, and a background that supports the character instead of competing with her.",
|
|
"Strict visual noise control: do not fill the image with tiny sparkles, glitter dust, particle clouds, micro-shards, confetti debris, random luminous specks, hairline glowing filigree, spiderweb crack networks, or glittering wire/vine nets. Show damage through large readable cut-open panels, bent armor plates, broken equipment pieces, broad impact arcs, and a few bold elemental shapes; leave negative space around the face, torso, weapon, and main silhouette so the card reads cleanly at game size.",
|
|
weaponImpactReadability,
|
|
broadDamageGate,
|
|
"Composition/framing: full body or knees-up character centered, strong silhouette, vertical card art, cinematic but readable.",
|
|
`Damage state: ${spec.visual}.`,
|
|
`Impact intensity: ${spec.impactIntensity}. Make the damage feel forceful and physically convincing, not like mild fashion distressing.`,
|
|
`Material-aware damage pacing: ${spec.materialPacing ?? "no material damage yet; all materials remain intact before combat"}. Soft materials such as cloth, capes, ribbons, skirts, bodysuit edges, and leather straps should fail faster; hard materials such as metal armor, rigid sci-fi plates, helmets, drones, weapons, and mechanical joints should fail more slowly but must show dents, cracks, bent shapes, broken fasteners, detached plates, and disabled mechanisms as damage escalates.`,
|
|
"Baseline comparison gate: evaluate every damaged stage against this character's damage-0 outfit and equipment. From damage-2 onward, the worn costume/armor/equipment itself must be visibly destroyed by the required percentage; dirt, soot, scratches, thin crack lines, glow effects, pose changes, background rubble, and loose floating debris do not count unless the original worn gear is actually broken, torn away, detached, disabled, or hanging loose.",
|
|
`Armor/equipment destruction floor: ${spec.destructionFloor}. Treat this as a visual minimum for the stage, while keeping the image readable and non-gore.`,
|
|
`Damage coverage: ${spec.damageCoverage}. Damage must feel physically consistent across the whole outfit and armor system, but not as identical tiny tears everywhere; group the destruction around a few readable impact paths and make the affected regions visibly open, broken, or hanging loose. Adult pinup posing, hip-forward stances, arched backs, twisted torsos, side-back views, opened-leg kneeling, damaged-costume torso silhouettes, and mature defeated body language are strongly encouraged when they read as battle damage or defeat rather than an intimate act.`,
|
|
`Protected-zone damage balance: ${spec.protectedZoneDamage}. Damage to chest, bust armor, hips, and rear-body outfit panels should read as natural combat destruction of equipment and clothing, never as selective preservation.`,
|
|
`Base-layer damage balance: ${spec.baseLayerDamage}. The inner coverage should stay non-explicit and opaque where needed, but it must share the same battle history as the rest of the outfit instead of looking untouched.`,
|
|
"Cumulative damage continuity: each later damage stage must preserve every visible tear, crack, missing armor plate, damaged accessory, and exposed non-intimate area established in earlier stages, then add new damage on top. Never repair, clean up, replace, or re-cover a previously damaged zone as the stage number increases.",
|
|
`Battle-damage exposure direction: ${spec.exposure}. The card should not look modest or fully covered after impact; large opened costume panels are part of the adult card-game reward and should be aggressively favored while remaining framed as combat damage, damaged costume, or defeat. From damage-3 onward, avoid teen-safe costume preservation: use broken armor pieces and torn opaque remnants instead of clean replacement underlayers. High-damage cards fail if they use clean replacement coverage layers, pristine wraps, censorship effects, hair curtains, or unbroken replacement panels to hide the damage.`,
|
|
`Missing gear/clothing emphasis: ${spec.missingArmor}. Emphasize battle intensity through missing or damaged hand, foot, shoulder, abdomen, hip, and torso gear where appropriate, adapting the damage to this character's era and outfit.`,
|
|
`Continuity: keep this character's face, body type, race/species, ear shape, hair color/style, weapon/equipment, world/era, costume silhouette, color motif, and elemental identity consistent across all five damage stages. If any later stage looks like a different person, it is a failed card and must be regenerated.`,
|
|
"Constraints: all characters are adults; adult-oriented multi-world card art; mature silhouette, adult pinup posing, bold damaged-costume presentation, and visible non-sensitive body areas are encouraged when framed as battle damage or defeat; pain expressions, tears, gasping, and small saliva/drool details must read as combat injury reactions; no intimate act; no penetration; no gore; no minors; no text; no logo; no watermark; no explicit closeup focus.",
|
|
"Avoid: childlike appearance, underage cues, malformed hands, extra limbs, cropped head, UI elements.",
|
|
].join("\n");
|
|
}
|
|
|
|
const source = fs.readFileSync(mainPath, "utf8");
|
|
const context = {
|
|
document: buildDomStub(),
|
|
window: { setInterval() {}, clearInterval() {} },
|
|
fetch: async () => ({ ok: false }),
|
|
console,
|
|
};
|
|
const exportCode = `${source}
|
|
globalThis.__cardExport = {
|
|
damageStages: DAMAGE_ART_STAGES,
|
|
characters: CHARACTERS.map((character) => ({
|
|
id: character.id,
|
|
index: character.index,
|
|
name: character.name,
|
|
gender: character.gender,
|
|
race: character.race,
|
|
body: character.body,
|
|
affinity: character.affinity,
|
|
stars: character.stars,
|
|
title: character.title,
|
|
role: character.role,
|
|
weapon: character.weapon,
|
|
personality: character.personality,
|
|
pose: character.pose,
|
|
origin: character.origin,
|
|
world: character.world,
|
|
era: character.era,
|
|
scene: character.scene,
|
|
lighting: character.lighting,
|
|
costume: character.costume,
|
|
style: character.style,
|
|
damageProfile: character.damageProfile,
|
|
appealFocus: character.appealFocus,
|
|
appearance: character.appearance,
|
|
stats: character.stats
|
|
}))
|
|
};`;
|
|
|
|
vm.runInNewContext(exportCode, context);
|
|
|
|
const exported = context.__cardExport;
|
|
fs.mkdirSync(cardsRoot, { recursive: true });
|
|
fs.mkdirSync(promptsDir, { recursive: true });
|
|
|
|
const manifest = {
|
|
schemaVersion: 1,
|
|
artRoot: "assets/cards",
|
|
damageStages: Array.from({ length: exported.damageStages }, (_, stage) => ({
|
|
stage,
|
|
...damageSpec(stage),
|
|
})),
|
|
expectedImageCount: exported.characters.length * exported.damageStages,
|
|
imageSizeTarget: "portrait high resolution, 2048x3072 or better for final production",
|
|
safetyStyle: "adult-oriented multi-world pinup combat damage; emphasized mature silhouettes, adult pinup posing, bold damaged-costume presentation encouraged as battle-damage or defeat presentation; no minors, intimate acts, penetration, gore, or explicit closeup focus",
|
|
characters: exported.characters.map((character) => ({
|
|
...character,
|
|
art: Array.from({ length: exported.damageStages }, (_, stage) => ({
|
|
damageStage: stage,
|
|
path: `assets/cards/${character.id}/damage-${stage}.png`,
|
|
promptId: `${character.id}-D${stage}`,
|
|
status: "needed",
|
|
})),
|
|
})),
|
|
};
|
|
|
|
const promptLines = [];
|
|
for (const character of exported.characters) {
|
|
for (let stage = 0; stage < exported.damageStages; stage += 1) {
|
|
promptLines.push(
|
|
JSON.stringify({
|
|
promptId: `${character.id}-D${stage}`,
|
|
characterId: character.id,
|
|
damageStage: stage,
|
|
outputPath: `assets/cards/${character.id}/damage-${stage}.png`,
|
|
prompt: promptFor(character, stage),
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
const availablePath = path.join(cardsRoot, "available-assets.json");
|
|
if (!fs.existsSync(availablePath)) {
|
|
fs.writeFileSync(
|
|
availablePath,
|
|
JSON.stringify(
|
|
{
|
|
schemaVersion: 1,
|
|
assets: [],
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
"utf8",
|
|
);
|
|
}
|
|
|
|
fs.writeFileSync(path.join(cardsRoot, "card-manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
|
|
fs.writeFileSync(path.join(promptsDir, "card-image-prompts.jsonl"), `${promptLines.join("\n")}\n`, "utf8");
|
|
|
|
console.log(
|
|
JSON.stringify(
|
|
{
|
|
characters: exported.characters.length,
|
|
damageStages: exported.damageStages,
|
|
expectedImageCount: manifest.expectedImageCount,
|
|
manifest: "assets/cards/card-manifest.json",
|
|
prompts: "assets/cards/prompts/card-image-prompts.jsonl",
|
|
available: "assets/cards/available-assets.json",
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|