create-vimp-game 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.
Files changed (72) hide show
  1. package/bin/create-vimp-game.js +15 -0
  2. package/package.json +40 -0
  3. package/src/cli.js +227 -0
  4. package/src/generator.js +196 -0
  5. package/src/preflight.js +48 -0
  6. package/src/prompts.js +79 -0
  7. package/src/tokens.js +99 -0
  8. package/src/ui.js +66 -0
  9. package/src/versions.generated.json +4 -0
  10. package/src/versions.js +87 -0
  11. package/templates/default/CLAUDE.md.tpl +51 -0
  12. package/templates/default/Cargo.toml.tpl +27 -0
  13. package/templates/default/LICENSE.tpl +21 -0
  14. package/templates/default/README.md.tpl +61 -0
  15. package/templates/default/_gitignore +8 -0
  16. package/templates/default/assets/audio-raw/death.wav +0 -0
  17. package/templates/default/assets/audio-raw/shot.wav +0 -0
  18. package/templates/default/assets/sounds/death.mp3 +0 -0
  19. package/templates/default/assets/sounds/death.webm +0 -0
  20. package/templates/default/assets/sounds/shot.mp3 +0 -0
  21. package/templates/default/assets/sounds/shot.webm +0 -0
  22. package/templates/default/core/Cargo.toml.tpl +19 -0
  23. package/templates/default/core/src/actor.rs +407 -0
  24. package/templates/default/core/src/body_tag.rs +56 -0
  25. package/templates/default/core/src/client/mod.rs +385 -0
  26. package/templates/default/core/src/client/predictor.rs +552 -0
  27. package/templates/default/core/src/config.rs +236 -0
  28. package/templates/default/core/src/game.rs +692 -0
  29. package/templates/default/core/src/lib.rs +118 -0
  30. package/templates/default/core/src/motion.rs +126 -0
  31. package/templates/default/core/tests/sim.rs +351 -0
  32. package/templates/default/dev/main.js +28 -0
  33. package/templates/default/eslint.config.js +84 -0
  34. package/templates/default/index.html.tpl +35 -0
  35. package/templates/default/package.json.tpl +48 -0
  36. package/templates/default/scripts/build-game-manifest.js +188 -0
  37. package/templates/default/scripts/copy-game-images.js +35 -0
  38. package/templates/default/scripts/copy-game-sounds.js +55 -0
  39. package/templates/default/scripts/export-maps.js +19 -0
  40. package/templates/default/scripts/lib/rangeToPattern.js +74 -0
  41. package/templates/default/scripts/process-audio.js +115 -0
  42. package/templates/default/src/client/bakers/actorTexture.js +40 -0
  43. package/templates/default/src/client/bakers/index.js +8 -0
  44. package/templates/default/src/client/index.js +67 -0
  45. package/templates/default/src/client/parts/Actor.js +69 -0
  46. package/templates/default/src/client/parts/Map.js +74 -0
  47. package/templates/default/src/client/parts/ShotEffect.js +107 -0
  48. package/templates/default/src/client/parts/index.js +13 -0
  49. package/templates/default/src/client/style.css +26 -0
  50. package/templates/default/src/config/auth.js +61 -0
  51. package/templates/default/src/config/client.js +195 -0
  52. package/templates/default/src/config/game.js +170 -0
  53. package/templates/default/src/config/snapshot.js +70 -0
  54. package/templates/default/src/config/sounds.js +17 -0
  55. package/templates/default/src/data/maps/arena.js +69 -0
  56. package/templates/default/src/data/maps/index.js +7 -0
  57. package/templates/default/src/data/models.js +39 -0
  58. package/templates/default/src/data/weapons.js +37 -0
  59. package/templates/default/src/host/ScriptedManager.js +142 -0
  60. package/templates/default/src/host/createModules.js +12 -0
  61. package/templates/default/src/host/index.js +56 -0
  62. package/templates/default/src/host/nodeCore.js +23 -0
  63. package/templates/default/src/host/spawnCommand.js +32 -0
  64. package/templates/default/src/host/systemMessages.js +10 -0
  65. package/templates/default/tests/client/parts.test.js +128 -0
  66. package/templates/default/tests/config/contract.test.js +132 -0
  67. package/templates/default/tests/config/game.test.js +45 -0
  68. package/templates/default/tests/core/nodeCore.test.js +61 -0
  69. package/templates/default/tests/host/hostPlugin.test.js +198 -0
  70. package/templates/default/tests/stubs/wasmCore.js +19 -0
  71. package/templates/default/vite.config.js +74 -0
  72. package/templates/default/vitest.config.js +55 -0
