@origingame/create-game 0.1.1 → 0.3.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.
Files changed (179) hide show
  1. package/README.md +17 -9
  2. package/archetypes/README.md +5 -2
  3. package/archetypes/arena/.origin/setup +14 -0
  4. package/archetypes/arena/LICENSES.md +8 -0
  5. package/archetypes/arena/README.md +67 -0
  6. package/archetypes/arena/assets/audio/coin.ogt +9 -0
  7. package/archetypes/arena/assets/audio/dash.ogt +9 -0
  8. package/archetypes/arena/assets/audio/win.ogt +9 -0
  9. package/archetypes/arena/assets/fx/coin.ogt +20 -0
  10. package/archetypes/arena/assets/looks/arena.ogt +10 -0
  11. package/archetypes/arena/gitattributes +22 -0
  12. package/archetypes/arena/gitignore +8 -0
  13. package/archetypes/arena/host.d.ts +46 -0
  14. package/archetypes/arena/package.json +18 -0
  15. package/archetypes/arena/project.ogt +14 -0
  16. package/archetypes/arena/src/game.ts +314 -0
  17. package/archetypes/arena/src/main.ts +100 -0
  18. package/archetypes/arena/tests/duel.og.ts +32 -0
  19. package/archetypes/arena/tests/solo.og.ts +22 -0
  20. package/archetypes/arena/tsconfig.json +17 -0
  21. package/archetypes/idle/LICENSES.md +1 -1
  22. package/archetypes/idle/README.md +3 -3
  23. package/archetypes/idle/assets/looks/idle.ogt +1 -1
  24. package/archetypes/idle/assets/{skins → styles}/sparkorb/LICENSES.md +4 -4
  25. package/archetypes/idle/assets/{skins/sparkorb/skin.ogt → styles/sparkorb/style.ogt} +47 -47
  26. package/archetypes/idle/package.json +1 -1
  27. package/archetypes/idle/project.ogt +1 -1
  28. package/archetypes/idle/references/metal/card.png +0 -0
  29. package/archetypes/idle/references/metal/orbit.png +0 -0
  30. package/archetypes/idle/references/metal/title.png +0 -0
  31. package/archetypes/idle/src/main.ts +2 -2
  32. package/archetypes/idle/src/ui.ts +11 -11
  33. package/archetypes/idle/tools/make-assets.py +4 -4
  34. package/archetypes/match3/package.json +1 -1
  35. package/archetypes/match3/project.ogt +1 -1
  36. package/archetypes/rhythm/LICENSES.md +1 -1
  37. package/archetypes/rhythm/README.md +3 -3
  38. package/archetypes/rhythm/assets/{skins → styles}/lanebeat/LICENSES.md +4 -4
  39. package/archetypes/rhythm/assets/{skins/lanebeat/skin.ogt → styles/lanebeat/style.ogt} +45 -45
  40. package/archetypes/rhythm/package.json +1 -1
  41. package/archetypes/rhythm/project.ogt +1 -1
  42. package/archetypes/rhythm/references/metal/results.png +0 -0
  43. package/archetypes/rhythm/references/metal/title.png +0 -0
  44. package/archetypes/rhythm/src/main.ts +2 -2
  45. package/archetypes/rhythm/src/ui.ts +11 -11
  46. package/archetypes/roguelike/LICENSES.md +1 -1
  47. package/archetypes/roguelike/README.md +3 -3
  48. package/archetypes/roguelike/assets/{skins → styles}/lampdive/LICENSES.md +4 -4
  49. package/archetypes/roguelike/assets/{skins/lampdive/skin.ogt → styles/lampdive/style.ogt} +41 -41
  50. package/archetypes/roguelike/package.json +1 -1
  51. package/archetypes/roguelike/project.ogt +1 -1
  52. package/archetypes/roguelike/references/metal/over.png +0 -0
  53. package/archetypes/roguelike/references/metal/title.png +0 -0
  54. package/archetypes/roguelike/src/main.ts +2 -2
  55. package/archetypes/roguelike/src/ui.ts +7 -7
  56. package/archetypes/runner/package.json +1 -1
  57. package/archetypes/runner/project.ogt +1 -1
  58. package/archetypes/third-person/LICENSES.md +1 -1
  59. package/archetypes/third-person/README.md +3 -3
  60. package/archetypes/third-person/assets/{skins → styles}/ember/LICENSES.md +2 -2
  61. package/archetypes/third-person/assets/{skins/ember/skin.ogt → styles/ember/style.ogt} +40 -41
  62. package/archetypes/third-person/package.json +1 -1
  63. package/archetypes/third-person/project.ogt +1 -1
  64. package/archetypes/third-person/references/metal/beacon.png +0 -0
  65. package/archetypes/third-person/references/metal/dawn.png +0 -0
  66. package/archetypes/third-person/references/metal/title.png +0 -0
  67. package/archetypes/third-person/references/webgl2/beacon.png +0 -0
  68. package/archetypes/third-person/src/main.ts +2 -2
  69. package/archetypes/third-person/src/ui.ts +10 -10
  70. package/archetypes/topdown/package.json +1 -1
  71. package/archetypes/topdown/project.ogt +1 -1
  72. package/archetypes/tower-defense/LICENSES.md +1 -1
  73. package/archetypes/tower-defense/README.md +5 -5
  74. package/archetypes/tower-defense/assets/{skins → styles}/pathguard/LICENSES.md +4 -4
  75. package/archetypes/tower-defense/assets/styles/pathguard/atlas.png +0 -0
  76. package/archetypes/tower-defense/assets/styles/pathguard/style.ogt +111 -0
  77. package/archetypes/tower-defense/package.json +1 -1
  78. package/archetypes/tower-defense/project.ogt +1 -1
  79. package/archetypes/tower-defense/references/metal/end.png +0 -0
  80. package/archetypes/tower-defense/references/metal/title.png +0 -0
  81. package/archetypes/tower-defense/references/metal/wave.png +0 -0
  82. package/archetypes/tower-defense/src/main.ts +2 -2
  83. package/archetypes/tower-defense/src/ui.ts +19 -17
  84. package/bin/create.ts +80 -20
  85. package/looks/catalogue.json +40 -40
  86. package/package.json +5 -4
  87. package/template/README.md +2 -1
  88. package/template/assets/audio/sting.ogt +10 -0
  89. package/template/assets/timelines/intro.ogt +101 -0
  90. package/template/assets/timelines/narration.srt +11 -0
  91. package/template/host.d.ts +46 -0
  92. package/template/package.json +1 -1
  93. package/template/project.ogt +1 -1
  94. package/template/references/metal/intro-4.png +0 -0
  95. package/template/references/metal/intro-8.png +0 -0
  96. package/template/src/main.ts +1 -1
  97. package/template/tests/frames.ts +134 -0
  98. package/template/tests/intro.og.ts +65 -0
  99. package/template/tests/smoke.og.ts +30 -36
  100. package/template/tsconfig.json +1 -1
  101. package/template-2d/.origin/setup +14 -0
  102. package/template-2d/LICENSES.md +15 -0
  103. package/template-2d/README.md +42 -0
  104. package/template-2d/assets/audio/coin.wav +0 -0
  105. package/template-2d/assets/audio/jump.wav +0 -0
  106. package/template-2d/assets/fonts/OFL.txt +93 -0
  107. package/template-2d/assets/fonts/ui.ttf +0 -0
  108. package/template-2d/assets/fonts/ui.ttf.ogt +11 -0
  109. package/template-2d/assets/looks/origin-game-2d.ogt +12 -0
  110. package/template-2d/assets/maps/level.ogt +112 -0
  111. package/template-2d/assets/rigs/keeper.ogt +292 -0
  112. package/template-2d/assets/rigs/keeper.png +0 -0
  113. package/template-2d/assets/rigs/keeper.png.ogt +7 -0
  114. package/template-2d/assets/sprites/coin.ogt +11 -0
  115. package/template-2d/assets/sprites/coin.png +0 -0
  116. package/template-2d/assets/sprites/coin.png.ogt +7 -0
  117. package/template-2d/assets/sprites/courier.ogt +34 -0
  118. package/template-2d/assets/sprites/courier.png +0 -0
  119. package/template-2d/assets/sprites/courier.png.ogt +7 -0
  120. package/template-2d/assets/strings/en.ogt +11 -0
  121. package/template-2d/assets/tiles/city.ogt +17 -0
  122. package/template-2d/assets/tiles/city.png +0 -0
  123. package/template-2d/assets/tiles/city.png.ogt +7 -0
  124. package/template-2d/gitattributes +22 -0
  125. package/template-2d/gitignore +8 -0
  126. package/template-2d/package.json +18 -0
  127. package/template-2d/project.ogt +21 -0
  128. package/template-2d/src/main.ts +460 -0
  129. package/template-2d/tests/keeper.og.ts +93 -0
  130. package/template-2d/tests/smoke.og.ts +60 -0
  131. package/template-2d/tsconfig.json +17 -0
  132. package/archetypes/tower-defense/assets/skins/pathguard/atlas.png +0 -0
  133. package/archetypes/tower-defense/assets/skins/pathguard/skin.ogt +0 -111
  134. /package/archetypes/idle/assets/{skins → styles}/sparkorb/ChakraPetch-BoldItalic.ttf +0 -0
  135. /package/archetypes/idle/assets/{skins → styles}/sparkorb/ChakraPetch-BoldItalic.ttf.ogt +0 -0
  136. /package/archetypes/idle/assets/{skins → styles}/sparkorb/OFL.txt +0 -0
  137. /package/archetypes/idle/assets/{skins → styles}/sparkorb/Orbitron-Medium.ttf +0 -0
  138. /package/archetypes/idle/assets/{skins → styles}/sparkorb/Orbitron-Medium.ttf.ogt +0 -0
  139. /package/archetypes/idle/assets/{skins → styles}/sparkorb/Rajdhani-Bold.ttf +0 -0
  140. /package/archetypes/idle/assets/{skins → styles}/sparkorb/Rajdhani-Bold.ttf.ogt +0 -0
  141. /package/archetypes/idle/assets/{skins → styles}/sparkorb/atlas.png +0 -0
  142. /package/archetypes/idle/assets/{skins → styles}/sparkorb/atlas.png.ogt +0 -0
  143. /package/archetypes/idle/assets/{skins → styles}/sparkorb/icons.png +0 -0
  144. /package/archetypes/idle/assets/{skins → styles}/sparkorb/icons.png.ogt +0 -0
  145. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/ChakraPetch-Medium.ttf +0 -0
  146. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/ChakraPetch-Medium.ttf.ogt +0 -0
  147. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/JetBrainsMono-Regular.ttf +0 -0
  148. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/JetBrainsMono-Regular.ttf.ogt +0 -0
  149. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/OFL.txt +0 -0
  150. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/Orbitron-Bold.ttf +0 -0
  151. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/Orbitron-Bold.ttf.ogt +0 -0
  152. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/atlas.png +0 -0
  153. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/atlas.png.ogt +0 -0
  154. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/icons.png +0 -0
  155. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/icons.png.ogt +0 -0
  156. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/DotGothic16-Regular.ttf +0 -0
  157. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/DotGothic16-Regular.ttf.ogt +0 -0
  158. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/OFL.txt +0 -0
  159. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/atlas.png +0 -0
  160. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/atlas.png.ogt +0 -0
  161. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/icons.png +0 -0
  162. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/icons.png.ogt +0 -0
  163. /package/archetypes/third-person/assets/{skins → styles}/ember/Cinzel-Bold.ttf +0 -0
  164. /package/archetypes/third-person/assets/{skins → styles}/ember/Cinzel-Bold.ttf.ogt +0 -0
  165. /package/archetypes/third-person/assets/{skins → styles}/ember/Newsreader-Medium.ttf +0 -0
  166. /package/archetypes/third-person/assets/{skins → styles}/ember/Newsreader-Medium.ttf.ogt +0 -0
  167. /package/archetypes/third-person/assets/{skins → styles}/ember/OFL.txt +0 -0
  168. /package/archetypes/third-person/assets/{skins → styles}/ember/atlas.png +0 -0
  169. /package/archetypes/third-person/assets/{skins → styles}/ember/atlas.png.ogt +0 -0
  170. /package/archetypes/third-person/assets/{skins → styles}/ember/icons.png +0 -0
  171. /package/archetypes/third-person/assets/{skins → styles}/ember/icons.png.ogt +0 -0
  172. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/Fredoka-SemiBold.ttf +0 -0
  173. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/Fredoka-SemiBold.ttf.ogt +0 -0
  174. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/LilitaOne-Regular.ttf +0 -0
  175. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/LilitaOne-Regular.ttf.ogt +0 -0
  176. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/OFL.txt +0 -0
  177. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/atlas.png.ogt +0 -0
  178. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/icons.png +0 -0
  179. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/icons.png.ogt +0 -0
