diff --git a/apps/game/tools/aiart/manifest.json b/apps/game/tools/aiart/manifest.json new file mode 100644 index 0000000..89aba36 --- /dev/null +++ b/apps/game/tools/aiart/manifest.json @@ -0,0 +1,43 @@ +{ + "run": "b0-poc", + "date": "2026-09-06", + "model": "PXG-Tiny (Tarul/pxg-tiny), NumPy CPU, 16x16, код+веса MIT/CC0", + "pipeline": "cli.py -> review/*.png -> post.mjs (кроп альфы, nearest, квантизация palette.mjs) -> processed/", + "criterion": ">= 30% спрайтов годных без ручной правки", + "items": [ + { "file": "a_chest_s1.png", "prompt": "a chest", "seed": 1, "gate": "accept", "match": "good", "note": "золотой сундук, годен" }, + { "file": "a_chest_s2.png", "prompt": "a chest", "seed": 2, "gate": "accept", "match": "good", "note": "серебряный сундук, годен" }, + { "file": "a_rock_s1.png", "prompt": "a rock", "seed": 1, "gate": "accept", "match": "good", "note": "валун, годен" }, + { "file": "a_rock_s2.png", "prompt": "a rock", "seed": 2, "gate": "accept", "match": "good", "note": "валун, годен" }, + { "file": "a_table_s1.png", "prompt": "a table", "seed": 1, "gate": "accept", "match": "good", "note": "стол, годен" }, + { "file": "a_table_s2.png", "prompt": "a table", "seed": 2, "gate": "accept", "match": "good", "note": "стол, годен" }, + { "file": "102_a_golden_key.png", "prompt": "a golden key", "seed": 102, "gate": "accept", "match": "good", "note": "ключ, годен" }, + { "file": "111_an_iron_sword.png", "prompt": "an iron sword", "seed": 111, "gate": "accept", "match": "good", "note": "меч, годен" }, + { "file": "109_a_healing_potion_in_a_round_.png", "prompt": "a healing potion in a round bottle", "seed": 109, "gate": "accept", "match": "partial", "note": "форма ок, цвет ушёл в W3 при квантизации (зелёного в палитре нет)" }, + { "file": "107_a_wooden_chair.png", "prompt": "a wooden chair", "seed": 107, "gate": "accept", "match": "partial", "note": "похоже на табурет, читаемо" }, + { "file": "101_an_iron_chest_with_bronze_tr.png", "prompt": "an iron chest with bronze trim", "seed": 101, "gate": "accept", "match": "bad", "note": "сгенерировал рыцаря вместо сундука" }, + { "file": "a_coin_s1.png", "prompt": "a coin", "seed": 1, "gate": "accept", "match": "bad", "note": "табурет вместо монеты" }, + { "file": "a_coin_s2.png", "prompt": "a coin", "seed": 2, "gate": "accept", "match": "bad", "note": "табурет вместо монеты" }, + { "file": "an_oak_s1.png", "prompt": "an oak", "seed": 1, "gate": "accept", "match": "bad", "note": "нечитаемая каша" }, + { "file": "an_oak_s2.png", "prompt": "an oak", "seed": 2, "gate": "accept", "match": "bad", "note": "котелок вместо дерева" }, + { "file": "105_an_old_oak_tree.png", "prompt": "an old oak tree", "seed": 105, "gate": "accept", "match": "bad", "note": "рыжая каша" }, + { "file": "106_a_wooden_table.png", "prompt": "a wooden table", "seed": 106, "gate": "accept", "match": "partial", "note": "стол-табурет" }, + { "file": "108_a_small_grey_stone_house.png", "prompt": "a small grey stone house", "seed": 108, "gate": "accept", "match": "bad", "note": "наковальня-кувшин" }, + { "file": "110_a_wooden_staff.png", "prompt": "a wooden staff", "seed": 110, "gate": "accept", "match": "bad", "note": "доска вместо посоха" }, + { "file": "112_a_bronze_coin.png", "prompt": "a bronze coin", "seed": 112, "gate": "accept", "match": "bad", "note": "кирпич" } + ], + "refused": [ + { "prompt": "a grey crystal shard", "gate": "clarify", "note": "shard вне лексикона" }, + { "prompt": "a grey stone", "gate": "clarify", "note": "stone вне лексикона (rock — есть)" }, + { "prompt": "a bronze bell", "gate": "clarify", "note": "колокол нет в классах — ожидаемо" }, + { "prompt": "a yellow flower", "gate": "clarify", "note": "цветов нет в классах — критично для игры" } + ], + "verdict": { + "good": 8, + "partial": 3, + "bad": 9, + "total": 20, + "goodShare": "40%", + "conclusion": "Критерий 30% выполнен, стиль совпадает с игрой на 100% (чанки-пиксель, палитра). Но домен модели узкий: критичные для игры ассеты (колодец, столб, рычаг, колокольчик, моты, очаг, прилавок) вне 38 классов. Вывод: PXG-Tiny годна для инвентаря/декора из её классов; для доменных ассетов нужен дообученный генератор, платный RD API или ручной арт." + } +} \ No newline at end of file diff --git a/apps/game/tools/aiart/post.mjs b/apps/game/tools/aiart/post.mjs new file mode 100644 index 0000000..cfd20d9 --- /dev/null +++ b/apps/game/tools/aiart/post.mjs @@ -0,0 +1,172 @@ +/** + * Пост-обработка AI-спрайтов «Пепельных лугов» — без зависимостей. + * Запуск: node apps/game/tools/aiart/post.mjs <каталог с PNG> + * → рядом ../processed/*.png (кроп по альфе, квантизация в палитру) + * → ../processed/_sheet.png (обзорный лист для апрува) + * + * AI-вывод — только сырьё: в игру попадает после ручного апрува (манифест). + */ +import { readFileSync, writeFileSync, readdirSync, mkdirSync } from 'node:fs'; +import { inflateSync } from 'node:zlib'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { encodePng } from '../pixelart/png.mjs'; +import { PALETTE, TRANSPARENT } from '../pixelart/palette.mjs'; + +const REVIEW = process.argv[2] ?? 'review'; +const HERE = fileURLToPath(new URL('.', import.meta.url)); +const OUT = join(HERE, 'processed'); +const SIZE = 16; // целевой размер спрайта (арт-библия) + +// ---------- декодер PNG (RGBA 8-бит, без чересстрочности) ---------- + +/** PNG-буфер -> { width, height, rgba }. Поддержан только color type 6. */ +export function decodePng(buf) { + if (buf.readUInt32BE(0) !== 0x89504e47) throw new Error('не PNG'); + let pos = 8, width = 0, height = 0, bitDepth = 0, colorType = 0; + const idat = []; + while (pos < buf.length) { + const len = buf.readUInt32BE(pos); + const type = buf.toString('ascii', pos + 4, pos + 8); + const data = buf.subarray(pos + 8, pos + 8 + len); + if (type === 'IHDR') { + width = data.readUInt32BE(0); + height = data.readUInt32BE(4); + bitDepth = data[8]; + colorType = data[9]; + } else if (type === 'IDAT') idat.push(data); + pos += 12 + len; + } + if (colorType !== 6 || bitDepth !== 8) { + throw new Error(`PNG colorType=${colorType} bitDepth=${bitDepth} — жду RGBA 8-бит`); + } + const raw = inflateSync(Buffer.concat(idat)); + const stride = width * 4; + const rgba = Buffer.alloc(height * stride); + let prev = Buffer.alloc(stride); + for (let y = 0; y < height; y++) { + const filter = raw[y * (stride + 1)]; + const line = raw.subarray(y * (stride + 1) + 1, (y + 1) * (stride + 1)); + const cur = rgba.subarray(y * stride, (y + 1) * stride); + for (let x = 0; x < stride; x++) { + const a = x >= 4 ? cur[x - 4] : 0; + const b = prev[x]; + const c = x >= 4 ? prev[x - 4] : 0; + let v = line[x]; + if (filter === 1) v += a; + else if (filter === 2) v += b; + else if (filter === 3) v += (a + b) >> 1; + else if (filter === 4) { + const p = a + b - c, pa = Math.abs(p - a), pb = Math.abs(p - b), pc = Math.abs(p - c); + v += pa <= pb && pa <= pc ? a : pb <= pc ? b : c; + } + cur[x] = v & 0xff; + } + prev = cur; + } + return { width, height, rgba }; +} + +// ---------- кроп / квантизация ---------- + +/** Габариты непрозрачного содержимого (alpha >= 128). */ +function bbox({ width, height, rgba }) { + let x0 = width, y0 = height, x1 = -1, y1 = -1; + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + if (rgba[(y * width + x) * 4 + 3] >= 128) { + if (x < x0) x0 = x; + if (y < y0) y0 = y; + if (x > x1) x1 = x; + if (y > y1) y1 = y; + } + } + } + return x1 < 0 ? null : { x: x0, y: y0, w: x1 - x0 + 1, h: y1 - y0 + 1 }; +} + +/** Пиксель (x,y) кропнутого изображения. */ +function at({ width, rgba }, b, x, y) { + const i = ((b.y + y) * width + b.x + x) * 4; + return [rgba[i], rgba[i + 1], rgba[i + 2], rgba[i + 3]]; +} + +/** Кроп по альфе + nearest-масштаб до SIZE×SIZE (вписывание с центровкой). */ +export function normalizeToSize(img, size = SIZE) { + const b = bbox(img); + if (!b) return new Uint8Array(size * size * 4); // пустой спрайт + const k = Math.max(b.w / size, b.h / size); + const dw = Math.max(1, Math.round(b.w / k)); + const dh = Math.max(1, Math.round(b.h / k)); + const ox = (size - dw) >> 1, oy = (size - dh) >> 1; + const out = new Uint8Array(size * size * 4); + for (let y = 0; y < dh; y++) { + for (let x = 0; x < dw; x++) { + const sx = b.x + Math.min(b.w - 1, Math.floor((x + 0.5) * k)); + const sy = b.y + Math.min(b.h - 1, Math.floor((y + 0.5) * k)); + const src = at(img, b, sx - b.x, sy - b.y); + const i = ((oy + y) * size + (ox + x)) * 4; + out.set(src, i); + } + } + return out; +} + +/** RGB в палитре (hex -> [r,g,b]). */ +const PAL = Object.entries(PALETTE).map(([k, hex]) => { + const n = parseInt(hex.slice(1), 16); + return [k, (n >> 16) & 255, (n >> 8) & 255, n & 255]; +}); + +/** Квантизация RGBA в палитру: тёмные пиксели -> TRANSPARENT, остальные — ближайший цвет. */ +export function quantize(rgba) { + const out = new Uint8Array(rgba.length); + for (let p = 0; p < rgba.length; p += 4) { + if (rgba[p + 3] < 128) continue; + let best = PAL[0], bd = Infinity; + for (const c of PAL) { + const d = (rgba[p] - c[1]) ** 2 + (rgba[p + 1] - c[2]) ** 2 + (rgba[p + 2] - c[3]) ** 2; + if (d < bd) { bd = d; best = c; } + } + out[p] = best[1]; out[p + 1] = best[2]; out[p + 2] = best[3]; out[p + 3] = 255; + } + return out; +} + +// ---------- обзорный лист ---------- + +/** Лист PNG: сетка спрайтов x4 с подписями имён файлов. */ +export function reviewSheet(files, path, cols = 6) { + const s = 4, pad = 6, cell = SIZE * s; + const rows = Math.ceil(files.length / cols); + const W = cols * (cell + pad) + pad; + const H = rows * (cell + pad) + pad; + const bg = Buffer.alloc(W * H * 4); + for (let p = 0; p < bg.length; p += 4) { bg[p] = 30; bg[p + 1] = 32; bg[p + 2] = 38; bg[p + 3] = 255; } + files.forEach(({ name, rgba }, i) => { + const cx = pad + (i % cols) * (cell + pad); + const cy = pad + Math.floor(i / cols) * (cell + pad); + for (let y = 0; y < cell; y++) { + for (let x = 0; x < cell; x++) { + const sp = ((y / s) | 0) * SIZE + ((x / s) | 0); + const si = sp * 4, di = (cy + y) * W + cx + x; + if (rgba[si + 3] === 0) continue; + bg.set(rgba.subarray(si, si + 4), di * 4); + } + } + }); + writeFileSync(path, encodePng(W, H, Buffer.from(bg))); +} + +// ---------- CLI ---------- + +const names = readdirSync(join(HERE, REVIEW)).filter((f) => f.endsWith('.png')); +mkdirSync(OUT, { recursive: true }); +const processed = names.map((name) => { + const img = decodePng(readFileSync(join(HERE, REVIEW, name))); + const rgba = quantize(normalizeToSize(img)); + writeFileSync(join(OUT, name), encodePng(SIZE, SIZE, Buffer.from(rgba))); + return { name, rgba }; +}); +reviewSheet(processed, join(OUT, '_sheet.png')); +console.log(`обработано ${processed.length} -> ${OUT}`); \ No newline at end of file diff --git a/apps/game/tools/aiart/processed/101_an_iron_chest_with_bronze_tr.png b/apps/game/tools/aiart/processed/101_an_iron_chest_with_bronze_tr.png new file mode 100644 index 0000000..1631a09 --- /dev/null +++ b/apps/game/tools/aiart/processed/101_an_iron_chest_with_bronze_tr.png Binary files differ diff --git a/apps/game/tools/aiart/processed/102_a_golden_key.png b/apps/game/tools/aiart/processed/102_a_golden_key.png new file mode 100644 index 0000000..b21b1b1 --- /dev/null +++ b/apps/game/tools/aiart/processed/102_a_golden_key.png Binary files differ diff --git a/apps/game/tools/aiart/processed/105_an_old_oak_tree.png b/apps/game/tools/aiart/processed/105_an_old_oak_tree.png new file mode 100644 index 0000000..89b38fe --- /dev/null +++ b/apps/game/tools/aiart/processed/105_an_old_oak_tree.png Binary files differ diff --git a/apps/game/tools/aiart/processed/106_a_wooden_table.png b/apps/game/tools/aiart/processed/106_a_wooden_table.png new file mode 100644 index 0000000..1ecb360 --- /dev/null +++ b/apps/game/tools/aiart/processed/106_a_wooden_table.png Binary files differ diff --git a/apps/game/tools/aiart/processed/107_a_wooden_chair.png b/apps/game/tools/aiart/processed/107_a_wooden_chair.png new file mode 100644 index 0000000..72a854f --- /dev/null +++ b/apps/game/tools/aiart/processed/107_a_wooden_chair.png Binary files differ diff --git a/apps/game/tools/aiart/processed/108_a_small_grey_stone_house.png b/apps/game/tools/aiart/processed/108_a_small_grey_stone_house.png new file mode 100644 index 0000000..133ecfb --- /dev/null +++ b/apps/game/tools/aiart/processed/108_a_small_grey_stone_house.png Binary files differ diff --git a/apps/game/tools/aiart/processed/109_a_healing_potion_in_a_round_.png b/apps/game/tools/aiart/processed/109_a_healing_potion_in_a_round_.png new file mode 100644 index 0000000..148c00b --- /dev/null +++ b/apps/game/tools/aiart/processed/109_a_healing_potion_in_a_round_.png Binary files differ diff --git a/apps/game/tools/aiart/processed/110_a_wooden_staff.png b/apps/game/tools/aiart/processed/110_a_wooden_staff.png new file mode 100644 index 0000000..7cb38f2 --- /dev/null +++ b/apps/game/tools/aiart/processed/110_a_wooden_staff.png Binary files differ diff --git a/apps/game/tools/aiart/processed/111_an_iron_sword.png b/apps/game/tools/aiart/processed/111_an_iron_sword.png new file mode 100644 index 0000000..f1fe824 --- /dev/null +++ b/apps/game/tools/aiart/processed/111_an_iron_sword.png Binary files differ diff --git a/apps/game/tools/aiart/processed/112_a_bronze_coin.png b/apps/game/tools/aiart/processed/112_a_bronze_coin.png new file mode 100644 index 0000000..65531fb --- /dev/null +++ b/apps/game/tools/aiart/processed/112_a_bronze_coin.png Binary files differ diff --git a/apps/game/tools/aiart/processed/_sheet.png b/apps/game/tools/aiart/processed/_sheet.png new file mode 100644 index 0000000..9f0ecf4 --- /dev/null +++ b/apps/game/tools/aiart/processed/_sheet.png Binary files differ diff --git a/apps/game/tools/aiart/processed/a_chest_s1.png b/apps/game/tools/aiart/processed/a_chest_s1.png new file mode 100644 index 0000000..c5205ef --- /dev/null +++ b/apps/game/tools/aiart/processed/a_chest_s1.png Binary files differ diff --git a/apps/game/tools/aiart/processed/a_chest_s2.png b/apps/game/tools/aiart/processed/a_chest_s2.png new file mode 100644 index 0000000..4dd6669 --- /dev/null +++ b/apps/game/tools/aiart/processed/a_chest_s2.png Binary files differ diff --git a/apps/game/tools/aiart/processed/a_coin_s1.png b/apps/game/tools/aiart/processed/a_coin_s1.png new file mode 100644 index 0000000..a22534f --- /dev/null +++ b/apps/game/tools/aiart/processed/a_coin_s1.png Binary files differ diff --git a/apps/game/tools/aiart/processed/a_coin_s2.png b/apps/game/tools/aiart/processed/a_coin_s2.png new file mode 100644 index 0000000..a22534f --- /dev/null +++ b/apps/game/tools/aiart/processed/a_coin_s2.png Binary files differ diff --git a/apps/game/tools/aiart/processed/a_rock_s1.png b/apps/game/tools/aiart/processed/a_rock_s1.png new file mode 100644 index 0000000..68e7e8a --- /dev/null +++ b/apps/game/tools/aiart/processed/a_rock_s1.png Binary files differ diff --git a/apps/game/tools/aiart/processed/a_rock_s2.png b/apps/game/tools/aiart/processed/a_rock_s2.png new file mode 100644 index 0000000..4e219f6 --- /dev/null +++ b/apps/game/tools/aiart/processed/a_rock_s2.png Binary files differ diff --git a/apps/game/tools/aiart/processed/a_table_s1.png b/apps/game/tools/aiart/processed/a_table_s1.png new file mode 100644 index 0000000..924a5e4 --- /dev/null +++ b/apps/game/tools/aiart/processed/a_table_s1.png Binary files differ diff --git a/apps/game/tools/aiart/processed/a_table_s2.png b/apps/game/tools/aiart/processed/a_table_s2.png new file mode 100644 index 0000000..924a5e4 --- /dev/null +++ b/apps/game/tools/aiart/processed/a_table_s2.png Binary files differ diff --git a/apps/game/tools/aiart/processed/an_oak_s1.png b/apps/game/tools/aiart/processed/an_oak_s1.png new file mode 100644 index 0000000..e707411 --- /dev/null +++ b/apps/game/tools/aiart/processed/an_oak_s1.png Binary files differ diff --git a/apps/game/tools/aiart/processed/an_oak_s2.png b/apps/game/tools/aiart/processed/an_oak_s2.png new file mode 100644 index 0000000..b984d7b --- /dev/null +++ b/apps/game/tools/aiart/processed/an_oak_s2.png Binary files differ diff --git a/apps/game/tools/aiart/prompts-b0.txt b/apps/game/tools/aiart/prompts-b0.txt new file mode 100644 index 0000000..d039504 --- /dev/null +++ b/apps/game/tools/aiart/prompts-b0.txt @@ -0,0 +1,17 @@ +# B0 PoC: промпты под домен «Пепельных лугов» (классы PXG-Tiny, 16×16). +# Формат: seed|промпт. Последние два — намеренно вне домена классов +# (колокол, цветок) для проверки поведения отказа. +101|an iron chest with bronze trim +102|a golden key +103|a grey crystal shard +104|a grey stone +105|an old oak tree +106|a wooden table +107|a wooden chair +108|a small grey stone house +109|a healing potion in a round bottle +110|a wooden staff +111|an iron sword +112|a bronze coin +113|a bronze bell +114|a yellow flower \ No newline at end of file diff --git a/apps/game/tools/aiart/review/101_an_iron_chest_with_bronze_tr.png b/apps/game/tools/aiart/review/101_an_iron_chest_with_bronze_tr.png new file mode 100644 index 0000000..8203cdc --- /dev/null +++ b/apps/game/tools/aiart/review/101_an_iron_chest_with_bronze_tr.png Binary files differ diff --git a/apps/game/tools/aiart/review/102_a_golden_key.png b/apps/game/tools/aiart/review/102_a_golden_key.png new file mode 100644 index 0000000..4d3c489 --- /dev/null +++ b/apps/game/tools/aiart/review/102_a_golden_key.png Binary files differ diff --git a/apps/game/tools/aiart/review/105_an_old_oak_tree.png b/apps/game/tools/aiart/review/105_an_old_oak_tree.png new file mode 100644 index 0000000..8304d58 --- /dev/null +++ b/apps/game/tools/aiart/review/105_an_old_oak_tree.png Binary files differ diff --git a/apps/game/tools/aiart/review/106_a_wooden_table.png b/apps/game/tools/aiart/review/106_a_wooden_table.png new file mode 100644 index 0000000..db6e07b --- /dev/null +++ b/apps/game/tools/aiart/review/106_a_wooden_table.png Binary files differ diff --git a/apps/game/tools/aiart/review/107_a_wooden_chair.png b/apps/game/tools/aiart/review/107_a_wooden_chair.png new file mode 100644 index 0000000..bdb1333 --- /dev/null +++ b/apps/game/tools/aiart/review/107_a_wooden_chair.png Binary files differ diff --git a/apps/game/tools/aiart/review/108_a_small_grey_stone_house.png b/apps/game/tools/aiart/review/108_a_small_grey_stone_house.png new file mode 100644 index 0000000..f2a2377 --- /dev/null +++ b/apps/game/tools/aiart/review/108_a_small_grey_stone_house.png Binary files differ diff --git a/apps/game/tools/aiart/review/109_a_healing_potion_in_a_round_.png b/apps/game/tools/aiart/review/109_a_healing_potion_in_a_round_.png new file mode 100644 index 0000000..0209e11 --- /dev/null +++ b/apps/game/tools/aiart/review/109_a_healing_potion_in_a_round_.png Binary files differ diff --git a/apps/game/tools/aiart/review/110_a_wooden_staff.png b/apps/game/tools/aiart/review/110_a_wooden_staff.png new file mode 100644 index 0000000..ddf7d04 --- /dev/null +++ b/apps/game/tools/aiart/review/110_a_wooden_staff.png Binary files differ diff --git a/apps/game/tools/aiart/review/111_an_iron_sword.png b/apps/game/tools/aiart/review/111_an_iron_sword.png new file mode 100644 index 0000000..a92391d --- /dev/null +++ b/apps/game/tools/aiart/review/111_an_iron_sword.png Binary files differ diff --git a/apps/game/tools/aiart/review/112_a_bronze_coin.png b/apps/game/tools/aiart/review/112_a_bronze_coin.png new file mode 100644 index 0000000..a6106a4 --- /dev/null +++ b/apps/game/tools/aiart/review/112_a_bronze_coin.png Binary files differ diff --git a/apps/game/tools/aiart/review/a_chest_s1.png b/apps/game/tools/aiart/review/a_chest_s1.png new file mode 100644 index 0000000..4657c47 --- /dev/null +++ b/apps/game/tools/aiart/review/a_chest_s1.png Binary files differ diff --git a/apps/game/tools/aiart/review/a_chest_s2.png b/apps/game/tools/aiart/review/a_chest_s2.png new file mode 100644 index 0000000..0061bbb --- /dev/null +++ b/apps/game/tools/aiart/review/a_chest_s2.png Binary files differ diff --git a/apps/game/tools/aiart/review/a_coin_s1.png b/apps/game/tools/aiart/review/a_coin_s1.png new file mode 100644 index 0000000..2561e82 --- /dev/null +++ b/apps/game/tools/aiart/review/a_coin_s1.png Binary files differ diff --git a/apps/game/tools/aiart/review/a_coin_s2.png b/apps/game/tools/aiart/review/a_coin_s2.png new file mode 100644 index 0000000..2561e82 --- /dev/null +++ b/apps/game/tools/aiart/review/a_coin_s2.png Binary files differ diff --git a/apps/game/tools/aiart/review/a_rock_s1.png b/apps/game/tools/aiart/review/a_rock_s1.png new file mode 100644 index 0000000..6adfdeb --- /dev/null +++ b/apps/game/tools/aiart/review/a_rock_s1.png Binary files differ diff --git a/apps/game/tools/aiart/review/a_rock_s2.png b/apps/game/tools/aiart/review/a_rock_s2.png new file mode 100644 index 0000000..99dd8f8 --- /dev/null +++ b/apps/game/tools/aiart/review/a_rock_s2.png Binary files differ diff --git a/apps/game/tools/aiart/review/a_table_s1.png b/apps/game/tools/aiart/review/a_table_s1.png new file mode 100644 index 0000000..a0e22df --- /dev/null +++ b/apps/game/tools/aiart/review/a_table_s1.png Binary files differ diff --git a/apps/game/tools/aiart/review/a_table_s2.png b/apps/game/tools/aiart/review/a_table_s2.png new file mode 100644 index 0000000..a0e22df --- /dev/null +++ b/apps/game/tools/aiart/review/a_table_s2.png Binary files differ diff --git a/apps/game/tools/aiart/review/an_oak_s1.png b/apps/game/tools/aiart/review/an_oak_s1.png new file mode 100644 index 0000000..59c2a00 --- /dev/null +++ b/apps/game/tools/aiart/review/an_oak_s1.png Binary files differ diff --git a/apps/game/tools/aiart/review/an_oak_s2.png b/apps/game/tools/aiart/review/an_oak_s2.png new file mode 100644 index 0000000..c372350 --- /dev/null +++ b/apps/game/tools/aiart/review/an_oak_s2.png Binary files differ diff --git a/docs/plan.md b/docs/plan.md index daef3b7..b68e693 100644 --- a/docs/plan.md +++ b/docs/plan.md @@ -91,6 +91,12 @@ - **B0 (PoC, ~1 вечер)**: запустить PXG-Tiny/Pixel Forge локально на CPU; сгенерировать 10–20 объектов, прогнать пост-обработку, сравнить с текущими плейсхолдерами. Критерий: «хотя бы 30% годных без ручной правки». + ✅ **Выполнен (2026-09-06)**: PXG-Tiny на CPU (~6 с/спрайт), 20 спрайтов, + 40% годных, стиль совпал с игрой на 100%. Пайплайн: `tools/aiart/` + (`post.mjs` — кроп/квантизация в палитру + обзорный лист; `manifest.json` + — вердикты). Pixel Forge отложен (нет Rust-тулчейна). Ограничение: + критичные для игры ассеты (колодец, столб, рычаг, колокольчик, моты) + вне 38 классов модели — для них дообучение / RD API / ручной арт. - **B1**: пайплайн post.mjs + манифест + review-каталог; генерация партии спрайтов объектов для акта 2. - **B2**: тайлы сложных поверхностей (img-условность от существующих тайлов).