@auraindustry/aurajs 0.0.2 → 0.0.3
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/package.json +13 -1
- package/src/build-contract.mjs +708 -2
- package/src/cli.mjs +1584 -13
- package/src/conformance.mjs +1013 -86
- package/src/game-state-runtime.mjs +1067 -0
- package/src/headless-test.mjs +525 -10
- package/src/host-binary.mjs +33 -10
- package/src/react/aura-game.mjs +310 -0
- package/src/react/index.mjs +1 -0
- package/src/scaffold.mjs +267 -13
- package/src/web-api.mjs +454 -0
- package/templates/create/2d/aura.config.json +28 -0
- package/templates/create/2d/src/main.js +196 -0
- package/templates/create/3d/aura.config.json +28 -0
- package/templates/create/3d/src/main.js +306 -0
- package/templates/create/blank/aura.config.json +28 -0
- package/templates/create/blank/src/main.js +28 -0
- package/templates/create/shared/src/starter-utils/core.js +114 -0
- package/templates/create/shared/src/starter-utils/enemy-archetypes-2d.js +68 -0
- package/templates/create/shared/src/starter-utils/index.js +6 -0
- package/templates/create/shared/src/starter-utils/platformer-3d.js +101 -0
- package/templates/create/shared/src/starter-utils/wave-director.js +101 -0
- package/templates/skills/aurajs/SKILL.md +40 -0
- package/templates/skills/aurajs/api-contract-3d.md +7 -0
- package/templates/skills/aurajs/api-contract.md +7 -0
- package/templates/starter/src/main.js +48 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { createIntervalSpawner, tickIntervalSpawner } from './core.js';
|
|
2
|
+
|
|
3
|
+
export function createWaveDirector(options = {}) {
|
|
4
|
+
const waves = Array.isArray(options.waves) ? options.waves : [];
|
|
5
|
+
const loop = options.loop === true;
|
|
6
|
+
const initialDelay = Math.max(0, Number(options.initialDelay) || 0);
|
|
7
|
+
const betweenWaveDelay = Math.max(0, Number(options.betweenWaveDelay) || 0);
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
waves,
|
|
11
|
+
loop,
|
|
12
|
+
initialDelay,
|
|
13
|
+
betweenWaveDelay,
|
|
14
|
+
delayRemaining: initialDelay,
|
|
15
|
+
waveIndex: 0,
|
|
16
|
+
waveElapsed: 0,
|
|
17
|
+
waveSpawned: 0,
|
|
18
|
+
activeSpawner: null,
|
|
19
|
+
completed: waves.length === 0,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function activeWave(director) {
|
|
24
|
+
if (!director || director.completed) return null;
|
|
25
|
+
return director.waves[director.waveIndex] || null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function stepWaveDirector(director, dt, onSpawn = null) {
|
|
29
|
+
if (!director || director.completed) {
|
|
30
|
+
return { spawned: 0, waveIndex: -1, completed: true };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const frameDt = Math.max(0, Number(dt) || 0);
|
|
34
|
+
if (director.delayRemaining > 0) {
|
|
35
|
+
director.delayRemaining = Math.max(0, director.delayRemaining - frameDt);
|
|
36
|
+
return { spawned: 0, waveIndex: director.waveIndex, completed: director.completed };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const wave = activeWave(director);
|
|
40
|
+
if (!wave) {
|
|
41
|
+
director.completed = true;
|
|
42
|
+
return { spawned: 0, waveIndex: -1, completed: true };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!director.activeSpawner) {
|
|
46
|
+
const interval = Math.max(0.04, Number(wave.spawnEvery) || 0.8);
|
|
47
|
+
director.activeSpawner = createIntervalSpawner(interval, { startReady: true });
|
|
48
|
+
director.waveElapsed = 0;
|
|
49
|
+
director.waveSpawned = 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
director.waveElapsed += frameDt;
|
|
53
|
+
|
|
54
|
+
const maxSpawns = Number.isFinite(Number(wave.maxSpawns))
|
|
55
|
+
? Math.max(0, Math.floor(Number(wave.maxSpawns)))
|
|
56
|
+
: Number.POSITIVE_INFINITY;
|
|
57
|
+
const maxDuration = Number.isFinite(Number(wave.durationSeconds))
|
|
58
|
+
? Math.max(0, Number(wave.durationSeconds))
|
|
59
|
+
: Number.POSITIVE_INFINITY;
|
|
60
|
+
|
|
61
|
+
let spawned = 0;
|
|
62
|
+
tickIntervalSpawner(director.activeSpawner, frameDt, () => {
|
|
63
|
+
if (director.waveSpawned >= maxSpawns) return;
|
|
64
|
+
director.waveSpawned += 1;
|
|
65
|
+
spawned += 1;
|
|
66
|
+
if (typeof onSpawn === 'function') {
|
|
67
|
+
onSpawn({
|
|
68
|
+
waveIndex: director.waveIndex,
|
|
69
|
+
wave,
|
|
70
|
+
spawnedInWave: director.waveSpawned,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const waveDone = (
|
|
76
|
+
director.waveSpawned >= maxSpawns
|
|
77
|
+
|| director.waveElapsed >= maxDuration
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (waveDone) {
|
|
81
|
+
director.waveIndex += 1;
|
|
82
|
+
if (director.waveIndex >= director.waves.length) {
|
|
83
|
+
if (director.loop) {
|
|
84
|
+
director.waveIndex = 0;
|
|
85
|
+
} else {
|
|
86
|
+
director.completed = true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
director.delayRemaining = director.completed ? 0 : director.betweenWaveDelay;
|
|
91
|
+
director.waveElapsed = 0;
|
|
92
|
+
director.waveSpawned = 0;
|
|
93
|
+
director.activeSpawner = null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
spawned,
|
|
98
|
+
waveIndex: director.completed ? -1 : director.waveIndex,
|
|
99
|
+
completed: director.completed,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# AuraJS Skill
|
|
2
|
+
|
|
3
|
+
Use this skill when building or modifying AuraJS games.
|
|
4
|
+
|
|
5
|
+
## Read First
|
|
6
|
+
- `api-contract.md` (2D/core surface)
|
|
7
|
+
- `api-contract-3d.md` (3D surface)
|
|
8
|
+
|
|
9
|
+
Do not load these files end-to-end by default.
|
|
10
|
+
Use targeted reads: jump to the exact namespace/method you are touching, then apply that contract strictly.
|
|
11
|
+
|
|
12
|
+
## Contract Workflow
|
|
13
|
+
1. Identify the APIs your change touches.
|
|
14
|
+
2. Search the relevant contract file for those APIs.
|
|
15
|
+
3. Read only those sections and follow signature + validation + error semantics.
|
|
16
|
+
4. If examples conflict with contract docs, contract docs win.
|
|
17
|
+
|
|
18
|
+
## Build Loop
|
|
19
|
+
1. Implement the smallest playable mechanic in `src/main.js`.
|
|
20
|
+
2. Run `npm run dev` for fast iteration.
|
|
21
|
+
3. Validate release behavior with `npm run play`.
|
|
22
|
+
4. Publish with `npm run publish` when the package is ready.
|
|
23
|
+
|
|
24
|
+
## Engine Guardrails
|
|
25
|
+
- Put initialization in `aura.setup`.
|
|
26
|
+
- Put frame-step logic in `aura.update(dt)`.
|
|
27
|
+
- Put rendering in `aura.draw`.
|
|
28
|
+
- Keep rendering API calls in draw-only paths.
|
|
29
|
+
- Use `aura.window.getSize()` for runtime layout.
|
|
30
|
+
- Clamp player/world values to avoid drift.
|
|
31
|
+
|
|
32
|
+
## Vibe Coding Defaults
|
|
33
|
+
- Start with one loop and one strong interaction.
|
|
34
|
+
- Add juice after core feel works: camera, hit feedback, audio, score.
|
|
35
|
+
- Keep systems loosely coupled so AI agents can modify one mechanic at a time.
|
|
36
|
+
|
|
37
|
+
## Suggested Project Structure
|
|
38
|
+
- `src/main.js`: primary loop and gameplay systems.
|
|
39
|
+
- `assets/`: sprites, audio, fonts.
|
|
40
|
+
- `skills/`: agent workflows and contract references.
|
|
@@ -12,6 +12,53 @@ let movedOutsideBounds = false;
|
|
|
12
12
|
const PLAYER_SPRITE_PATH = "player.png";
|
|
13
13
|
const HUD_FONT_PATH = "fonts/ui.ttf";
|
|
14
14
|
|
|
15
|
+
function hasMethod(obj, method) {
|
|
16
|
+
return Boolean(obj) && typeof obj[method] === "function";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function failWithReason(reasonCode, message) {
|
|
20
|
+
throw new Error(`[starter-template] ${message} [reason:${reasonCode}]`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function assertRuntimeCapabilities() {
|
|
24
|
+
const missing = [];
|
|
25
|
+
if (!hasMethod(aura.input, "isKeyDown")) missing.push("aura.input.isKeyDown");
|
|
26
|
+
if (!hasMethod(aura.input, "isKeyPressed")) missing.push("aura.input.isKeyPressed");
|
|
27
|
+
if (!hasMethod(aura.input, "isKeyReleased")) missing.push("aura.input.isKeyReleased");
|
|
28
|
+
if (!hasMethod(aura.input, "isGamepadConnected")) missing.push("aura.input.isGamepadConnected");
|
|
29
|
+
if (!hasMethod(aura.window, "getSize")) missing.push("aura.window.getSize");
|
|
30
|
+
if (!hasMethod(aura.window, "getFPS")) missing.push("aura.window.getFPS");
|
|
31
|
+
if (!hasMethod(aura.draw2d, "clear")) missing.push("aura.draw2d.clear");
|
|
32
|
+
if (!hasMethod(aura.draw2d, "sprite")) missing.push("aura.draw2d.sprite");
|
|
33
|
+
if (!hasMethod(aura.draw2d, "pushTransform")) missing.push("aura.draw2d.pushTransform");
|
|
34
|
+
if (!hasMethod(aura.draw2d, "translate")) missing.push("aura.draw2d.translate");
|
|
35
|
+
if (!hasMethod(aura.draw2d, "rotate")) missing.push("aura.draw2d.rotate");
|
|
36
|
+
if (!hasMethod(aura.draw2d, "rect")) missing.push("aura.draw2d.rect");
|
|
37
|
+
if (!hasMethod(aura.draw2d, "popTransform")) missing.push("aura.draw2d.popTransform");
|
|
38
|
+
if (!hasMethod(aura.draw2d, "text")) missing.push("aura.draw2d.text");
|
|
39
|
+
if (!hasMethod(aura.draw2d, "measureText")) missing.push("aura.draw2d.measureText");
|
|
40
|
+
if (!hasMethod(aura.collision, "rectRect")) missing.push("aura.collision.rectRect");
|
|
41
|
+
if (!hasMethod(aura.assets, "exists")) missing.push("aura.assets.exists");
|
|
42
|
+
if (!hasMethod(aura.math, "clamp")) missing.push("aura.math.clamp");
|
|
43
|
+
if (typeof aura.rgb !== "function") missing.push("aura.rgb");
|
|
44
|
+
if (typeof aura.rgba !== "function") missing.push("aura.rgba");
|
|
45
|
+
if (!aura.Color || !aura.Color.WHITE) missing.push("aura.Color.WHITE");
|
|
46
|
+
if (!aura.Color || !aura.Color.YELLOW) missing.push("aura.Color.YELLOW");
|
|
47
|
+
|
|
48
|
+
if (missing.length > 0) {
|
|
49
|
+
failWithReason("missing_runtime_api", `runtime missing required APIs: ${missing.join(", ")}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const small = aura.draw2d.measureText("Probe", { size: 8 });
|
|
53
|
+
const large = aura.draw2d.measureText("Probe", { size: 24 });
|
|
54
|
+
if (!Number.isFinite(Number(small?.width)) || !Number.isFinite(Number(large?.width)) || Number(large.width) <= Number(small.width)) {
|
|
55
|
+
failWithReason(
|
|
56
|
+
"placeholder_runtime_behavior",
|
|
57
|
+
"draw2d.measureText appears to be placeholder behavior (size does not affect width).",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
15
62
|
function keyDown(name) {
|
|
16
63
|
if (aura.input && typeof aura.input.isKeyDown === "function") {
|
|
17
64
|
return aura.input.isKeyDown(name);
|
|
@@ -105,6 +152,7 @@ function clearScreen(color) {
|
|
|
105
152
|
}
|
|
106
153
|
|
|
107
154
|
aura.setup = function () {
|
|
155
|
+
assertRuntimeCapabilities();
|
|
108
156
|
console.log("{{PROJECT_NAME}} started");
|
|
109
157
|
if (!hasAsset(PLAYER_SPRITE_PATH)) {
|
|
110
158
|
console.log(`Tip: add assets/${PLAYER_SPRITE_PATH} to preview sprite draw2d options.`);
|