package/bin/create.ts CHANGED
@@ -2,13 +2,16 @@
2
2
  // `npm create @origingame/game` (and `bun create`): scaffolds the Origin
3
3
  // Engine project template (templates/game) into a directory, naming the game.
4
4
  //
5
- // create [dir] [--name "Name"] [--id slug] [--archetype runner|match3|...]
5
+ // create [dir] [--name "Name"] [--id slug] [--profile 2d|3d] [--archetype runner|match3|...]
6
6
  // [--premise "one line"] [--look <slug>] [--force]
7
7
  //
8
- // The template comes from ./template/ in the published package (prepack:
9
- // scripts/pack-template.ts) or ../../templates/game in the engine repository;
10
- // an archetype (a complete small game to start from) from ./archetypes/<name>/
11
- // or ../../templates/archetypes/<name>/.
8
+ // The template comes from ./template/ (the 3D template, templates/game) or
9
+ // ./template-2d/ (the 2D starter, templates/game-2d) in the published
10
+ // package (prepack: scripts/pack-template.ts), or from ../../templates/ in
11
+ // the engine repository; an archetype (a complete small game to start from)
12
+ // from ./archetypes/<name>/ or ../../templates/archetypes/<name>/. The 2D
13
+ // starter is written for --profile 2d, or for a premise that says 2D
14
+ // (platformer, side-scroller, metroidvania, pixel art) without saying 3D.
12
15
  //
