@gtanczyk/diorama 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +157 -2
- package/app/bin/diorama.mjs +13 -0
- package/app/src/actions/budget.ts +152 -0
- package/app/src/actions/build.ts +158 -0
- package/app/src/actions/cast.ts +248 -0
- package/app/src/actions/depth.ts +95 -0
- package/app/src/actions/extend.ts +36 -0
- package/app/src/actions/faces.ts +160 -0
- package/app/src/actions/feedback.ts +87 -0
- package/app/src/actions/fix.ts +382 -0
- package/app/src/actions/history.ts +93 -0
- package/app/src/actions/idea-preview.ts +65 -0
- package/app/src/actions/idea-review.ts +102 -0
- package/app/src/actions/idea-sketch.ts +172 -0
- package/app/src/actions/index.ts +31 -0
- package/app/src/actions/items.ts +94 -0
- package/app/src/actions/kids.ts +215 -0
- package/app/src/actions/models.ts +64 -0
- package/app/src/actions/motion.ts +119 -0
- package/app/src/actions/pictures.ts +49 -0
- package/app/src/actions/reactions.ts +36 -0
- package/app/src/actions/registry.ts +101 -0
- package/app/src/actions/share.ts +32 -0
- package/app/src/actions/sound.ts +73 -0
- package/app/src/actions/start.ts +154 -0
- package/app/src/actions/step-work.ts +150 -0
- package/app/src/actions/story.ts +101 -0
- package/app/src/actions/studio.ts +133 -0
- package/app/src/actions/style.ts +72 -0
- package/app/src/actions/video.ts +46 -0
- package/app/src/actions/view.ts +109 -0
- package/app/src/actions/weather.ts +38 -0
- package/app/src/actions/world.ts +289 -0
- package/app/src/commands/action.ts +39 -0
- package/app/src/commands/backdrop.ts +188 -0
- package/app/src/commands/browsers.ts +21 -0
- package/app/src/commands/build.ts +503 -0
- package/app/src/commands/cast.ts +142 -0
- package/app/src/commands/clip.ts +431 -0
- package/app/src/commands/derive.ts +167 -0
- package/app/src/commands/doctor.ts +74 -0
- package/app/src/commands/explore.ts +123 -0
- package/app/src/commands/export.ts +142 -0
- package/app/src/commands/extend.ts +75 -0
- package/app/src/commands/faces.ts +80 -0
- package/app/src/commands/icons.ts +69 -0
- package/app/src/commands/index.ts +36 -0
- package/app/src/commands/layout.ts +31 -0
- package/app/src/commands/masks.ts +117 -0
- package/app/src/commands/mcp.ts +52 -0
- package/app/src/commands/model.ts +284 -0
- package/app/src/commands/paint.ts +182 -0
- package/app/src/commands/plates.ts +97 -0
- package/app/src/commands/play.ts +192 -0
- package/app/src/commands/playthrough.ts +111 -0
- package/app/src/commands/publish.ts +78 -0
- package/app/src/commands/reactions.ts +40 -0
- package/app/src/commands/reel.ts +46 -0
- package/app/src/commands/setup.ts +85 -0
- package/app/src/commands/sound.ts +90 -0
- package/app/src/commands/sprites.ts +232 -0
- package/app/src/commands/story.ts +37 -0
- package/app/src/commands/studio.ts +65 -0
- package/app/src/commands/style.ts +87 -0
- package/app/src/commands/verify.ts +45 -0
- package/app/src/commands/voice-retry.ts +14 -0
- package/app/src/commands/voices.ts +145 -0
- package/app/src/commands/weather.ts +52 -0
- package/app/src/commands/world.ts +104 -0
- package/app/src/film/clip.ts +386 -0
- package/app/src/film/reel.ts +126 -0
- package/app/src/film/screencast.ts +123 -0
- package/app/src/image/mask.ts +29 -0
- package/app/src/image/overlay.ts +38 -0
- package/app/src/image/raster.ts +224 -0
- package/app/src/main.ts +28 -0
- package/app/src/meta/choice.ts +27 -0
- package/app/src/meta/http.ts +99 -0
- package/app/src/meta/memo.ts +91 -0
- package/app/src/meta/muse.ts +70 -0
- package/app/src/meta/openrouter.ts +93 -0
- package/app/src/meta/prices.ts +25 -0
- package/app/src/meta/sam.ts +83 -0
- package/app/src/meta/schema.ts +63 -0
- package/app/src/meta/spark.ts +220 -0
- package/app/src/meta/step.ts +14 -0
- package/app/src/meta/voice-file.ts +78 -0
- package/app/src/meta/voice.ts +103 -0
- package/app/src/model/animation.ts +38 -0
- package/app/src/model/blender/relax.py +284 -0
- package/app/src/model/blender.ts +115 -0
- package/app/src/model/build.ts +50 -0
- package/app/src/model/face.ts +21 -0
- package/app/src/model/fit.ts +181 -0
- package/app/src/model/glb.ts +241 -0
- package/app/src/model/plan.ts +151 -0
- package/app/src/model/quality.ts +48 -0
- package/app/src/model/space.ts +168 -0
- package/app/src/model/sprites.ts +293 -0
- package/app/src/model/views.ts +163 -0
- package/app/src/play/creature.ts +43 -0
- package/app/src/play/depth-occlusion.ts +150 -0
- package/app/src/play/exit-outline.ts +48 -0
- package/app/src/play/face3d.ts +34 -0
- package/app/src/play/focus.ts +78 -0
- package/app/src/play/geometry.ts +272 -0
- package/app/src/play/gestures.ts +23 -0
- package/app/src/play/glow.ts +187 -0
- package/app/src/play/lipsync.ts +69 -0
- package/app/src/play/living.ts +83 -0
- package/app/src/play/outline-path.ts +130 -0
- package/app/src/play/parallax.ts +200 -0
- package/app/src/play/path.ts +262 -0
- package/app/src/play/preview.ts +71 -0
- package/app/src/play/reactions.ts +92 -0
- package/app/src/play/rig.ts +172 -0
- package/app/src/play/shadow.ts +253 -0
- package/app/src/play/sound.ts +51 -0
- package/app/src/play/weather.ts +58 -0
- package/app/src/qa/explore.ts +136 -0
- package/app/src/qa/probe.ts +139 -0
- package/app/src/qa/route.ts +118 -0
- package/app/src/registry.ts +23 -0
- package/app/src/scene/animation-source.ts +36 -0
- package/app/src/scene/assets.ts +37 -0
- package/app/src/scene/autofix.ts +133 -0
- package/app/src/scene/backdrop.ts +326 -0
- package/app/src/scene/blockin.ts +189 -0
- package/app/src/scene/brief.ts +74 -0
- package/app/src/scene/carry.ts +177 -0
- package/app/src/scene/cast-fixes.ts +31 -0
- package/app/src/scene/cast.ts +192 -0
- package/app/src/scene/corrections.ts +164 -0
- package/app/src/scene/critic.ts +169 -0
- package/app/src/scene/depthmap.ts +149 -0
- package/app/src/scene/derive.ts +475 -0
- package/app/src/scene/diagnose.ts +132 -0
- package/app/src/scene/face-calibration.ts +65 -0
- package/app/src/scene/face-registration.ts +21 -0
- package/app/src/scene/face-work.ts +71 -0
- package/app/src/scene/faces-data.ts +48 -0
- package/app/src/scene/faces-settings.ts +20 -0
- package/app/src/scene/faces.ts +435 -0
- package/app/src/scene/glows.ts +153 -0
- package/app/src/scene/held.ts +55 -0
- package/app/src/scene/icons.ts +185 -0
- package/app/src/scene/layers.ts +822 -0
- package/app/src/scene/layout.ts +216 -0
- package/app/src/scene/locate.ts +118 -0
- package/app/src/scene/maskcheck.ts +43 -0
- package/app/src/scene/masks.ts +392 -0
- package/app/src/scene/occluders.ts +123 -0
- package/app/src/scene/occlusion-check.ts +138 -0
- package/app/src/scene/paint-layout.ts +135 -0
- package/app/src/scene/paint.ts +81 -0
- package/app/src/scene/photo.ts +123 -0
- package/app/src/scene/plates.ts +256 -0
- package/app/src/scene/playbook.ts +121 -0
- package/app/src/scene/preview.ts +23 -0
- package/app/src/scene/reactions.ts +118 -0
- package/app/src/scene/restyle.ts +53 -0
- package/app/src/scene/rig-check.ts +68 -0
- package/app/src/scene/rig.ts +375 -0
- package/app/src/scene/scale.ts +148 -0
- package/app/src/scene/shadows.ts +207 -0
- package/app/src/scene/stairs.ts +183 -0
- package/app/src/scene/status.ts +32 -0
- package/app/src/scene/verify.ts +156 -0
- package/app/src/scene/walk.ts +326 -0
- package/app/src/scene/warp.ts +417 -0
- package/app/src/scene/weather.ts +118 -0
- package/app/src/scene/words.ts +65 -0
- package/app/src/secrets.ts +219 -0
- package/app/src/setup/checks.ts +110 -0
- package/app/src/sound/cost.ts +74 -0
- package/app/src/sound/data.ts +89 -0
- package/app/src/sound/fixes.ts +23 -0
- package/app/src/sound/issues.ts +21 -0
- package/app/src/sound/language.ts +11 -0
- package/app/src/sound/lipsync-make.ts +98 -0
- package/app/src/sound/lipsync.ts +24 -0
- package/app/src/sound/make.ts +229 -0
- package/app/src/sound/musicgen.py +20 -0
- package/app/src/sound/pcm.ts +280 -0
- package/app/src/sound/plan.ts +154 -0
- package/app/src/sound/retry.ts +43 -0
- package/app/src/sound/settings.ts +28 -0
- package/app/src/sound/sources.ts +81 -0
- package/app/src/sound/spoken.ts +70 -0
- package/app/src/sound/voices.ts +579 -0
- package/app/src/speech/transcribe.ts +38 -0
- package/app/src/speech/types.ts +18 -0
- package/app/src/story/check.ts +230 -0
- package/app/src/story/design.ts +108 -0
- package/app/src/story/engine.ts +233 -0
- package/app/src/story/fixes.ts +54 -0
- package/app/src/story/npc-guidance.ts +7 -0
- package/app/src/story/phrases.ts +4 -0
- package/app/src/story/schema.ts +92 -0
- package/app/src/studio/agent.ts +554 -0
- package/app/src/studio/discovery.ts +28 -0
- package/app/src/studio/events.ts +48 -0
- package/app/src/studio/history.ts +124 -0
- package/app/src/studio/jobs.ts +291 -0
- package/app/src/studio/locks.ts +33 -0
- package/app/src/studio/mcp.ts +86 -0
- package/app/src/studio/operation.ts +18 -0
- package/app/src/studio/repair.ts +31 -0
- package/app/src/studio/server.ts +421 -0
- package/app/src/studio/trouble.ts +48 -0
- package/app/src/world/design.ts +72 -0
- package/app/src/world/drawings.ts +198 -0
- package/app/src/world/extend-stage.ts +109 -0
- package/app/src/world/extend.ts +123 -0
- package/app/src/world/extension-progress.ts +10 -0
- package/app/src/world/files.ts +62 -0
- package/app/src/world/idea-review.ts +32 -0
- package/app/src/world/photo-limits.ts +2 -0
- package/app/src/world/pool.ts +41 -0
- package/app/src/world/schedule.ts +64 -0
- package/app/src/world/schema.ts +74 -0
- package/app/src/world/sketch.ts +81 -0
- package/app/src/world/style-plan.ts +90 -0
- package/app/src/world/style-reference.ts +16 -0
- package/app/src/world/styled.ts +51 -0
- package/app/src/world/told.ts +105 -0
- package/app/src/world/video-limits.ts +3 -0
- package/app/src/world/video.ts +98 -0
- package/app/studio/activity.tsx +124 -0
- package/app/studio/agent.tsx +384 -0
- package/app/studio/api.ts +111 -0
- package/app/studio/atelier.tsx +234 -0
- package/app/studio/audio-missing.tsx +25 -0
- package/app/studio/audio-setup.tsx +33 -0
- package/app/studio/blender-setup.tsx +30 -0
- package/app/studio/build.tsx +677 -0
- package/app/studio/camera.tsx +59 -0
- package/app/studio/cast-work.tsx +207 -0
- package/app/studio/cast.tsx +178 -0
- package/app/studio/concept-edit.tsx +47 -0
- package/app/studio/creator-idea.tsx +46 -0
- package/app/studio/custom-look.tsx +36 -0
- package/app/studio/faces-setup.tsx +28 -0
- package/app/studio/favicon.svg +11 -0
- package/app/studio/figure.tsx +314 -0
- package/app/studio/fonts/OFL.txt +93 -0
- package/app/studio/fonts/fonts.css +7 -0
- package/app/studio/fonts/fraunces-italic-latin-ext.woff2 +0 -0
- package/app/studio/fonts/fraunces-italic-latin.woff2 +0 -0
- package/app/studio/fonts/fraunces-normal-latin-ext.woff2 +0 -0
- package/app/studio/fonts/fraunces-normal-latin.woff2 +0 -0
- package/app/studio/fonts/instrument-sans-normal-latin-ext.woff2 +0 -0
- package/app/studio/fonts/instrument-sans-normal-latin.woff2 +0 -0
- package/app/studio/icons.tsx +42 -0
- package/app/studio/idea-card.tsx +42 -0
- package/app/studio/idea-review.tsx +181 -0
- package/app/studio/idea-storyboard.tsx +134 -0
- package/app/studio/index.html +15 -0
- package/app/studio/job-words.ts +33 -0
- package/app/studio/lightbox.tsx +49 -0
- package/app/studio/main.tsx +1328 -0
- package/app/studio/nav.ts +14 -0
- package/app/studio/place.tsx +264 -0
- package/app/studio/review-sketch.tsx +47 -0
- package/app/studio/say-it.tsx +49 -0
- package/app/studio/step-work.tsx +134 -0
- package/app/studio/story.tsx +394 -0
- package/app/studio/style-menu.tsx +81 -0
- package/app/studio/style.css +1390 -0
- package/app/studio/sure.tsx +54 -0
- package/app/studio/tip.tsx +74 -0
- package/app/studio/tsconfig.json +5 -0
- package/app/studio/video.tsx +73 -0
- package/app/studio/world.tsx +188 -0
- package/app/tsconfig.json +13 -0
- package/app/web/audio.ts +315 -0
- package/app/web/face.ts +131 -0
- package/app/web/face3d.ts +35 -0
- package/app/web/glows.ts +387 -0
- package/app/web/hero3d.ts +728 -0
- package/app/web/index.html +118 -0
- package/app/web/living.ts +404 -0
- package/app/web/main.ts +3751 -0
- package/app/web/pad.ts +174 -0
- package/app/web/reactions.ts +79 -0
- package/app/web/sheet.ts +36 -0
- package/app/web/synth.ts +140 -0
- package/app/web/tsconfig.json +5 -0
- package/app/web/ui.ts +263 -0
- package/app/web/weather.ts +306 -0
- package/app/web/words.ts +12 -0
- package/docs/characters.md +58 -0
- package/docs/npc-motion.md +69 -0
- package/docs/scenes.md +75 -0
- package/docs/setup.md +60 -0
- package/docs/sound.md +23 -0
- package/docs/studio.md +62 -0
- package/package.json +70 -4
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import sharp from 'sharp';
|
|
2
|
+
// diorama backdrop <slug> [place...]: backdrop.png per place, the painting without its cards, so the runtime can
|
|
3
|
+
// slide the cards against it (parallax). Cards are the sharp occluders of geometry.json, so run it after derive.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { maskFromPng } from '../image/mask.ts';
|
|
6
|
+
import { dilate, subtract, unionAll } from '../image/raster.ts';
|
|
7
|
+
import { FRAME } from '../scene/layout.ts';
|
|
8
|
+
import { Geometry } from '../play/geometry.ts';
|
|
9
|
+
import { defineCommand } from '../registry.ts';
|
|
10
|
+
import { BACKDROP_GROW, backdropFor, smearedBackdrop, type Card } from '../scene/backdrop.ts';
|
|
11
|
+
import { carryBetween, carryFrom, paintingFor, ran, remakes, tilesLine, withChange } from '../scene/carry.ts';
|
|
12
|
+
import { cloudSheet, CRITTER, critterSheet, Depth, FALLING, fallSheet, farFlats, floatSheet, cloneFill, mistSheet, smokeSheet, waterSheet } from '../scene/layers.ts';
|
|
13
|
+
import { maskFromPng as waterMaskOf } from '../image/mask.ts';
|
|
14
|
+
import { WorldFiles } from '../world/files.ts';
|
|
15
|
+
|
|
16
|
+
/** A thing that is water itself: its mask is where the swell is, not dry land. */
|
|
17
|
+
const WET_THING = /\b(water|river|sea|lake|pond|pool|stream|canal|creek|brook|waves?|fountain|puddle)s?\b/i;
|
|
18
|
+
|
|
19
|
+
defineCommand({
|
|
20
|
+
name: 'backdrop',
|
|
21
|
+
usage: 'backdrop <slug> [place...] paint each place without its standing things, for parallax [--smear: no Muse, smear the gaps]',
|
|
22
|
+
async run(args) {
|
|
23
|
+
const smearOnly = args.includes('--smear');
|
|
24
|
+
const [slug, ...only] = args.filter((a) => a !== '--smear');
|
|
25
|
+
if (!slug) throw new Error('usage: diorama backdrop <slug> [place...]');
|
|
26
|
+
const files = new WorldFiles(slug);
|
|
27
|
+
const world = files.world();
|
|
28
|
+
const places = world.places.filter((p) => only.length === 0 || only.includes(p.key));
|
|
29
|
+
await Promise.all(places.map(async (place) => {
|
|
30
|
+
const at = (...parts: string[]) => files.place(place.key, ...parts);
|
|
31
|
+
if (!fs.existsSync(at('geometry.json')) || !fs.existsSync(at('painting.png'))) {
|
|
32
|
+
console.log(` ${place.key}: no geometry.json or painting.png, no backdrop`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const g = Geometry.parse(files.readJson(at('geometry.json')));
|
|
36
|
+
const notes = (files.readJson(at('masks.json')) as { things: Record<string, { phrase: string }> }).things;
|
|
37
|
+
const keys = [...new Set(g.occluders.map((o) => o.thing ?? o.key))].filter((k) => notes[k] && fs.existsSync(at('masks', `${k}.png`)));
|
|
38
|
+
const painting = fs.readFileSync(at('painting.png'));
|
|
39
|
+
// What the last run made, to carry over what a small repaint did not reach.
|
|
40
|
+
const lastBackdrop = fs.existsSync(at('.ran', 'backdrop-made.png')) ? fs.readFileSync(at('.ran', 'backdrop-made.png')) : undefined;
|
|
41
|
+
const lastLayers = (fs.existsSync(at('layers.json')) ? files.readJson(at('layers.json')) : {}) as { flats?: Array<{ box?: [number, number, number, number] }>; water?: { mask: string } };
|
|
42
|
+
const lastWater = lastLayers.water && fs.existsSync(at(lastLayers.water.mask)) ? await maskFromPng(fs.readFileSync(at(lastLayers.water.mask))) : undefined;
|
|
43
|
+
fs.rmSync(at('backdrop.png'), { force: true });
|
|
44
|
+
const cards: Card[] = await Promise.all(keys.map(async (key) => ({ key, phrase: notes[key].phrase, mask: await maskFromPng(fs.readFileSync(at('masks', `${key}.png`))) })));
|
|
45
|
+
if (smearOnly && cards.length) {
|
|
46
|
+
files.write(at('backdrop.png'), await smearedBackdrop(cards, painting));
|
|
47
|
+
console.log(`${place.key}: smeared backdrop behind ${keys.join(', ')}`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// A repaint that reaches no card keeps the backdrop made last time; the repainted tiles come from the new painting.
|
|
51
|
+
const carry = cards.length ? await carryFrom(at(), 'backdrop', painting) : undefined;
|
|
52
|
+
let png: Buffer = painting;
|
|
53
|
+
if (!cards.length) {
|
|
54
|
+
// Nothing stands in front: the painting is the backdrop, and its far view, water and fog still move
|
|
55
|
+
// (a river with nothing standing by it used to stay still).
|
|
56
|
+
files.write(at('backdrop.png'), painting);
|
|
57
|
+
console.log(`${place.key}: no standing things, the painting is the backdrop`);
|
|
58
|
+
} else {
|
|
59
|
+
// A repaint that leaves every card's outline as it was keeps the backdrop made last time: behind the cards it is
|
|
60
|
+
// still what is there, and the repainted tiles around them come from the new painting.
|
|
61
|
+
const kept = carry && !cards.some((c) => remakes(carry, c.mask));
|
|
62
|
+
const made = await backdropFor(files.memo, place, cards, kept ? carry.old : painting);
|
|
63
|
+
const { report } = made;
|
|
64
|
+
png = kept ? await withChange(made.png, painting, carry, dilate(unionAll(FRAME.width, FRAME.height, cards.map((c) => c.mask)), BACKDROP_GROW)) : made.png;
|
|
65
|
+
if (carry) console.log(` ${place.key}: ${tilesLine(carry)}; ${kept ? 'no card changes, the backdrop is kept' : 'a card changes, the backdrop is made again'}`);
|
|
66
|
+
files.write(at('backdrop.png'), png);
|
|
67
|
+
files.write(at('backdrop.json'), report);
|
|
68
|
+
const stuck = Object.entries(report).filter(([, r]) => r.stillThere).map(([k]) => k);
|
|
69
|
+
const ghosts = Object.entries(report).filter(([, r]) => r.ghost).map(([k]) => k);
|
|
70
|
+
for (const [k, r] of Object.entries(report)) if (r.cut) console.log(` ${place.key}: ${k}: its card cuts through a painted part of it at [${r.cut.box?.join(', ')}] (its mask leaves that part out, so it stays on the backdrop and slides apart)`);
|
|
71
|
+
console.log(`${place.key}: backdrop behind ${keys.join(', ')}${stuck.length ? ` (Muse left ${stuck.join(', ')}: smeared there)` : ''}${ghosts.length ? ` (a silhouette of ${ghosts.join(', ')} was left: painted over again)` : ''}`);
|
|
72
|
+
}
|
|
73
|
+
// Painted animals (fish, gulls) swim and fly as small cards of their own: painted out of the backdrop here.
|
|
74
|
+
const alive = (fs.existsSync(at('brief.json')) ? (files.readJson(at('brief.json')) as { alive?: Array<{ key: string; as: string }> }).alive : undefined) ?? [];
|
|
75
|
+
const critterKeys = Object.keys(notes).filter((k) => !keys.includes(k) && fs.existsSync(at('masks', `${k}.png`)) && (CRITTER.test(`${k} ${notes[k].phrase}`) || alive.some((a) => a.key === k && a.as === 'animal')));
|
|
76
|
+
const critterMask = unionAll(FRAME.width, FRAME.height, await Promise.all(critterKeys.map(async (k) => maskFromPng(fs.readFileSync(at('masks', `${k}.png`))))));
|
|
77
|
+
const withCritters = png;
|
|
78
|
+
if (critterKeys.length) {
|
|
79
|
+
const raw = new Uint8Array(await sharp(png).resize(FRAME.width, FRAME.height, { fit: 'fill' }).removeAlpha().raw().toBuffer());
|
|
80
|
+
png = await sharp(cloneFill(raw, FRAME.width, FRAME.height, dilate(critterMask, 3)), { raw: { width: FRAME.width, height: FRAME.height, channels: 3 } }).png().toBuffer();
|
|
81
|
+
files.write(at('backdrop.png'), png);
|
|
82
|
+
}
|
|
83
|
+
// Painted waterfalls pour (layers/fall-<i>.png); they stay out of the flat water below them.
|
|
84
|
+
const fallKeys = Object.keys(notes).filter((k) => !keys.includes(k) && FALLING.test(`${k} ${notes[k].phrase}`) && fs.existsSync(at('masks', `${k}.png`)));
|
|
85
|
+
const fallMasks = await Promise.all(fallKeys.map(async (k) => maskFromPng(fs.readFileSync(at('masks', `${k}.png`)))));
|
|
86
|
+
// The far view as flats one behind another (layers/), so it has depth of its own.
|
|
87
|
+
fs.rmSync(at('layers'), { recursive: true, force: true });
|
|
88
|
+
fs.rmSync(at('layers.json'), { force: true });
|
|
89
|
+
// The far flats and the water are cut from the backdrop: asked about the last backdrop where its change does not reach them.
|
|
90
|
+
const moved = lastBackdrop && (await carryBetween(lastBackdrop, png));
|
|
91
|
+
// Where the far view ends: Spark's horizon, unless it lies far above the planned one (a scale fitted to a
|
|
92
|
+
// tilted view put it at y 20 and no flat was left above it): then the plan's horizon.
|
|
93
|
+
const planned = fs.existsSync(at('layout.json')) ? Number((files.readJson(at('layout.json')) as { horizon?: number }).horizon) : NaN;
|
|
94
|
+
const farLine = Number.isFinite(planned) && g.scale.horizonY < planned - 120 ? planned : g.scale.horizonY;
|
|
95
|
+
if (farLine !== g.scale.horizonY) console.log(` ${place.key}: far flats down to the planned horizon ${planned} (the scale's horizon ${Math.round(g.scale.horizonY)} lies far above it)`);
|
|
96
|
+
const farOff: [number, number, number, number][] = [[0, 0, FRAME.width, Math.max(0, farLine)], ...(lastLayers.flats ?? []).flatMap((f) => (f.box ? [f.box] : []))];
|
|
97
|
+
const farAsked = farFlats(files.memo, place, paintingFor(moved || undefined, png, farOff), farLine).catch((error) => {
|
|
98
|
+
console.log(` ${place.key}: no far flats (${error instanceof Error ? error.message.slice(0, 120) : error})`);
|
|
99
|
+
return undefined;
|
|
100
|
+
});
|
|
101
|
+
// Water to set flowing (layers/water.jpg and its mask), found by SAM on the backdrop.
|
|
102
|
+
const floorDir = at('masks', 'floors');
|
|
103
|
+
const floors = fs.existsSync(floorDir) ? await Promise.all(fs.readdirSync(floorDir).filter((f) => f.endsWith('.png')).map(async (f) => ({ key: f.replace(/\.png$/, ''), mask: await maskFromPng(fs.readFileSync(`${floorDir}/${f}`)) }))) : [];
|
|
104
|
+
const depth = Depth.parse(fs.existsSync(at('depth.json')) ? files.readJson(at('depth.json')) : {});
|
|
105
|
+
// Every painted thing stays dry, not only the cards: a mooring post or a jetty that is no card would
|
|
106
|
+
// otherwise get the swell drawn over it. Only a thing that is the water itself (a pond, the river) flows.
|
|
107
|
+
const dryThings = await Promise.all(Object.keys(notes).filter((k) => !WET_THING.test(`${k} ${notes[k].phrase}`) && !keys.includes(k) && !critterKeys.includes(k) && fs.existsSync(at('masks', `${k}.png`)))
|
|
108
|
+
.map(async (k) => maskFromPng(fs.readFileSync(at('masks', `${k}.png`)))));
|
|
109
|
+
// Painted fog to set drifting (layers/mist.jpg and its mask), found by SAM on the backdrop too.
|
|
110
|
+
const things = dilate(unionAll(FRAME.width, FRAME.height, cards.map((c) => c.mask)), 4);
|
|
111
|
+
const [far, water, mist, smoke] = await Promise.all([
|
|
112
|
+
farAsked,
|
|
113
|
+
waterSheet(files.memo, place, paintingFor(moved || undefined, png, lastWater ?? []), unionAll(FRAME.width, FRAME.height, [...cards.map((c) => c.mask), ...dryThings]), floors, depth, critterKeys.length ? critterMask : undefined).catch((error) => {
|
|
114
|
+
console.log(` ${place.key}: no flowing water (${error instanceof Error ? error.message.slice(0, 120) : error})`);
|
|
115
|
+
return undefined;
|
|
116
|
+
}),
|
|
117
|
+
mistSheet(files.memo, place, png, things, unionAll(FRAME.width, FRAME.height, floors.map((f) => f.mask)), depth).catch((error) => {
|
|
118
|
+
console.log(` ${place.key}: no drifting fog (${error instanceof Error ? error.message.slice(0, 120) : error})`);
|
|
119
|
+
return undefined;
|
|
120
|
+
}),
|
|
121
|
+
// Painted smoke to set curling (layers/smoke-<i>.png), found by SAM on the backdrop.
|
|
122
|
+
smokeSheet(files.memo, place, png, things).catch((error) => {
|
|
123
|
+
console.log(` ${place.key}: no curling smoke (${error instanceof Error ? error.message.slice(0, 120) : error})`);
|
|
124
|
+
return [];
|
|
125
|
+
}),
|
|
126
|
+
]);
|
|
127
|
+
// Boats painted on the water (not cards) to rock on the swell: layers/float-<i>.png, inside the water found above.
|
|
128
|
+
const wetMask = water && await waterMaskOf(await sharp({ create: { width: FRAME.width, height: FRAME.height, channels: 3, background: '#000' } }).composite([{ input: water.mask, left: water.box[0], top: water.box[1] }]).png().toBuffer());
|
|
129
|
+
const floats = wetMask ? await floatSheet(files.memo, place, png, things, wetMask).catch((error) => {
|
|
130
|
+
console.log(` ${place.key}: no rocking boats (${error instanceof Error ? error.message.slice(0, 120) : error})`);
|
|
131
|
+
return [];
|
|
132
|
+
}) : [];
|
|
133
|
+
const falls = (await Promise.all(fallMasks.map((m) => fallSheet(png, m)))).filter((f) => !!f);
|
|
134
|
+
falls.forEach((f, i) => files.write(at('layers', `fall-${i}.png`), f.png));
|
|
135
|
+
if (falls.length) console.log(` ${place.key}: ${falls.length} waterfall(s) pouring`);
|
|
136
|
+
const critters = critterKeys.length ? await critterSheet(withCritters, critterMask, wetMask) : [];
|
|
137
|
+
critters.forEach((c, i) => files.write(at('layers', `critter-${i}.png`), c.png));
|
|
138
|
+
if (critters.length) console.log(` ${place.key}: ${critters.length} painted animal(s) ${critters.some((c) => c.medium === 'air') ? 'flying and ' : ''}swimming (${critterKeys.join(', ')})`);
|
|
139
|
+
// The painted clouds drift as bands (layers/cloud-<i>.png); painted things in the sky and its light stay put.
|
|
140
|
+
const keepInSky = unionAll(FRAME.width, FRAME.height, dryThings);
|
|
141
|
+
const clouds = await cloudSheet(files.memo, place, png, unionAll(FRAME.width, FRAME.height, [...(wetMask ? [wetMask] : []), ...floors.map((f) => f.mask), ...fallMasks]), subtract(keepInSky, unionAll(FRAME.width, FRAME.height, fallMasks))).catch((error) => {
|
|
142
|
+
console.log(` ${place.key}: no drifting clouds (${error instanceof Error ? error.message.slice(0, 120) : error})`);
|
|
143
|
+
return undefined;
|
|
144
|
+
});
|
|
145
|
+
clouds?.bands.forEach((b, i) => files.write(at('layers', `cloud-${i}.png`), b.png));
|
|
146
|
+
if (clouds) {
|
|
147
|
+
files.write(at('layers', 'cloud-clip.png'), clouds.clip);
|
|
148
|
+
if (clouds.keep) files.write(at('layers', 'cloud-keep.png'), clouds.keep);
|
|
149
|
+
console.log(` ${place.key}: ${clouds.bands.length} cloud band(s) drifting`);
|
|
150
|
+
}
|
|
151
|
+
floats.forEach((s, i) => files.write(at('layers', `float-${i}.png`), s.png));
|
|
152
|
+
if (floats.length) console.log(` ${place.key}: ${floats.length} painted boat(s) rocking on the water`);
|
|
153
|
+
smoke.forEach((s, i) => files.write(at('layers', `smoke-${i}.png`), s.png));
|
|
154
|
+
if (smoke.length) console.log(` ${place.key}: ${smoke.length} plume(s) of smoke curling`);
|
|
155
|
+
if (water) {
|
|
156
|
+
files.write(at('layers', 'water.jpg'), water.tex);
|
|
157
|
+
files.write(at('layers', 'water-mask.png'), water.mask);
|
|
158
|
+
console.log(` ${place.key}: ${water.phrase} ${water.flow[0] || water.flow[1] ? `flowing [${water.flow}]` : 'still'}`);
|
|
159
|
+
}
|
|
160
|
+
if (mist) {
|
|
161
|
+
files.write(at('layers', 'mist.png'), mist.tex);
|
|
162
|
+
files.write(at('layers', 'mist-mask.png'), mist.mask);
|
|
163
|
+
console.log(` ${place.key}: painted ${mist.phrase} drifting ${mist.drift === null ? 'with the wind' : mist.drift < 0 ? 'left' : 'right'}${mist.speed !== 1 ? ` x${mist.speed}` : ''}`);
|
|
164
|
+
}
|
|
165
|
+
if (far || water || mist || smoke.length || falls.length || critters.length || clouds) {
|
|
166
|
+
if (far) {
|
|
167
|
+
files.write(at('layers', 'sky.png'), far.sky);
|
|
168
|
+
far.flats.forEach((f, i) => files.write(at('layers', `${i}.png`), f.png));
|
|
169
|
+
console.log(` ${place.key}: far flats, farthest first: ${far.flats.map((f) => f.phrase).join(' · ')}`);
|
|
170
|
+
}
|
|
171
|
+
files.write(at('layers.json'), {
|
|
172
|
+
sky: far ? 'layers/sky.png' : null,
|
|
173
|
+
flats: (far?.flats ?? []).map((f, i) => ({ phrase: f.phrase, file: `layers/${i}.png`, box: f.box, px: f.px })),
|
|
174
|
+
...(water && { water: { tex: 'layers/water.jpg', mask: 'layers/water-mask.png', box: water.box, flow: water.flow } }),
|
|
175
|
+
...(mist && { mist: { tex: 'layers/mist.png', mask: 'layers/mist-mask.png', box: mist.box, drift: mist.drift, speed: mist.speed } }),
|
|
176
|
+
...(smoke.length && { smoke: smoke.map((s, i) => ({ file: `layers/smoke-${i}.png`, box: s.box })) }),
|
|
177
|
+
...(floats.length && { floats: floats.map((s, i) => ({ file: `layers/float-${i}.png`, box: s.box })) }),
|
|
178
|
+
...(falls.length && { falls: falls.map((f, i) => ({ file: `layers/fall-${i}.png`, box: f.box })) }),
|
|
179
|
+
...(critters.length && { critters: critters.map((c, i) => ({ file: `layers/critter-${i}.png`, box: c.box, medium: c.medium, facing: c.facing })) }),
|
|
180
|
+
...(clouds && { clouds: { box: clouds.box, clip: 'layers/cloud-clip.png', ...(clouds.keep && { keep: 'layers/cloud-keep.png' }), bands: clouds.bands.map((b, i) => ({ file: `layers/cloud-${i}.png`, box: b.box, speed: b.speed })) } }),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
ran(at(), 'backdrop', carry?.old ?? painting);
|
|
184
|
+
ran(at(), 'backdrop-made', moved ? moved.old : png);
|
|
185
|
+
}));
|
|
186
|
+
console.log(`calls sent: muse ${files.memo.sent.muse}, sam ${files.memo.sent.sam}`);
|
|
187
|
+
},
|
|
188
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// diorama browsers: downloads Chromium for the Playwright that ships with diorama, so explore, playthrough and
|
|
2
|
+
// sprites work the same from a checkout, a global install or npx (a bare `npx playwright` may fetch another version).
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { defineCommand } from '../registry.ts';
|
|
7
|
+
|
|
8
|
+
defineCommand({
|
|
9
|
+
name: 'browsers',
|
|
10
|
+
usage: 'browsers download Chromium for explore, playthrough and sprites (Playwright, ~150 MB, once)',
|
|
11
|
+
async run(args) {
|
|
12
|
+
const cli = path.join(path.dirname(createRequire(import.meta.url).resolve('playwright/package.json')), 'cli.js');
|
|
13
|
+
const code = await new Promise<number>((resolve) => {
|
|
14
|
+
// Loaded through -e so argv[1] is not the npx cache path, which makes Playwright warn about missing project dependencies.
|
|
15
|
+
const child = spawn(process.execPath, ['-e', `require(${JSON.stringify(cli)})`, 'diorama-browsers', 'install', 'chromium', ...args], { stdio: 'inherit' });
|
|
16
|
+
child.on('error', () => resolve(1));
|
|
17
|
+
child.on('close', (c) => resolve(c ?? 1));
|
|
18
|
+
});
|
|
19
|
+
if (code !== 0) throw new Error(`playwright install chromium exited with ${code}`);
|
|
20
|
+
},
|
|
21
|
+
});
|