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.
- package/bin/create-vimp-game.js +15 -0
- package/package.json +40 -0
- package/src/cli.js +227 -0
- package/src/generator.js +196 -0
- package/src/preflight.js +48 -0
- package/src/prompts.js +79 -0
- package/src/tokens.js +99 -0
- package/src/ui.js +66 -0
- package/src/versions.generated.json +4 -0
- package/src/versions.js +87 -0
- package/templates/default/CLAUDE.md.tpl +51 -0
- package/templates/default/Cargo.toml.tpl +27 -0
- package/templates/default/LICENSE.tpl +21 -0
- package/templates/default/README.md.tpl +61 -0
- package/templates/default/_gitignore +8 -0
- package/templates/default/assets/audio-raw/death.wav +0 -0
- package/templates/default/assets/audio-raw/shot.wav +0 -0
- package/templates/default/assets/sounds/death.mp3 +0 -0
- package/templates/default/assets/sounds/death.webm +0 -0
- package/templates/default/assets/sounds/shot.mp3 +0 -0
- package/templates/default/assets/sounds/shot.webm +0 -0
- package/templates/default/core/Cargo.toml.tpl +19 -0
- package/templates/default/core/src/actor.rs +407 -0
- package/templates/default/core/src/body_tag.rs +56 -0
- package/templates/default/core/src/client/mod.rs +385 -0
- package/templates/default/core/src/client/predictor.rs +552 -0
- package/templates/default/core/src/config.rs +236 -0
- package/templates/default/core/src/game.rs +692 -0
- package/templates/default/core/src/lib.rs +118 -0
- package/templates/default/core/src/motion.rs +126 -0
- package/templates/default/core/tests/sim.rs +351 -0
- package/templates/default/dev/main.js +28 -0
- package/templates/default/eslint.config.js +84 -0
- package/templates/default/index.html.tpl +35 -0
- package/templates/default/package.json.tpl +48 -0
- package/templates/default/scripts/build-game-manifest.js +188 -0
- package/templates/default/scripts/copy-game-images.js +35 -0
- package/templates/default/scripts/copy-game-sounds.js +55 -0
- package/templates/default/scripts/export-maps.js +19 -0
- package/templates/default/scripts/lib/rangeToPattern.js +74 -0
- package/templates/default/scripts/process-audio.js +115 -0
- package/templates/default/src/client/bakers/actorTexture.js +40 -0
- package/templates/default/src/client/bakers/index.js +8 -0
- package/templates/default/src/client/index.js +67 -0
- package/templates/default/src/client/parts/Actor.js +69 -0
- package/templates/default/src/client/parts/Map.js +74 -0
- package/templates/default/src/client/parts/ShotEffect.js +107 -0
- package/templates/default/src/client/parts/index.js +13 -0
- package/templates/default/src/client/style.css +26 -0
- package/templates/default/src/config/auth.js +61 -0
- package/templates/default/src/config/client.js +195 -0
- package/templates/default/src/config/game.js +170 -0
- package/templates/default/src/config/snapshot.js +70 -0
- package/templates/default/src/config/sounds.js +17 -0
- package/templates/default/src/data/maps/arena.js +69 -0
- package/templates/default/src/data/maps/index.js +7 -0
- package/templates/default/src/data/models.js +39 -0
- package/templates/default/src/data/weapons.js +37 -0
- package/templates/default/src/host/ScriptedManager.js +142 -0
- package/templates/default/src/host/createModules.js +12 -0
- package/templates/default/src/host/index.js +56 -0
- package/templates/default/src/host/nodeCore.js +23 -0
- package/templates/default/src/host/spawnCommand.js +32 -0
- package/templates/default/src/host/systemMessages.js +10 -0
- package/templates/default/tests/client/parts.test.js +128 -0
- package/templates/default/tests/config/contract.test.js +132 -0
- package/templates/default/tests/config/game.test.js +45 -0
- package/templates/default/tests/core/nodeCore.test.js +61 -0
- package/templates/default/tests/host/hostPlugin.test.js +198 -0
- package/templates/default/tests/stubs/wasmCore.js +19 -0
- package/templates/default/vite.config.js +74 -0
- 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
|
+
}
|
package/src/versions.js
ADDED
|
@@ -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/`.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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 }
|