13
16
  // Every game gets its look, `assets/looks/<id>.ogt`, named by GameDesc.look
14
17
  // in the entry: it extends the style catalogue's preset given by --look, or
@@ -44,8 +47,10 @@ export interface CreateOptions {
44
47
  readonly name?: string;
45
48
  readonly id?: string;
46
49
  readonly force?: boolean;
47
- /** Start from an archetype (`runner`, `match3`, `topdown`, `tower-defense`, `roguelike`, `idle`, `rhythm`, `third-person`: templates/archetypes/README.md) instead of the project template. */
50
+ /** Start from an archetype (`runner`, `match3`, `topdown`, `tower-defense`, `roguelike`, `idle`, `rhythm`, `third-person`, `arena`: templates/archetypes/README.md) instead of the project template. */
48
51
  readonly archetype?: string;
52
+ /** The template's profile: `2d` writes the 2D starter (templates/game-2d), `3d` the 3D template (templates/game). Unset, a premise that says 2D picks `2d`; else `3d`. Not for an archetype, which has its own. */
53
+ readonly profile?: Profile;
49
54
  /** The template directory; found next to this file when omitted. */
50
55
  readonly template?: string;
51
56
  /** A style catalogue slug the game's look extends (og-taste §12). */
@@ -68,11 +73,22 @@ export interface CreatedLook {
68
73
  readonly chosen: "flag" | "premise" | "template" | "none";
69
74
  }
70
75
 
76
+ /** A project's profile (`project.ogt`'s `profile` line): 2D or 3D. */
77
+ export type Profile = "2d" | "3d";
78
+
79
+ /** The profile the game was written in, and how it was chosen. */
80
+ export interface CreatedProfile {
81
+ readonly value: Profile;
82
+ /** `flag` for --profile, `premise` for a premise that says 2D, `archetype` for an archetype's own, `template` for the 3D template by default. */
83
+ readonly chosen: "flag" | "premise" | "archetype" | "template";
84
+ }
85
+
71
86
  export interface Created {
72
87
  readonly dir: string;
73
88
  readonly name: string;
74
89
  readonly id: string;
75
90
  readonly files: readonly string[];
91
+ readonly profile: CreatedProfile;
76
92
  readonly look: CreatedLook;
77
93
  /** What the game's author should know, such as reference frames a look will move. */
78
94
  readonly notes: readonly string[];
@@ -86,7 +102,7 @@ export interface Style {
86
102
  readonly descriptor: string;
87
103
  readonly premise: Readonly<Record<string, string>>;
88
104
  readonly look: { readonly presetReady: boolean; readonly support: string; readonly missing: readonly string[] };
89
- readonly skin: string;
105
+ readonly ui: string;
90
106
  }
91
107
 
92
108
  /** The style catalogue this copy of the package reads. */
@@ -164,8 +180,30 @@ function title(id: string): string {
164
180
  .join(" ");
165
181
  }
166
182
 
167
- /** The template this copy of the package scaffolds. */
168
- export function templateDir(archetype?: string): string {
183
+ /** Words of a premise that say the game is 2D (platformer, side-scroller, metroidvania, pixel art, sprites, tiles). */
184
+ const TWO_D = new Set(["platformer", "platforming", "side-scroller", "side-scrolling", "sidescroller", "sidescrolling", "scroller", "metroidvania", "pixel", "pixel-art", "pixelart", "sprite", "tilemap", "tile", "8bit", "16bit", "1bit"]);
185
+ const THREE_D = new Set(["third-person", "first-person", "fps", "voxel", "lowpoly", "low-poly"]);
186
+
187
+ /**
188
+ * The profile a premise asks for: `2d` when it uses a 2D word (TWO_D, or
189
+ * `2D` itself) and no 3D word (THREE_D, or `3D`), else null: the template's
190
+ * own, 3D, applies.
191
+ */
192
+ export function premiseProfile(premise: string): Profile | null {
193
+ const asked = new Set(words(premise));
194
+ const says = (w: string) => new RegExp(`(^|[^a-z0-9])${w}($|[^a-z0-9])`, "i").test(premise);
195
+ if (says("3d") || [...THREE_D].some((w) => asked.has(w))) return null;
196
+ return says("2d") || [...TWO_D].some((w) => asked.has(w)) ? "2d" : null;
197
+ }
198
+
199
+ /** The `profile "2d"` line of a project manifest; `3d` when it has none. */
200
+ function profileOf(dir: string): Profile {
201
+ const m = /^profile[ \t]+"(2d|3d)"/m.exec(readFileSync(join(dir, "project.ogt"), "utf8"));
202
+ return m?.[1] === "2d" ? "2d" : "3d";
203
+ }
204
+
205
+ /** The template this copy of the package scaffolds: the archetype's, or the project template of a profile (3D unless `2d`). */
206
+ export function templateDir(archetype?: string, profile: Profile = "3d"): string {
169
207
  const here = dirname(fileURLToPath(import.meta.url));
170
208
  if (archetype !== undefined) {
171
209
  const roots = [join(here, "..", "archetypes"), join(here, "..", "..", "..", "templates", "archetypes")];
@@ -175,10 +213,15 @@ export function templateDir(archetype?: string): string {
175
213
  const known = roots.filter(existsSync).flatMap((r) => readdirSync(r).filter((d) => existsSync(join(r, d, "project.ogt"))));
176
214
  throw new Error(`no archetype '${archetype}': choose one of ${[...new Set(known)].sort().join(", ") || "(none shipped)"}`);
177
215
  }
178
- for (const candidate of [join(here, "..", "template"), join(here, "..", "..", "..", "templates", "game")]) {
216
+ const packed = profile === "2d" ? "template-2d" : "template";
217
+ const source = profile === "2d" ? "game-2d" : "game";
218
+ // In the engine repository the live template wins: a ./template/ left by an
219
+ // earlier prepack is stale as soon as templates/ changes. A published
220
+ // package has no templates/ beside it, so it scaffolds from its packed copy.
221
+ for (const candidate of [join(here, "..", "..", "..", "templates", source), join(here, "..", packed)]) {
179
222
  if (existsSync(join(candidate, "project.ogt"))) return candidate;
180
223
  }
181
- throw new Error(`no project template next to ${here}: expected ../template (published package) or ../../../templates/game (engine repository)`);
224
+ throw new Error(`no ${profile} project template next to ${here}: expected ../${packed} (published package) or ../../../templates/${source} (engine repository)`);
182
225
  }
183
226
 
184
227
  function ogtString(value: string): string {
@@ -241,8 +284,8 @@ function lookText(name: string, style: Style | undefined, slug: string | null, i
241
284
  head.push(
242
285
  `# It extends the style catalogue's ${slug} preset (${style.name}),`,
243
286
  style.look.support === "full" ? "# whose primitives all draw today." : `# support ${style.look.support}: ${style.look.missing.join("; ")}.`,
244
- `# The style's UI skin pack is ${style.skin}: copy templates/ui-kit/assets/skins/${style.skin}`,
245
- `# to assets/skins/${id}/ and add \`skin "skins/${id}/skin.ogt"\` to dress the UI.`,
287
+ `# The preset dresses the UI in the built-in ${style.ui} style; a \`ui "<slug>"\``,
288
+ `# line here picks another, or the game's own assets/styles/<name>/style.ogt.`,
246
289
  );
247
290
  } else if (slug) head.push(`# It extends ${slug}.`);
248
291
  else head.push("# It extends no preset yet: the frame is the engine's defaults plus the lines below.");
@@ -269,11 +312,11 @@ function lookReadme(look: CreatedLook, style: Style | undefined, entry: string,
269
312
  "after the first scene spawns and before `start` runs, and sets tone",
270
313
  "mapping and exposure, the post stack, the stylisation (`Stylise`),",
271
314
  "material defaults (`MaterialStyle`: toon, outlines, flat shading), fog,",
272
- "sky, the environment map and the UI skin.",
315
+ "sky, the environment map and the UI style (its `ui` line).",
273
316
  "",
274
317
  ];
275
318
  if (look.extends && style) {
276
- lines.push(`It extends \`${look.extends}\`${how}: "${style.name}" in the style`, `catalogue. ${style.descriptor} Its UI skin pack is \`${style.skin}\`.`, "");
319
+ lines.push(`It extends \`${look.extends}\`${how}: "${style.name}" in the style`, `catalogue. ${style.descriptor} Its UI style is \`${style.ui}\`.`, "");
277
320
  } else if (look.extends) lines.push(`It extends \`${look.extends}\`${how}.`, "");
278
321
  else lines.push("It extends no preset yet: the frame is the engine's defaults plus what", "the file sets.", "");
279
322
  lines.push(
@@ -322,8 +365,16 @@ export function create(options: CreateOptions): Created {
322
365
  throw new Error(`${dir} is not empty; pick an empty directory, or pass --force to write the template over it`);
323
366
  }
324
367
  }
368
+ if (options.profile !== undefined && options.archetype !== undefined) {
369
+ throw new Error(`--profile and --archetype together: an archetype has its own profile (${options.archetype}); drop one`);
370
+ }
325
371
  const choice = chooseStyle(options);
326
- const template = options.template ?? templateDir(options.archetype);
372
+ const asked = options.profile ?? (options.premise !== undefined ? premiseProfile(options.premise) : null);
373
+ const template = options.template ?? templateDir(options.archetype, asked ?? "3d");
374
+ const profile: CreatedProfile = {
375
+ value: profileOf(template),
376
+ chosen: options.archetype !== undefined ? "archetype" : options.profile !== undefined ? "flag" : asked !== null ? "premise" : "template",
377
+ };
327
378
  const list = files(template);
328
379
  const written: string[] = [];
329
380
  mkdirSync(dir, { recursive: true });
@@ -342,7 +393,7 @@ export function create(options: CreateOptions): Created {
342
393
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
343
394
  }
344
395
  const { look, notes } = writeLook(dir, name, id, choice, written);
345
- return { dir, name, id, files: written, look, notes };
396
+ return { dir, name, id, files: written, profile, look, notes };
346
397
  }
347
398
 
348
399
  /**
@@ -427,12 +478,13 @@ function writeLook(dir: string, name: string, id: string, choice: ReturnType<typ
427
478
  return { look, notes };
428
479
  }
429
480
 
430
- const USAGE = `usage: create [dir] [--name "Name"] [--id slug] [--archetype name] [--premise "one line"] [--look slug] [--force]
481
+ const USAGE = `usage: create [dir] [--name "Name"] [--id slug] [--profile 2d|3d] [--archetype name] [--premise "one line"] [--look slug] [--force]
431
482
 
432
483
  Scaffolds an Origin Engine game into dir (default: ./origin-game).
433
484
  --name the game's display name (default: from the id or the directory)
434
485
  --id the game's id, lowercase-hyphen (default: from the name)
435
- --archetype start from a complete archetype game (runner, match3, topdown, tower-defense, roguelike, idle, rhythm, third-person) instead of the template
486
+ --profile 2d writes the 2D starter (a platformer: sprites, tilemap, 2D physics), 3d the 3D template (default: 2d for a premise that says 2D, platformer, side-scroller, metroidvania or pixel art without saying 3D; else 3d)
487
+ --archetype start from a complete archetype game (runner, match3, topdown, tower-defense, roguelike, idle, rhythm, third-person, arena) instead of the template; it has its own profile
436
488
  --premise the game in one line (genre, mood, setting, audience): its look extends the style catalogue's best match (asked for on a terminal when neither this nor --look is given)
437
489
  --look the style catalogue slug the look extends (og-taste §12), such as anime-cel-3d or 16bit-pixel
438
490
  --no-prompt never ask for the premise
@@ -444,6 +496,7 @@ function main(argv: string[]): number {
444
496
  let id: string | undefined;
445
497
  let force = false;
446
498
  let archetype: string | undefined;
499
+ let profile: Profile | undefined;
447
500
  let premise: string | undefined;
448
501
  let look: string | undefined;
449
502
  let ask = true;
@@ -458,7 +511,11 @@ function main(argv: string[]): number {
458
511
  else if (a === "--id") id = value();
459
512
  else if (a === "--force") force = true;
460
513
  else if (a === "--archetype") archetype = value();
461
- else if (a === "--premise") premise = value();
514
+ else if (a === "--profile") {
515
+ const v = value();
516
+ if (v !== "2d" && v !== "3d") throw new Error(`--profile ${v}: use 2d or 3d. ${USAGE}`);
517
+ profile = v;
518
+ } else if (a === "--premise") premise = value();
462
519
  else if (a === "--look") look = value();
463
520
  else if (a === "--no-prompt") ask = false;
464
521
  else if (a === "-h" || a === "--help") {
@@ -476,13 +533,16 @@ function main(argv: string[]): number {
476
533
  ...(name ? { name } : {}),
477
534
  ...(id ? { id } : {}),
478
535
  ...(archetype ? { archetype } : {}),
536
+ ...(profile ? { profile } : {}),
479
537
  ...(premise ? { premise } : {}),
480
538
  ...(look ? { look } : {}),
481
539
  force,
482
540
  });
483
541
  const shown = relative(process.cwd(), made.dir) || ".";
484
542
  const style = made.look.extends ? `extends ${made.look.extends}${made.look.chosen === "premise" ? " (from the premise)" : made.look.chosen === "flag" ? " (--look)" : ""}` : "extends no preset";
543
+ const how = { flag: " (--profile)", premise: " (from the premise)", archetype: ` (the ${archetype} archetype's)`, template: "" }[made.profile.chosen];
485
544
  console.log(`Created ${made.name} (${made.id}) in ${shown}: ${made.files.length} files.
545
+ Profile: ${made.profile.value}${how}.
486
546
  Look: ${made.look.file}, ${style}; README.md says how to change it.${made.notes.map((n) => `\nNote: ${n}`).join("")}
487
547
 
488
548
  Next:
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "about": "Origin Engine's style catalogue: 37 art styles in the engine's own words, each with what a critic checks on a rendered frame, the premises it fits, the look preset it maps to (an .ogt of type look; `extends \"<slug>\"` in a game's own look builds on it), the UI skin pack, palette and asset specs, and an art-bible direction the agent expands per project. `look.support` says how faithfully the engine renders the style today: full (every primitive draws), partial or planned (the primitives in `missing` are still to land), content (the look lives in authored art; the engine frames it). og-taste §12 explains how to use it. The descriptions are original; no game or studio is the reference for a style.",
3
+ "about": "Origin Engine's style catalogue: 37 art styles in the engine's own words, each with what a critic checks on a rendered frame, the premises it fits, the look preset it maps to (an .ogt of type look; `extends \"<slug>\"` in a game's own look builds on it), the built-in UI style its look's `ui` line names, palette and asset specs, and an art-bible direction the agent expands per project. `look.support` says how faithfully the engine renders the style today: full (every primitive draws), partial or planned (the primitives in `missing` are still to land), content (the look lives in authored art; the engine frames it). og-taste §12 explains how to use it. The descriptions are original; no game or studio is the reference for a style.",
4
4
  "categories": {
5
5
  "pixel": "Pixel and retro",
6
6
  "paint": "Paint and line",
7
7
  "shape": "Shape and material",
8
8
  "render": "3D rendering"
9
9
  },
10
- "skins": [
10
+ "ui": [
11
11
  "pixel",
12
12
  "cartoon",
13
13
  "fantasy",
@@ -15,7 +15,7 @@
15
15
  "paper",
16
16
  "neon"
17
17
  ],
18
- "artBibleTemplate": "Art bible for {title}. Premise: {premise}. Camera: {camera}; platform and display size: {platform}. Style: {name} ({descriptor}) Direction: {direction} Palette: {palette}. Asset specs: {assets}. UI: the {skin} skin pack, re-coloured to this palette (og-taste §1). Avoid: {avoid}. Proof: the game's look is assets/looks/{game}.ogt, extending \"{slug}\"; capture the title, play and results screens with og run and check each against: {checks}. Every later asset is made inside this bible; a request for one asset never redesigns the style.",
18
+ "artBibleTemplate": "Art bible for {title}. Premise: {premise}. Camera: {camera}; platform and display size: {platform}. Style: {name} ({descriptor}) Direction: {direction} Palette: {palette}. Asset specs: {assets}. UI: the {ui} style the preset names (a `ui` line in the game's look picks another, or the game's own styles/<name>/style.ogt), re-coloured to this palette (og-taste §1). Avoid: {avoid}. Proof: the game's look is assets/looks/{game}.ogt, extending \"{slug}\"; capture the title, play and results screens with og run and check each against: {checks}. Every later asset is made inside this bible; a request for one asset never redesigns the style.",
19
19
  "styles": [
20
20
  {
21
21
  "slug": "expressionist-painting",
@@ -47,7 +47,7 @@
47
47
  "Stylise.paint* for 3D parts to match the painted art"
48
48
  ]
49
49
  },
50
- "skin": "paper",
50
+ "ui": "paper",
51
51
  "palette": {
52
52
  "size": "free, but 3 to 5 dominant masses per scene",
53
53
  "rules": "Complementary clashes in shadows; one dominant temperature per scene; avoid neutral greys."
@@ -96,7 +96,7 @@
96
96
  ],
97
97
  "missing": []
98
98
  },
99
- "skin": "fantasy",
99
+ "ui": "fantasy",
100
100
  "palette": {
101
101
  "size": "20 to 40 colours",
102
102
  "rules": "Deep warm or cool shadow colour shared by all materials; accents in one hue family per faction."
@@ -145,7 +145,7 @@
145
145
  ],
146
146
  "missing": []
147
147
  },
148
- "skin": "paper",
148
+ "ui": "paper",
149
149
  "palette": {
150
150
  "size": "12 to 24 colours",
151
151
  "rules": "Light values dominate; two or three pigments mixed into the rest; no pure black."
@@ -196,7 +196,7 @@
196
196
  ],
197
197
  "missing": []
198
198
  },
199
- "skin": "fantasy",
199
+ "ui": "fantasy",
200
200
  "palette": {
201
201
  "size": "sprites 32 to 64 colours; the set is unconstrained but graded to match",
202
202
  "rules": "Grade the 3D set with a LUT towards the sprites' palette so the two read as one world."
@@ -244,7 +244,7 @@
244
244
  ],
245
245
  "missing": []
246
246
  },
247
- "skin": "paper",
247
+ "ui": "paper",
248
248
  "palette": {
249
249
  "size": "16 to 32 colours",
250
250
  "rules": "Colour per material, gradients only from lighting and fog."
@@ -295,7 +295,7 @@
295
295
  ],
296
296
  "missing": []
297
297
  },
298
- "skin": "cartoon",
298
+ "ui": "cartoon",
299
299
  "palette": {
300
300
  "size": "warm, slightly desaturated plasticine colours",
301
301
  "rules": "Matte surfaces; saturation from the clay, not the light."
@@ -344,7 +344,7 @@
344
344
  ],
345
345
  "missing": []
346
346
  },
347
- "skin": "pixel",
347
+ "ui": "pixel",
348
348
  "palette": {
349
349
  "size": "8 to 16 colours",
350
350
  "rules": "Ramps of 3 or 4 steps per material; one shared black; no gradients; background colours darker or desaturated against sprites."
@@ -393,7 +393,7 @@
393
393
  ],
394
394
  "missing": []
395
395
  },
396
- "skin": "pixel",
396
+ "ui": "pixel",
397
397
  "palette": {
398
398
  "size": "32 to 64 colours (a voxel palette)",
399
399
  "rules": "One palette for the whole world; shade by light, not by palette steps."
@@ -444,7 +444,7 @@
444
444
  "Stylise.paintCanvas for the vellum grain"
445
445
  ]
446
446
  },
447
- "skin": "paper",
447
+ "ui": "paper",
448
448
  "palette": {
449
449
  "size": "10 to 16 colours",
450
450
  "rules": "Parchment ground, iron-gall ink, a few mineral pigments (ultramarine, vermilion, verdigris) and gold."
@@ -495,7 +495,7 @@
495
495
  "the look is in frame-by-frame animation (flipbooks); the engine supplies the film treatment"
496
496
  ]
497
497
  },
498
- "skin": "cartoon",
498
+ "ui": "cartoon",
499
499
  "palette": {
500
500
  "size": "sepia or muted primaries, 12 to 24 colours",
501
501
  "rules": "Inked black lines, flat fills, a warm faded grade over everything."
@@ -544,7 +544,7 @@
544
544
  ],
545
545
  "missing": []
546
546
  },
547
- "skin": "paper",
547
+ "ui": "paper",
548
548
  "palette": {
549
549
  "size": "graphite or sepia ramp plus paper, optional single accent",
550
550
  "rules": "Value from line density only; the accent marks danger or the goal."
@@ -596,7 +596,7 @@
596
596
  "ornament is content; Stylise.depthBands for layered haze"
597
597
  ]
598
598
  },
599
- "skin": "fantasy",
599
+ "ui": "fantasy",
600
600
  "palette": {
601
601
  "size": "cool desaturated base with 1 or 2 glowing accents",
602
602
  "rules": "Dark blue-grey grounds, pale characters, glows in one hue."
@@ -647,7 +647,7 @@
647
647
  "painted backdrops are content; Stylise.paint* for 3D props"
648
648
  ]
649
649
  },
650
- "skin": "fantasy",
650
+ "ui": "fantasy",
651
651
  "palette": {
652
652
  "size": "free, organised by warm light and cool shadow",
653
653
  "rules": "Saturation highest at the light's edge; value grouping in three planes."
@@ -697,7 +697,7 @@
697
697
  "illustration is content; Stylise.ink and paintCanvas"
698
698
  ]
699
699
  },
700
- "skin": "cartoon",
700
+ "ui": "cartoon",
701
701
  "palette": {
702
702
  "size": "16 to 32 colours",
703
703
  "rules": "Soft saturated hues, coloured line work, patterns from the same palette."
@@ -747,7 +747,7 @@
747
747
  "the source paintings and cut-outs are content (public-domain scans or original painting); no engine primitive is missing"
748
748
  ]
749
749
  },
750
- "skin": "paper",
750
+ "ui": "paper",
751
751
  "palette": {
752
752
  "size": "the source paintings' own",
753
753
  "rules": "Grade all sources to a common varnish tone with a LUT."
@@ -797,7 +797,7 @@
797
797
  "the drawing is content; Stylise.ink can outline 3D props to match"
798
798
  ]
799
799
  },
800
- "skin": "paper",
800
+ "ui": "paper",
801
801
  "palette": {
802
802
  "size": "monochrome ink or 4 to 8 light colours",
803
803
  "rules": "White or off-white ground; colour never hides line."
@@ -847,7 +847,7 @@
847
847
  "silhouettes are content; Stylise.palette locks the colours"
848
848
  ]
849
849
  },
850
- "skin": "fantasy",
850
+ "ui": "fantasy",
851
851
  "palette": {
852
852
  "size": "2 to 4 colours",
853
853
  "rules": "Ochre ground, black figures, a lighter line colour, optional red-brown."
@@ -896,7 +896,7 @@
896
896
  ],
897
897
  "missing": []
898
898
  },
899
- "skin": "fantasy",
899
+ "ui": "fantasy",
900
900
  "palette": {
901
901
  "size": "muted 12 to 24 colours plus black",
902
902
  "rules": "Desaturated mid-tones, black for every shadow, one blood or fire accent."
@@ -945,7 +945,7 @@
945
945
  ],
946
946
  "missing": []
947
947
  },
948
- "skin": "pixel",
948
+ "ui": "pixel",
949
949
  "palette": {
950
950
  "size": "2 colours plus at most 1 accent",
951
951
  "rules": "Pick ink and paper first; shading only through dither or pattern; the accent means one thing."
@@ -994,7 +994,7 @@
994
994
  ],
995
995
  "missing": []
996
996
  },
997
- "skin": "pixel",
997
+ "ui": "pixel",
998
998
  "palette": {
999
999
  "size": "32 to 64 colours",
1000
1000
  "rules": "Ramps of 4 to 6 steps; hue-shifted shadows (cooler) and highlights (warmer); saturated accents on characters, calmer backgrounds."
@@ -1044,7 +1044,7 @@
1044
1044
  ],
1045
1045
  "missing": []
1046
1046
  },
1047
- "skin": "fantasy",
1047
+ "ui": "fantasy",
1048
1048
  "palette": {
1049
1049
  "size": "48 to 128 colours",
1050
1050
  "rules": "Long hue-shifted ramps; a value plan per scene that keeps the play area mid-value and the foreground darkest."
@@ -1094,7 +1094,7 @@
1094
1094
  ],
1095
1095
  "missing": []
1096
1096
  },
1097
- "skin": "neon",
1097
+ "ui": "neon",
1098
1098
  "palette": {
1099
1099
  "size": "24 to 48 colours",
1100
1100
  "rules": "A near-black ground with two or three saturated light hues; no mid-grey fills; accents reserved for interactables."
@@ -1143,7 +1143,7 @@
1143
1143
  ],
1144
1144
  "missing": []
1145
1145
  },
1146
- "skin": "fantasy",
1146
+ "ui": "fantasy",
1147
1147
  "palette": {
1148
1148
  "size": "32 to 64 colours",
1149
1149
  "rules": "Floors desaturated and mid-value; walls darker on their faces; characters most saturated."
@@ -1194,7 +1194,7 @@
1194
1194
  ],
1195
1195
  "missing": []
1196
1196
  },
1197
- "skin": "scifi",
1197
+ "ui": "scifi",
1198
1198
  "palette": {
1199
1199
  "size": "free, but posterised to 32 levels per channel",
1200
1200
  "rules": "Fog colour equals the clear colour; dithered gradients."
@@ -1242,7 +1242,7 @@
1242
1242
  ],
1243
1243
  "missing": []
1244
1244
  },
1245
- "skin": "pixel",
1245
+ "ui": "pixel",
1246
1246
  "palette": {
1247
1247
  "size": "6 to 16 colours",
1248
1248
  "rules": "Each scene has one dominant mass colour; detail colour only where the eye should go."
@@ -1293,7 +1293,7 @@
1293
1293
  ],
1294
1294
  "missing": []
1295
1295
  },
1296
- "skin": "neon",
1296
+ "ui": "neon",
1297
1297
  "palette": {
1298
1298
  "size": "24 to 48 colours",
1299
1299
  "rules": "Two tones per material (lit, shadow) with hue-shifted shadows; highlights as shapes."
@@ -1344,7 +1344,7 @@
1344
1344
  ],
1345
1345
  "missing": []
1346
1346
  },
1347
- "skin": "scifi",
1347
+ "ui": "scifi",
1348
1348
  "palette": {
1349
1349
  "size": "free, saturated",
1350
1350
  "rules": "Bold local colours, black ink for all shadow edges."
@@ -1394,7 +1394,7 @@
1394
1394
  ],
1395
1395
  "missing": []
1396
1396
  },
1397
- "skin": "paper",
1397
+ "ui": "paper",
1398
1398
  "palette": {
1399
1399
  "size": "8 to 16 colours",
1400
1400
  "rules": "Colours of real paper stock: muted, slightly warm; white is off-white."
@@ -1444,7 +1444,7 @@
1444
1444
  ],
1445
1445
  "missing": []
1446
1446
  },
1447
- "skin": "cartoon",
1447
+ "ui": "cartoon",
1448
1448
  "palette": {
1449
1449
  "size": "soft pastel 24 to 48 colours",
1450
1450
  "rules": "Bright but slightly desaturated; warm sunlight, cool shadows."
@@ -1494,7 +1494,7 @@
1494
1494
  ],
1495
1495
  "missing": []
1496
1496
  },
1497
- "skin": "fantasy",
1497
+ "ui": "fantasy",
1498
1498
  "palette": {
1499
1499
  "size": "free, harmonised by a LUT",
1500
1500
  "rules": "Two or three dominant hues per area; graded towards them."
@@ -1546,7 +1546,7 @@
1546
1546
  ],
1547
1547
  "missing": []
1548
1548
  },
1549
- "skin": "scifi",
1549
+ "ui": "scifi",
1550
1550
  "palette": {
1551
1551
  "size": "free, graded",
1552
1552
  "rules": "One grade per area; avoid oversaturation; skin tones protected."
@@ -1597,7 +1597,7 @@
1597
1597
  ],
1598
1598
  "missing": []
1599
1599
  },
1600
- "skin": "scifi",
1600
+ "ui": "scifi",
1601
1601
  "palette": {
1602
1602
  "size": "greys and one accent",
1603
1603
  "rules": "Desaturated everything; the accent is red or warm light only where the story points."
@@ -1646,7 +1646,7 @@
1646
1646
  ],
1647
1647
  "missing": []
1648
1648
  },
1649
- "skin": "neon",
1649
+ "ui": "neon",
1650
1650
  "palette": {
1651
1651
  "size": "1 hue in 4 to 6 values, or black plus a gradient sky",
1652
1652
  "rules": "Value steps per layer; colour only in the far layers or a single accent."
@@ -1695,7 +1695,7 @@
1695
1695
  ],
1696
1696
  "missing": []
1697
1697
  },
1698
- "skin": "neon",
1698
+ "ui": "neon",
1699
1699
  "palette": {
1700
1700
  "size": "3 to 6 colours",
1701
1701
  "rules": "One ground, one figure colour, one accent; contrast by hue and value."
@@ -1746,7 +1746,7 @@
1746
1746
  ],
1747
1747
  "missing": []
1748
1748
  },
1749
- "skin": "neon",
1749
+ "ui": "neon",
1750
1750
  "palette": {
1751
1751
  "size": "black plus 2 to 4 saturated hues",
1752
1752
  "rules": "Each hue one meaning (player, enemy, pickup); white only for the hottest centres."
@@ -1795,7 +1795,7 @@
1795
1795
  ],
1796
1796
  "missing": []
1797
1797
  },
1798
- "skin": "pixel",
1798
+ "ui": "pixel",
1799
1799
  "palette": {
1800
1800
  "size": "2 to 4 colours",
1801
1801
  "rules": "Ink and paper, optionally one mid-tone and one accent."
@@ -1846,7 +1846,7 @@
1846
1846
  ],
1847
1847
  "missing": []
1848
1848
  },
1849
- "skin": "fantasy",
1849
+ "ui": "fantasy",
1850
1850
  "palette": {
1851
1851
  "size": "dark cool masses plus warm luminous accents",
1852
1852
  "rules": "Value structure first: dark foreground, mid play plane, glowing background."
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@origingame/create-game",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Scaffolds an Origin Engine game from the project template: npm create @origingame/game.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git+https://github.com/fran0220/origin-engine.git",
8
+ "url": "git+https://github.com/originai-game/origin-engine.git",
9
9
  "directory": "packages/create"
10
10
  },
11
- "homepage": "https://github.com/fran0220/origin-engine#readme",
11
+ "homepage": "https://github.com/originai-game/origin-engine#readme",
12
12
  "bugs": {
13
- "url": "https://github.com/fran0220/origin-engine/issues"
13
+ "url": "https://github.com/originai-game/origin-engine/issues"
14
14
  },
15
15
  "publishConfig": {
16
16
  "access": "public"
@@ -22,6 +22,7 @@
22
22
  "files": [
23
23
  "bin",
24
24
  "template",
25
+ "template-2d",
25
26
  "archetypes",
26
27
  "looks",
27
28
  "README.md"
@@ -8,7 +8,7 @@ through the wall, raises the alarm, chases, searches and goes back.
8
8
 
9
9
  ```sh
10
10
  og run . # headless with the probe; og dev . reloads on save
11
- og test . # tests/smoke.og.ts and tests/guard.og.ts
11
+ og test . # tests/smoke.og.ts, tests/guard.og.ts and tests/intro.og.ts
12
12
  og polish . # the taste and feel scores of those playtests
13
13
  og build . # the play page
14
14
  ```
@@ -25,6 +25,7 @@ og build . # the play page
25
25
  | Every word the menu shows from a string table, the base one in the source language | `assets/strings/en.ogt`, `Menu` | og-i18n §1, §2 |
26
26
  | A guard as data: a state machine on og.ai, a navmesh cooked from the yard, sight cones against the wall | `assets/ai/guard.ogt`, `assets/nav/yard.ogt` | og-ai §1–§5 |
27
27
  | Commands with their usage in `help`, facts for playtests | `commands`, `commandHelp`, `facts` | og-game §3; og-build §1 |
28
+ | A cutscene as data: a camera on a rail, the trickster's clips and walk, narration from an `.srt`, sounds, an event key running `spawn_wave`, fades and markers, played by `timeline:play:intro` | `assets/timelines/intro.ogt`, `narration.srt`, `tests/intro.og.ts` | og-game §9 "Cutscenes" |
28
29
  | Playtests that assert on dumps and facts, never on the code | `tests/` | og-build §3, §4 |
29
30
 
30
31
  `og polish .` (2026-10-19): taste 100, feel 87 (tests/evidence/E7/polish/
@@ -0,0 +1,10 @@
1
+ type sfx
2
+ version 1
3
+ ---
4
+ # The intro's music (timelines/intro.ogt's music track): a level-up
5
+ # jingle looped under the drop, faded in and out by the timeline.
6
+ preset "levelup"
7
+ seed 0
8
+ pitch 1
9
+ length 1
10
+ volume 1