@gtanczyk/diorama 0.1.0 → 0.2.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/app/web/main.ts CHANGED
@@ -35,7 +35,7 @@ type Data = {
35
35
  world: World;
36
36
  places: Record<string, { geometry: Geometry | null; painting: string | null; backdrop?: string; layers?: Layers; weather?: Weather; reactions?: Reactions; glows?: Glow[]; issue?: string }>;
37
37
  /** Figure per hero or NPC key: a cut-out image, and its rig when `diorama cast` could cut it into parts. */
38
- cast: Record<string, { image: string; height?: number; upright?: boolean; face?: { dir: string; info: FaceDefinition }; rig?: Rig; parts?: string; model?: { dir: string; info: ModelInfo }; sprites?: { dir: string; info: SpriteInfo } }>;
38
+ cast: Record<string, { image: string; height?: number; upright?: boolean; share?: number; face?: { dir: string; info: FaceDefinition }; rig?: Rig; parts?: string; model?: { dir: string; info: ModelInfo }; sprites?: { dir: string; info: SpriteInfo } }>;
39
39
  story: Story | null;
40
40
  /** Index into story.changes → the painting as it looks once that change holds. */
41
41
  changes?: Record<string, string>;
@@ -70,7 +70,7 @@ export type SpriteInfo = {
70
70
  /** walk: the cells a walk steps through, without the ones the sprites step found off the gait (a crouch, a sit). */
71
71
  type Sheet = { file: string; yaw: number; stand: number; img: HTMLImageElement; gestures: string[]; walk: number[] };
72
72
  /** height: metres standing (a grown-up 1.62, a cat 0.3), so each is drawn at its own size; upright false: an animal, drawn as painted. */
73
- type Figure = { img: HTMLImageElement; face?: PaintedFace; faceInfo?: FaceDefinition; height?: number; upright?: boolean; rig?: { rig: Rig; parts: Map<PartName, HTMLImageElement>; sheet?: HTMLImageElement; canvas: HTMLCanvasElement }; model?: Figure3D; sprites?: { info: SpriteInfo; sheets: Sheet[]; talk: Sheet[] } };
73
+ type Figure = { img: HTMLImageElement; face?: PaintedFace; faceInfo?: FaceDefinition; height?: number; upright?: boolean; share?: number; rig?: { rig: Rig; parts: Map<PartName, HTMLImageElement>; sheet?: HTMLImageElement; canvas: HTMLCanvasElement }; model?: Figure3D; sprites?: { info: SpriteInfo; sheets: Sheet[]; talk: Sheet[] } };
74
74
  /** y: the floor row the cut stands on, which sets how far it slides with parallax. */
75
75
  /** Things that float and ride the swell, by their names. */
76
76
  const FLOATS = /\b(boat|rowboat|dinghy|skiff|canoe|kayak|raft|ship|barge|punt|gondola|sampan|junk|ferry|trawler|yacht|sailboat)s?\b/i;
@@ -386,7 +386,7 @@ class Game {
386
386
  await Promise.all(Object.entries(this.data.cast).map(async ([key, cast]) => {
387
387
  const img = await loadImage(files(cast.image)).catch(() => undefined);
388
388
  if (!img) return;
389
- const figure: Figure = { img, height: cast.height, upright: cast.upright };
389
+ const figure: Figure = { img, height: cast.height, upright: cast.upright, share: cast.share };
390
390
  if (cast.rig && cast.parts) {
391
391
  const parts = new Map<PartName, HTMLImageElement>();
392
392
  await Promise.all(cast.rig.parts.map(async (p) => parts.set(p.name, await loadImage(files(`${cast.parts}/${p.file}`)))));
@@ -2057,15 +2057,19 @@ class Game {
2057
2057
  // ?people=0: the place alone, for a landscape take; everyone keeps playing, just unseen.
2058
2058
  if (nobody) continue;
2059
2059
  if (who.npc) {
2060
+ this.bodyTop = undefined;
2060
2061
  const drawn = this.drawWho(who, lit, 'body');
2062
+ const bodyTop = this.bodyTop;
2061
2063
  this.drawn.push({ hot: who.npc, rect: drawn });
2062
2064
  const body = who.npc.npc?.body;
2063
- this.occlude(drawn, who.y, gone, body?.x, body && (body.foot ?? stepFootY(g, body.x, who.y)));
2065
+ this.occlude(drawn, who.y, gone, body?.x, body && (body.foot ?? stepFootY(g, body.x, who.y)), bodyTop);
2064
2066
  } else {
2067
+ this.bodyTop = undefined;
2065
2068
  const drawn = this.drawWho(who, lit, 'body');
2069
+ const bodyTop = this.bodyTop;
2066
2070
  if (who.body === this.hero) rect = drawn;
2067
2071
  // The depth map goes by where the feet are drawn: on a stair's tread, not at the walker's row.
2068
- this.occlude(drawn, who.y, gone, who.body?.x, who.body && (who.body.foot ?? stepFootY(g, who.body.x, who.y)));
2072
+ this.occlude(drawn, who.y, gone, who.body?.x, who.body && (who.body.foot ?? stepFootY(g, who.body.x, who.y)), bodyTop);
2069
2073
  }
2070
2074
  }
2071
2075
  burn();
@@ -2136,14 +2140,14 @@ class Game {
2136
2140
  * than the feet. Painted figures and painted things are equally lit and equally sharp; this occlusion is
2137
2141
  * the cue that puts the figure in front. ctx is already shifted for parallax.
2138
2142
  */
2139
- private separate(x: number, y: number, h: number, paint: (c: CanvasRenderingContext2D) => number) {
2143
+ private separate(x: number, y: number, h: number, paint: (c: CanvasRenderingContext2D) => number, tall = h) {
2140
2144
  const { focus: d, g } = this.place;
2141
2145
  const feet = y - g.scale.horizonY;
2142
2146
  if (!d || separateScale <= 0 || feet < 4 || h < 8) return;
2143
2147
  const blur = h * SEPARATE_BLUR;
2144
2148
  const pad = Math.ceil(blur * 2.5 + h * SEPARATE_SHIFT);
2145
- const [bw, bh] = [Math.ceil(h * 1.2) + 2 * pad, Math.ceil(h * 1.15) + 2 * pad];
2146
- const [ox, oy] = [Math.round(bw / 2), Math.ceil(pad + h * 1.1)];
2149
+ const [bw, bh] = [Math.ceil(Math.max(h * 1.2, tall * 1.2)) + 2 * pad, Math.ceil(tall + h * 0.15) + 2 * pad];
2150
+ const [ox, oy] = [Math.round(bw / 2), Math.ceil(pad + tall + h * 0.1)];
2147
2151
  const c = this.sepCanvas;
2148
2152
  if (c.width < bw || c.height < bh) Object.assign(c, { width: Math.max(c.width, bw), height: Math.max(c.height, bh) });
2149
2153
  const cc = c.getContext('2d')!;
@@ -2399,6 +2403,10 @@ class Game {
2399
2403
  // A rig walks its legs; a plain cut-out (or the stand-in) only bobs and rocks as it slides.
2400
2404
  const bob = creature ? creature.lift * h : rig || model || sprites || !walking ? 0 : Math.abs(Math.sin(phase * Math.PI * 2)) * h * 0.025;
2401
2405
  const rock = creature ? creature.lean * (facing < 0 ? -1 : 1) : rig || model || sprites || !walking ? 0 : Math.sin(phase * Math.PI * 2) * 0.035;
2406
+ // How far above the feet the drawn figure reaches: the body's height, or more when it holds something over
2407
+ // its head (a walk sheet cut with it, or a cut-out's cut.json share). Scratch canvases and boxes are sized by it.
2408
+ const tall = rig ? Math.max(h, (h * rig.rig.ground) / rig.rig.height) : !model && !sprites && figure?.img && figure.share ? h / Math.max(0.2, Math.min(1, figure.share)) : h;
2409
+ this.bodyTop = y - h * 1.04;
2402
2410
  let rigCanvas: CanvasImageSource | undefined;
2403
2411
  if (rig) {
2404
2412
  const at = pose(rig.rig, phase, walking);
@@ -2446,10 +2454,12 @@ class Game {
2446
2454
  return Math.max(ax, rig.canvas.width - ax) * s;
2447
2455
  }
2448
2456
  const img = figure?.img;
2449
- const w = img ? (h * img.width) / img.height : h * 0.36;
2457
+ // A cut-out holding something over its head (cut.json share): the body keeps its height, the thing rises above.
2458
+ const ih = h / Math.max(0.2, Math.min(1, figure?.share ?? 1));
2459
+ const w = img ? (ih * img.width) / img.height : h * 0.36;
2450
2460
  // A creature squashes and stretches from the feet up, keeping its volume.
2451
2461
  const [sx2, sy2] = creature ? [1 / Math.sqrt(creature.stretch), creature.stretch] : [1, 1];
2452
- if (img) c.drawImage(figure?.face?.draw(img, '$image', 0, 1, 1, articulation, faceTime) ?? img, (-w / 2) * sx2, -h * sy2, w * sx2, h * sy2);
2462
+ if (img) c.drawImage(figure?.face?.draw(img, '$image', 0, 1, 1, articulation, faceTime) ?? img, (-w / 2) * sx2, -ih * sy2, w * sx2, ih * sy2);
2453
2463
  else drawFigure(c, h, colour);
2454
2464
  return w / 2;
2455
2465
  };
@@ -2467,7 +2477,7 @@ class Game {
2467
2477
  const breath = 0.5 + 0.5 * Math.sin(t * ((2 * Math.PI) / 3.6));
2468
2478
  const m = c.getTransform();
2469
2479
  const density = Math.max(1, Math.min(1.5, Math.hypot(m.a, m.b)));
2470
- return idleWarp(c, paintRaw, token, h, density, sway, k * 0.02 * breath, 0.009 * k * Math.sin(t * ((2 * Math.PI) / 4.1) + 0.7));
2480
+ return idleWarp(c, paintRaw, token, h, density, sway, k * 0.02 * breath, 0.009 * k * Math.sin(t * ((2 * Math.PI) / 4.1) + 0.7), tall);
2471
2481
  };
2472
2482
  // How much of the floor under the feet lies in a painted shadow: the person stands in it too.
2473
2483
  const shaded = Math.max(this.shadeAt(x, y, h), this.fireShadeAt(x, y, h));
@@ -2507,7 +2517,7 @@ class Game {
2507
2517
  const bright = this.lightAt(x, y - h * 0.5) * (1 - shaded * strength) * breath;
2508
2518
  // A party member is edged in its player's colour (twice, so the edge reads at a glance on a busy painting).
2509
2519
  const edge = glow ? ` drop-shadow(0 0 ${Math.max(2, h * 0.008).toFixed(1)}px ${glow}) drop-shadow(0 0 ${Math.max(2, h * 0.008).toFixed(1)}px ${glow})` : '';
2510
- this.separate(x, y, h, paintRaw);
2520
+ this.separate(x, y, h, paintRaw, tall);
2511
2521
  ctx.filter = `brightness(${bright.toFixed(2)})${lit ? ' drop-shadow(0 0 5px rgba(255,225,150,0.95))' : ''}${edge}`;
2512
2522
  const frameAt = ctx.getTransform();
2513
2523
  ctx.translate(x, y - bob);
@@ -2520,9 +2530,10 @@ class Game {
2520
2530
  ctx.setTransform(feetAt);
2521
2531
  this.rim(h, paint, bright * (1 - shaded), light);
2522
2532
  }
2523
- this.shadeOver(paint, frameAt, feetAt, [x - half - 4, y - h * 1.06 - bob - 4, x + half + 4, y + Math.max(3, h * 0.04) + 4]);
2533
+ this.shadeOver(paint, frameAt, feetAt, [x - half - 4, y - tall * 1.06 - bob - 4, x + half + 4, y + Math.max(3, h * 0.04) + 4]);
2524
2534
  ctx.restore();
2525
- return [x - half - 2 + sx, y - h * 1.04 - bob - 2 + sy, x + half + 2 + sx, y + Math.max(3, h * 0.04) + sy];
2535
+ this.bodyTop = y - h * 1.04 - bob - 2 + sy;
2536
+ return [x - half - 2 + sx, y - tall * 1.04 - bob - 2 + sy, x + half + 2 + sx, y + Math.max(3, h * 0.04) + sy];
2526
2537
  }
2527
2538
 
2528
2539
  /**
@@ -2933,16 +2944,18 @@ class Game {
2933
2944
  if (!light) return 1;
2934
2945
  const c = Math.min(light.cols - 1, Math.max(0, Math.floor(x / LIGHT_CELL)));
2935
2946
  const r = Math.min(light.rows - 1, Math.max(0, Math.floor(y / LIGHT_CELL)));
2936
- let sum = 0;
2937
- let n = 0;
2938
- for (let dr = -1; dr <= 1; dr++) for (let dc = -1; dc <= 1; dc++) {
2947
+ // The light, not the colour of what lies there: a black tile, a dark table or a blue chair is dark paint in
2948
+ // bright light, so the brighter cells around (the upper quartile of a 5 x 5) say how lit the spot is.
2949
+ const around: number[] = [];
2950
+ for (let dr = -2; dr <= 2; dr++) for (let dc = -2; dc <= 2; dc++) {
2939
2951
  const rr = r + dr;
2940
2952
  const cc = c + dc;
2941
2953
  if (rr < 0 || cc < 0 || rr >= light.rows || cc >= light.cols) continue;
2942
- sum += light.luma[rr * light.cols + cc];
2943
- n++;
2954
+ around.push(light.luma[rr * light.cols + cc]);
2944
2955
  }
2945
- return Math.min(1.1, Math.max(0.4, 0.35 + (0.65 * sum) / n / 150));
2956
+ around.sort((a, b) => a - b);
2957
+ const lit = around[Math.floor(around.length * 0.75)] ?? 150;
2958
+ return Math.min(1.1, Math.max(0.4, 0.35 + (0.65 * lit) / 150));
2946
2959
  }
2947
2960
 
2948
2961
  /** For the explorer: where the depth map puts the painting in front of feet at (x, feetY), over rect (src/qa/probe.ts). */
@@ -2955,6 +2968,8 @@ class Game {
2955
2968
  private snap?: HTMLCanvasElement;
2956
2969
  private snapped = false;
2957
2970
  private depthMask?: HTMLCanvasElement;
2971
+ /** The top of the body of the person drawn last (drawPerson), below anything held over their head. */
2972
+ private bodyTop?: number;
2958
2973
  private depthPatch?: HTMLCanvasElement;
2959
2974
 
2960
2975
  /**
@@ -3035,8 +3050,10 @@ class Game {
3035
3050
  }
3036
3051
 
3037
3052
  /** Puts the painting back over the hero, column by column, wherever an occluder stands in front of him. */
3038
- private occlude(rect: [number, number, number, number], feetY: number, gone: Set<string>, feetX?: number, drawnFeetY?: number) {
3039
- if (this.depthOcclusion) this.occludeDepth(rect, drawnFeetY ?? feetY, feetX);
3053
+ private occlude(rect: [number, number, number, number], feetY: number, gone: Set<string>, feetX?: number, drawnFeetY?: number, bodyTop?: number) {
3054
+ // The depth map hides only the body: what is held up over the head (a giant slice of toast) reaches where the
3055
+ // map holds the near ceiling and the wall's top, which never stand in front of someone on the floor.
3056
+ if (this.depthOcclusion) this.occludeDepth(bodyTop !== undefined && bodyTop > rect[1] ? [rect[0], bodyTop, rect[2], rect[3]] : rect, drawnFeetY ?? feetY, feetX);
3040
3057
  const support = (k: string) => this.place.byKey.get(k);
3041
3058
  for (const cut of this.place.cuts) {
3042
3059
  if (cut.occ.thing && gone.has(cut.occ.thing)) continue;
@@ -3680,8 +3697,9 @@ let idleOwner: { token: object; density: number; half: number } | undefined;
3680
3697
  * sway x height x u^1.7 (u = height above the feet as a share of the figure), so the feet stay planted and the head
3681
3698
  * moves most; the slices above the hips lift by up to `lift` x height (the breath), and the head nods by `nod`.
3682
3699
  */
3683
- function idleWarp(c: CanvasRenderingContext2D, paint: (c: CanvasRenderingContext2D) => number, token: object, h: number, density: number, sway: number, lift: number, nod: number): number {
3684
- const W = Math.ceil(h * 1.6 * density), H = Math.ceil(h * 1.35 * density);
3700
+ function idleWarp(c: CanvasRenderingContext2D, paint: (c: CanvasRenderingContext2D) => number, token: object, h: number, density: number, sway: number, lift: number, nod: number, tall = h): number {
3701
+ // Wide and high enough for what the figure holds over its head too (tall), else it is cut off flat.
3702
+ const W = Math.ceil(Math.max(h * 1.6, tall * 1.2) * density), H = Math.ceil((tall + h * 0.35) * density);
3685
3703
  const foot = Math.ceil(h * 0.15 * density);
3686
3704
  if (!(W >= 8 && H >= 8 && W <= 4096 && H <= 4096)) return paint(c);
3687
3705
  const scratch = (idleScratch ??= document.createElement('canvas'));
@@ -4,7 +4,7 @@
4
4
 
5
5
  `build` also makes the characters, after the places (steps `cast`, `model`, `sprites`; each also runs alone):
6
6
 
7
- - `diorama cast <slug>` paints the hero and every NPC (cast/<key>.png, with what they hold: a lantern or a net SAM's person leaves out stays on, gaps it leaves are filled, and what floats beside them as theirs, a heart or the bubble they are in, comes along) and makes their 2D walk: SAM finds arms, legs and boots to place the joints, then the whole painting is bent by those bones as one fine mesh, not cut into parts, so a coat, an apron or a skirt stretches with the legs and never tears (src/scene/warp.ts). The walk is drawn once into a sheet (cast/<key>/walk.png, 8 frames and the untouched painting for standing; joints in cast/<key>/rig.json) and measured: a walk that opens holes or loses a piece is marked in rig.json `check` and the game slides the painted figure as one piece instead. `diorama rig <slug> [key...]` (Studio agent: `cast_rewalk`) makes the walk again from the paintings already there, with no Muse calls. A drawing SAM does not see as a person is asked as a character; when it still finds none, `cast_status` says why in `rigIssue` and `moves` says how the game moves them instead. Walk sheets whose frames left the pose are listed in `spriteIssues`; `sprites_redo` paints new takes. Spark also says how tall each one stands and whether upright like a person (cast/figures.json, a grown-up 1.62 m): a cat or a child is drawn at its own size, and a creature that is not upright (an animal, a round eye on tiny feet, a living teapot) gets no 2D rig or sprites: it moves as one body, hopping as it goes, squashing as it lands, leaning the way it goes, breathing and blinking (src/play/creature.ts), as its painted figure or, when it is in the sprites plan, as its own 3D model.
7
+ - `diorama cast <slug>` paints the hero and every NPC (cast/<key>.png, with what they hold: a lantern or a net SAM's person leaves out stays on, gaps it leaves are filled, and what floats beside them as theirs, a heart or the bubble they are in, comes along) and makes their 2D walk: SAM finds arms, legs and boots to place the joints, then the whole painting is bent by those bones as one fine mesh, not cut into parts, so a coat, an apron or a skirt stretches with the legs and never tears (src/scene/warp.ts). The walk is drawn once into a sheet (cast/<key>/walk.png, 8 frames and the untouched painting for standing; joints in cast/<key>/rig.json) and measured: a walk that opens holes or loses a piece is marked in rig.json `check` and the game slides the painted figure as one piece instead. What the description says they hold or carry (one Spark call, only when the description says they hold something; the person's own list in cast/fixes.json `holds`, Studio agent: `cast_holds`) is found by SAM on its own, or as what stands out from the plain ground and touches them when SAM finds nothing, and kept on the cut-out and the walk however big it is: a slice of toast held over the head in both hands goes with the body and both arms carry it instead of swinging; cast/<key>/cut.json keeps the list and the share of the cut-out's height the body takes, so the game draws the body at its own height with the thing above it (src/scene/held.ts). `diorama rig <slug> [key...]` (Studio agent: `cast_rewalk`) makes the walk again from the paintings already there, with no Muse calls. A drawing SAM does not see as a person is asked as a character; when it still finds none, `cast_status` says why in `rigIssue` and `moves` says how the game moves them instead. Walk sheets whose frames left the pose are listed in `spriteIssues`; `sprites_redo` paints new takes. Spark also says how tall each one stands and whether upright like a person (cast/figures.json, a grown-up 1.62 m): a cat or a child is drawn at its own size, and a creature that is not upright (an animal, a round eye on tiny feet, a living teapot) gets no 2D rig or sprites: it moves as one body, hopping as it goes, squashing as it lands, leaning the way it goes, breathing and blinking (src/play/creature.ts), as its painted figure or, when it is in the sprites plan, as its own 3D model.
8
8
  - `diorama model <slug>` gives each character who needs sprites a rigged 3D model: Muse paints them from four sides, TRELLIS turns the views into a mesh and Make-It-Animatable adds a skeleton (both open Hugging Face Spaces; set `HUGGING_FACE_API_KEY` or `HF_TOKEN`, as they run on your account's GPU quota). The model only supplies poses. A creature (not upright) is painted from four sides as it is (no A-pose, no hands) and keeps TRELLIS's mesh with no skeleton (`model.json` `creature: true`): the game hops, squashes, leans and blinks the whole model, so a creature hero or NPC needs no sheets. Studio's Cast **Animate in 3D** (action `cast_animate`) builds a model for anyone the build skips, and the model step keeps building it. The figure is stood up straight in the file, and the poses start from the shape the skin was bound in (the reconstructed figure), not from whatever pose the rig saved its bones in (Make-It-Animatable's T-pose can sit a figure in a long coat down). What the character wears (a satchel, a backpack) is read off the front view and named in every other view's prompt with the side it shows on; Spark then checks all four together, and a view with it on the wrong side, or with anything standing on the ground at the feet, is painted again (up to 2 rounds; a wrong front repaints all four once). Things held in the hands are left out of the views: a suitcase put down beside the feet would be fused to a boot and swing with the leg in every sprite. A view the image safety filter refuses is asked once more in calm words (the looks left to the picture, the hands said plainly); refused again, the model made before stays, or the character walks with its 2D rig, and the build goes on.
9
9
  - Optional local Blender (3.6+, found on PATH, in /Applications, or at `DIORAMA_BLENDER`; on/off in Studio Setup, **3D animation**): after the rig, `src/model/blender/relax.py` puts the figure into a calm rest pose. Muse paints the reference with arms raised and feet wide, TRELLIS builds that pose, and the walk then had to turn each arm ~100° down from it, which pinched the shoulders, splayed the hands sideways and kept the legs straddled. Blender poses arms down by the sides with palms to the thighs and feet under the hips, smooths the bent shoulders and hips (Corrective Smooth) and makes that the rest pose (`model.json` `relaxed: true`, the rig as it came kept in `model.source.glb`). The original surface rides along as the `_REST` vertex attribute, so views fitted and painted in the original pose still land where they belong. `diorama model <slug> [key] --relax` (Studio agent: `cast_relax`) does it for existing models and draws their UV sprites again, with no calls. Without Blender nothing changes.
10
10
  - `diorama sprites <slug>` draws their walk and talk sheets (cast/<key>/sprites/): the model walks or gestures in a sheet of 8 frames, Muse draws the sheet again in the world's style, and each frame is cut out and lined up with its pose. A sheet that Muse handed back barely changed is drawn again over a different guide. A walk frame Muse painted out of the gait (a crouch, a sit: the painted figure under 88% of the sheet's usual height) is listed as `off` in sprites.json; the walk steps over it and standing still never shows it. Only what the story uses is drawn: the walk facings the hero's routes need, one talk sheet per speaker in the facing of their conversation, and the gesture Spark picks for each line (`--plan` prints the plan without drawing). Rendering the guides needs Playwright's Chromium (`diorama browsers`).
package/docs/setup.md CHANGED
@@ -32,7 +32,7 @@ npm run doctor -- --now # before building in front of people: is the netw
32
32
  npm run studio # the GUI
33
33
  npm run build -- <slug> "<idea>"
34
34
  npm run play -- <slug>
35
- npm run publish -- <slug>
35
+ npm run pages -- <slug> # diorama publish (npm keeps the script name publish for itself)
36
36
  npm run export -- <slug> --zip
37
37
  npm run browsers # Playwright's Chromium, for explore, playthrough and sprites
38
38
  npm run diorama -- <command> # any other command, e.g. npm run diorama -- masks <slug>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtanczyk/diorama",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Turn a story prompt into playable painted point-and-click scenes with Meta Muse and SAM 3.1",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -42,7 +42,7 @@
42
42
  "studio": "node app/bin/diorama.mjs studio",
43
43
  "build": "node app/bin/diorama.mjs build",
44
44
  "play": "node app/bin/diorama.mjs play",
45
- "publish": "node app/bin/diorama.mjs publish",
45
+ "pages": "node app/bin/diorama.mjs publish",
46
46
  "export": "node app/bin/diorama.mjs export",
47
47
  "browsers": "node app/bin/diorama.mjs browsers",
48
48
  "typecheck": "tsc --noEmit -p app && tsc --noEmit -p app/web && tsc --noEmit -p app/studio",