313 lines
20 KiB
JavaScript
313 lines
20 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",
|
|
camera:
|
|
"front or three-quarter front view for clear character identity",
|
|
},
|
|
{
|
|
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",
|
|
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 avoiding erotic focus",
|
|
camera:
|
|
"three-quarter front view or slight side angle, enough to show both face and first armor damage",
|
|
},
|
|
{
|
|
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, one bare shoulder, torn neckline or side seam, clearly exposed abdomen or side torso, exposed upper thigh or hip line through broken outer layers",
|
|
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",
|
|
exposure:
|
|
"clear adult battle-damage exposure: one bare shoulder, side torso, abdomen, upper thigh, hip line, and emphasized mature bust silhouette should be obvious, not hidden by intact modest underlayers",
|
|
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: at least half of the outfit's protective system should read as cracked, torn away, bent, detached, disabled, or hanging loose, distributed across the whole body rather than isolated in one spot",
|
|
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 the chest or rear-body gear: chest armor or bodice panels, side-bust 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 cover intimate areas",
|
|
camera:
|
|
"side view or strong three-quarter side view, body twisted from impact so shoulder and abdomen damage are visible",
|
|
},
|
|
{
|
|
label: "heavy-damage",
|
|
visual:
|
|
"heavy battle damage after a brutal exchange: broken and warped outer armor or modern gear, shredded mantle/jacket/stage outfit, fractured bracers, missing glove armor, crushed or split waist plates, damaged boots or bare foot/ankle on one side, heavily exposed shoulders, back, abdomen/midriff, hips, upper thighs, and torn costume layers 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",
|
|
exposure:
|
|
"push to maximum allowed non-sexual battle-damage exposure: exposed shoulders, back, cleavage or side-bust silhouette, abdomen, hips, upper thighs, and torn outfit remnants should make the impact feel severe",
|
|
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 non-erotic 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: most protective plates, straps, harnesses, boots, gloves, weapon attachments, and outer outfit layers should look broken, missing, warped, or disabled after a fierce battle",
|
|
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 chest and rear-body equipment too: dented or split chest armor, torn bodice seams, damaged under-bust straps, cracked side-bust 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, not erotic framing; never leave bust or rear/hip regions unusually pristine compared with the rest of the body",
|
|
camera:
|
|
"dramatic side view, rear three-quarter view, or over-the-shoulder angle while keeping face partially visible",
|
|
},
|
|
{
|
|
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, bare shoulders, exposed back or side-back, exposed abdomen/midriff, hips, upper thighs, and torn costume 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",
|
|
exposure:
|
|
"maximum permitted adult battle-damage exposure for the defeated card: broad exposed shoulder/back/side torso/abdomen/hip/thigh areas, dramatic torn costume remnants, and emphasized mature silhouette while staying framed as combat defeat rather than a sexual act",
|
|
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: the armor and equipment system should be comprehensively wrecked, with only battered opaque remnants and broken supports still covering intimate areas",
|
|
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 chest and rear-body gear as everywhere else: warped chest armor or torn bodice panels, broken under-bust support straps, damaged cleavage-edge armor without explicit nudity, 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 cover intimate areas",
|
|
camera:
|
|
"rear three-quarter, side-back, or low side view; show back/shoulder/cloak damage while keeping enough face profile to read pain",
|
|
},
|
|
];
|
|
return specs[stage];
|
|
}
|
|
|
|
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 large breasts through the armor, uniform, stage outfit, or gear silhouette; curvy mature body proportions; elegant sensual costume design";
|
|
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.`,
|
|
`Personality and pose: ${character.personality}; ${character.pose}; ${spec.poseNote}.`,
|
|
`Facial expression: ${spec.expression}.`,
|
|
`Camera/view variation: ${spec.camera}. Across a character's five damage stages, avoid repeating the same straight-front pose; include front, side, and rear three-quarter views where appropriate.`,
|
|
`Adult visual appeal: ${character.appealFocus}; ${adultAppeal}.`,
|
|
`Scene/backdrop: ${character.scene}; ${elementName} elemental atmosphere; no other characters.`,
|
|
`Style/medium: ${character.style}, portrait card composition.`,
|
|
"Visual noise control: keep tiny sparkles, glitter, particle clutter, micro-shards, and confetti-like debris sparse; use a few large readable elemental shapes and clear silhouettes instead of dense shimmering detail that makes the card hard to look at.",
|
|
"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.`,
|
|
`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, while avoiding sexual posing or explicit intimate exposure.`,
|
|
`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 and never as explicit sexual display.`,
|
|
"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; exposure is part of the adult card-game reward while remaining non-sexual combat 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, weapon/equipment, world/era, costume silhouette, color motif, and elemental identity consistent across all five damage stages.`,
|
|
"Constraints: all characters are adults; adult-oriented multi-world card art; sensual silhouette, nudity, and body exposure are allowed when framed as battle damage or defeat; pain expressions, tears, gasping, and small saliva/drool details must read as combat injury reactions; no sexual act; no penetration; no gore; no text; no logo; no watermark.",
|
|
"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, nudity/body exposure allowed as battle-damage presentation; no minors, sexual acts, penetration, or gore",
|
|
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,
|
|
),
|
|
);
|