package/src/ui.js ADDED
@@ -0,0 +1,66 @@
1
+ // Вывод CLI. Текст интерфейса английский (как у bin/vimp-sim.js): его
2
+ // читает автор игры, в том числе вне этого репозитория.
3
+
4
+ // цвета гасятся, когда вывод уходит не в терминал (пайп, CI) или задан
5
+ // NO_COLOR — иначе escape-последовательности попадают в лог
6
+ const enabled =
7
+ process.stdout.isTTY === true && process.env.NO_COLOR === undefined;
8
+
9
+ const CODES = {
10
+ bold: '1',
11
+ dim: '2',
12
+ red: '31',
13
+ green: '32',
14
+ yellow: '33',
15
+ cyan: '36',
16
+ };
17
+
18
+ // ESC собирается из кода: буквальный управляющий символ в исходнике
19
+ // невидим при чтении и теряется при копировании
20
+ const ESC = String.fromCharCode(27);
21
+
22
+ function paint(name, text) {
23
+ return enabled ? `${ESC}[${CODES[name]}m${text}${ESC}[0m` : String(text);
24
+ }
25
+
26
+ export const color = {
27
+ bold: text => paint('bold', text),
28
+ dim: text => paint('dim', text),
29
+ red: text => paint('red', text),
30
+ green: text => paint('green', text),
31
+ yellow: text => paint('yellow', text),
32
+ cyan: text => paint('cyan', text),
33
+ };
34
+
35
+ export function log(message = '') {
36
+ process.stdout.write(`${message}\n`);
37
+ }
38
+
39
+ export function warn(message) {
40
+ process.stderr.write(`${color.yellow('warning:')} ${message}\n`);
41
+ }
42
+
43
+ // финальный экран: команды, которые автор игры выполняет следующими.
44
+ // Порядок повторяет docs/ai/11-authoring-workflow.md: зависимости → ядро →
45
+ // проверка контракта → headless-прогон → браузер
46
+ export function nextSteps({ directory, tokens }) {
47
+ const lines = [
48
+ '',
49
+ `${color.green('Done.')} ${color.bold(tokens.GAME_TITLE)} scaffolded in ${color.cyan(directory)}`,
50
+ '',
51
+ 'Next steps:',
52
+ ` cd ${directory}`,
53
+ ' npm install',
54
+ ' npm run core:build # cargo + wasm-pack',
55
+ ' npm run check:contract # static plugin contract check',
56
+ ' npm run build',
57
+ ' npm run sim # headless match',
58
+ '',
59
+ color.dim(
60
+ 'Read CLAUDE.md in the generated project before writing gameplay.',
61
+ ),
62
+ '',
63
+ ];
64
+
65
+ return lines.join('\n');
66
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "engine": "0.10.0",
3
+ "core": "0.3.2"
4
+ }
@@ -0,0 +1,87 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ // Пины vimp-engine / vimp-engine-core для сгенерированной игры. В шаблоне
6
+ // они не хардкодятся: устаревший пин — тихая поломка, которая всплывает
7
+ // только на сборке ядра (vimp-street-fighters приехал на
8
+ // vimp-engine-core = "0.1.0" при актуальном 0.3.2).
9
+ //
10
+ // Два источника, в порядке приоритета:
11
+ // 1. запуск из монорепозитория движка — версии читаются из
12
+ // packages/engine/package.json и packages/engine/core/Cargo.toml;
13
+ // 2. установка из npm — src/versions.generated.json, который пишет
14
+ // scripts/write-versions.js хуком prepack.
15
+
16
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
17
+
18
+ export const GENERATED_FILE = path.join(moduleDir, 'versions.generated.json');
19
+
20
+ // packages/create-vimp-game/src → корень репозитория
21
+ export const REPO_ROOT = path.resolve(moduleDir, '..', '..', '..');
22
+
23
+ // версия крейта — первый `version = "…"` после [package]: в Cargo.toml
24
+ // ниже идут секции зависимостей со своими version.
25
+ //
26
+ // Копия живёт в движке (devtools/contract/loadContext.js): скаффолдер
27
+ // ставится через `npm create` и обязан работать без движка в зависимостях.
28
+ // Разбор обязан совпадать — правьте обе или ни одной.
29
+ export function parseCrateVersion(toml) {
30
+ const packageSection = toml
31
+ .split(/^\s*\[/m)
32
+ .find(part => part.startsWith('package]'));
33
+
34
+ if (packageSection === undefined) {
35
+ throw new Error('Cargo.toml has no [package] section');
36
+ }
37
+
38
+ const match = packageSection.match(/^\s*version\s*=\s*"([^"]+)"/m);
39
+
40
+ if (match === null) {
41
+ throw new Error('Cargo.toml [package] has no version');
42
+ }
43
+
44
+ return match[1];
45
+ }
46
+
47
+ export async function readRepoVersions(root = REPO_ROOT) {
48
+ const enginePackage = JSON.parse(
49
+ await readFile(
50
+ path.join(root, 'packages', 'engine', 'package.json'),
51
+ 'utf8',
52
+ ),
53
+ );
54
+ const cargo = await readFile(
55
+ path.join(root, 'packages', 'engine', 'core', 'Cargo.toml'),
56
+ 'utf8',
57
+ );
58
+
59
+ return { engine: enginePackage.version, core: parseCrateVersion(cargo) };
60
+ }
61
+
62
+ async function readGeneratedVersions(file = GENERATED_FILE) {
63
+ const raw = JSON.parse(await readFile(file, 'utf8'));
64
+
65
+ if (typeof raw.engine !== 'string' || typeof raw.core !== 'string') {
66
+ throw new Error(`${file} is malformed: expected { engine, core }`);
67
+ }
68
+
69
+ return { engine: raw.engine, core: raw.core };
70
+ }
71
+
72
+ // пины для buildTokens: движок — caret-диапазон (патчи подхватываются),
73
+ // крейт — точная версия (cargo сам трактует её как ^)
74
+ export function toPins({ engine, core }) {
75
+ return { engineVersion: `^${engine}`, coreVersion: core };
76
+ }
77
+
78
+ export async function resolveVersions({
79
+ root = REPO_ROOT,
80
+ generated = GENERATED_FILE,
81
+ } = {}) {
82
+ try {
83
+ return await readRepoVersions(root);
84
+ } catch {
85
+ return readGeneratedVersions(generated);
86
+ }
87
+ }
@@ -0,0 +1,51 @@
1
+ # CLAUDE.md
2
+
3
+ ## Overview
4
+
5
+ `{{PACKAGE_NAME}}` is a game plugin for the VIMP engine: the engine owns
6
+ networking, rounds, chat, panel and stat; this package owns the game. Only
7
+ `dist/` is published. The full contract lives in the engine repository under
8
+ `docs/ai/` (`03-host-plugin`, `04-client-plugin`, `05-wasm-core`,
9
+ `06-snapshot-protocol`, `10-pitfalls`) — read it there, it is not copied here.
10
+
11
+ ## Boundaries
12
+
13
+ - `src/host/` runs in a **Web Worker**: no DOM, no PixiJS, no `window`.
14
+ - `src/client/` runs in the **main thread**: PixiJS parts and bakers only.
15
+ - `core/` (Rust) owns **all** physics, movement and damage. The engine does
16
+ none of it: `core/src/game.rs` and `core/src/motion.rs` are the simulation,
17
+ and `motion.rs` is shared by the host step and the client predictor — moving
18
+ logic out of it desynchronises prediction from the server.
19
+
20
+ ## Contract constants
21
+
22
+ - `ENGINE_API_VERSION` is always imported from
23
+ `vimp-engine/config/opcodes.js`, never written as a literal.
24
+ - `PLAYER_STATE_LEN = 8` — the prediction budget `[x, y, angle, vx, vy, hp,
25
+ ammo, 0]`; the JS and Rust sides must agree on it.
26
+ - Hot snapshot buffers are `indexed8` / `indexedNoNull8` only.
27
+ - `src/config/auth.js` must declare the `model` parameter — the engine expects
28
+ that exact name.
29
+
30
+ ## Order of work
31
+
32
+ 1. snapshot schema and `src/config/` — decide what travels the wire first;
33
+ 2. Rust simulation in `core/`, with `npm run core:test` green;
34
+ 3. `npm run check:contract` and `npm run sim` — the machine reads the
35
+ invariants the browser only hints at;
36
+ 4. rendering: `src/client/parts/` and bakers.
37
+
38
+ ## Commands
39
+
40
+ ```bash
41
+ npm run core:build # REQUIRED before npm run dev
42
+ npm run core:test
43
+ npm run check:contract
44
+ npm run sim
45
+ npm run build
46
+ npm test && npx eslint .
47
+ npm run dev
48
+ ```
49
+
50
+ Any functional change updates the tests covering it in the same change;
51
+ `npx eslint .` and `npm test` end every change green.
@@ -0,0 +1,27 @@
1
+ # Workspace root of the Rust core. The crate itself lives in core/.
2
+ [workspace]
3
+ members = ["core"]
4
+ resolver = "3"
5
+
6
+ [workspace.package]
7
+ version = "0.1.0"
8
+ edition = "2024"
9
+ license = "MIT"
10
+ authors = ["{{AUTHOR}}"]
11
+
12
+ [workspace.dependencies]
13
+ vimp-engine-core = "{{CORE_VERSION}}"
14
+ # insertion order of models/weapons/panel is a contract: the weapon index of
15
+ # the snapshot row is the position of the key in the config, so a HashMap
16
+ # would silently reshuffle it between runs
17
+ indexmap = { version = "2.14.0", features = ["serde"] }
18
+ # enhanced-determinism is mandatory: without it physics diverges between
19
+ # machines, and the host and the client predictor stop agreeing
20
+ rapier2d = { version = "0.34.0", features = ["enhanced-determinism", "serde-serialize"] }
21
+ serde = { version = "1.0.228", features = ["derive"] }
22
+ serde_json = "1.0.150"
23
+ wasm-bindgen = "0.2.126"
24
+
25
+ [profile.release]
26
+ opt-level = 3
27
+ lto = true
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) {{YEAR}} {{AUTHOR}}
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,61 @@
1
+ # {{GAME_TITLE}}
2
+
3
+ A VIMP game plugin (`{{PACKAGE_NAME}}`), scaffolded with `npm create vimp-game`.
4
+
5
+ ## Run it
6
+
7
+ ```bash
8
+ npm install
9
+ npm run core:build # REQUIRED before npm run dev
10
+ npm run dev
11
+ ```
12
+
13
+ `npm run core:build` is not optional and not a later step: the dev harness
14
+ (`dev/main.js`) imports the wasm out of `core/pkg-web/`, so until the Rust core
15
+ has been built once Vite fails to resolve the import and `npm run dev` dies on
16
+ startup. The same holds before the master is started against this package,
17
+ even in dev mode — build the core and then `npm run build` at least once.
18
+
19
+ ## Commands
20
+
21
+ ```bash
22
+ npm run core:build # wasm-pack -> core/pkg-web (runtime) + core/pkg-node (tests)
23
+ npm run core:test # cargo test --workspace
24
+ npm run build # dist/: both bundles, maps, sounds, manifest.json
25
+ npm run check:contract # static engine<->game contract check (vimp-contract)
26
+ npm test # vitest
27
+ npm run sim # headless match on the real core
28
+ npm run dev # a match against bots in the tab
29
+ npm run audio:process # ffmpeg: assets/audio-raw/ -> build/sounds/ (optional)
30
+ ```
31
+
32
+ ## Layout
33
+
34
+ | Path | What |
35
+ | --- | --- |
36
+ | `core/` | the Rust crate `{{CRATE_NAME}}` — physics, damage, movement |
37
+ | `src/host/` | HostPlugin: runs in a Web Worker, no DOM and no PixiJS |
38
+ | `src/client/` | ClientPlugin: render parts and bakers, main thread |
39
+ | `src/config/` | game, client, auth, snapshot and sound configuration |
40
+ | `src/data/` | maps and balance data |
41
+ | `scripts/` | build steps: bundles -> `dist/` + `manifest.json` |
42
+ | `dev/` | the standalone dev harness — never published |
43
+
44
+ Only `dist/` is published (`files: ["dist"]`).
45
+
46
+ ## Sounds without ffmpeg
47
+
48
+ The real pipeline is `assets/audio-raw/*.wav` → `npm run audio:process` →
49
+ `build/sounds/*.{webm,mp3}`. That needs ffmpeg, so the template also ships
50
+ ready-made placeholders in `assets/sounds/`: when `build/sounds/` is absent,
51
+ `scripts/copy-game-sounds.js` falls back to them and the first build stays
52
+ green on a bare machine. Process your own audio and the fallback stops being
53
+ used.
54
+
55
+ ## Engine
56
+
57
+ - `vimp-engine` `{{ENGINE_VERSION}}`
58
+ - `vimp-engine-core` `{{CORE_VERSION}}`
59
+
60
+ Game id: `{{GAME_ID}}`. The plugin contract lives in the engine repository
61
+ under `docs/ai/`.
@@ -0,0 +1,8 @@
1
+ node_modules/
2
+ dist/
3
+ build/
4
+ target/
5
+ core/pkg-web/
6
+ core/pkg-node/
7
+ .debug/
8
+ _*
@@ -0,0 +1,19 @@
1
+ [package]
2
+ name = "{{CRATE_NAME}}"
3
+ description = "{{GAME_TITLE}} — game simulation + wasm-bindgen ABI on top of vimp-engine-core."
4
+ version.workspace = true
5
+ edition.workspace = true
6
+ license.workspace = true
7
+ authors.workspace = true
8
+
9
+ [lib]
10
+ # cdylib — the .wasm itself, rlib — the same code for `cargo test`
11
+ crate-type = ["cdylib", "rlib"]
12
+
13
+ [dependencies]
14
+ vimp-engine-core = { workspace = true }
15
+ indexmap = { workspace = true }
16
+ rapier2d = { workspace = true }
17
+ serde = { workspace = true }
18
+ serde_json = { workspace = true }
19
+ wasm-bindgen = { workspace = true }