@netforge/engine 0.0.0-stage → 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/GAME-DESIGN.md +195 -0
- package/LICENSE +21 -0
- package/NOTICE +30 -0
- package/README.md +100 -2
- package/dist/api/appearance.d.ts +37 -0
- package/dist/api/appearance.js +41 -0
- package/dist/api/cards.d.ts +105 -0
- package/dist/api/cards.js +133 -0
- package/dist/api/collector.d.ts +114 -0
- package/dist/api/collector.js +204 -0
- package/dist/api/dungeon.d.ts +58 -0
- package/dist/api/dungeon.js +216 -0
- package/dist/api/harness.d.ts +149 -0
- package/dist/api/harness.js +549 -0
- package/dist/api/idle.d.ts +71 -0
- package/dist/api/idle.js +104 -0
- package/dist/api/race-harness.d.ts +3 -0
- package/dist/api/race-harness.js +148 -0
- package/dist/api/registry.d.ts +241 -0
- package/dist/api/registry.js +394 -0
- package/dist/api/scheduler.d.ts +56 -0
- package/dist/api/scheduler.js +160 -0
- package/dist/api/schemas.d.ts +958 -0
- package/dist/api/schemas.js +859 -0
- package/dist/api/tower.d.ts +127 -0
- package/dist/api/tower.js +183 -0
- package/dist/game/adapter.d.ts +69 -0
- package/dist/game/adapter.js +341 -0
- package/dist/game/arena-bot.d.ts +66 -0
- package/dist/game/arena-bot.js +188 -0
- package/dist/game/bot.d.ts +95 -0
- package/dist/game/bot.js +285 -0
- package/dist/game/compile.d.ts +97 -0
- package/dist/game/compile.js +476 -0
- package/dist/game/director.d.ts +42 -0
- package/dist/game/director.js +179 -0
- package/dist/game/earn-source.d.ts +122 -0
- package/dist/game/earn-source.js +386 -0
- package/dist/game/earn.d.ts +49 -0
- package/dist/game/earn.js +55 -0
- package/dist/game/index.d.ts +37 -0
- package/dist/game/index.js +25 -0
- package/dist/game/platformer.d.ts +156 -0
- package/dist/game/platformer.js +619 -0
- package/dist/game/profile.d.ts +97 -0
- package/dist/game/profile.js +140 -0
- package/dist/game/race.d.ts +177 -0
- package/dist/game/race.js +386 -0
- package/dist/game/racer.d.ts +98 -0
- package/dist/game/racer.js +379 -0
- package/dist/game/runner.d.ts +286 -0
- package/dist/game/runner.js +824 -0
- package/dist/game/threats.d.ts +23 -0
- package/dist/game/threats.js +49 -0
- package/dist/game/view.d.ts +47 -0
- package/dist/game/view.js +123 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +24 -0
- package/dist/net/bits.d.ts +33 -0
- package/dist/net/bits.js +135 -0
- package/dist/net/client.d.ts +75 -0
- package/dist/net/client.js +151 -0
- package/dist/net/index.d.ts +23 -0
- package/dist/net/index.js +23 -0
- package/dist/net/input.d.ts +49 -0
- package/dist/net/input.js +112 -0
- package/dist/net/interpolation.d.ts +37 -0
- package/dist/net/interpolation.js +97 -0
- package/dist/net/netsim.d.ts +22 -0
- package/dist/net/netsim.js +52 -0
- package/dist/net/prediction.d.ts +31 -0
- package/dist/net/prediction.js +101 -0
- package/dist/net/profile-client.d.ts +75 -0
- package/dist/net/profile-client.js +173 -0
- package/dist/net/protocol.d.ts +215 -0
- package/dist/net/protocol.js +10 -0
- package/dist/net/room-client.d.ts +65 -0
- package/dist/net/room-client.js +156 -0
- package/dist/net/schema.d.ts +78 -0
- package/dist/net/schema.js +121 -0
- package/dist/net/sim-adapter.d.ts +134 -0
- package/dist/net/sim-adapter.js +22 -0
- package/dist/net/snapshot.d.ts +61 -0
- package/dist/net/snapshot.js +301 -0
- package/dist/rules/adapter.d.ts +53 -0
- package/dist/rules/adapter.js +261 -0
- package/dist/rules/cards.d.ts +115 -0
- package/dist/rules/cards.js +547 -0
- package/dist/rules/collector.d.ts +161 -0
- package/dist/rules/collector.js +732 -0
- package/dist/rules/genres.d.ts +13 -0
- package/dist/rules/genres.js +38 -0
- package/dist/rules/harness.d.ts +16 -0
- package/dist/rules/harness.js +127 -0
- package/dist/rules/idle.d.ts +115 -0
- package/dist/rules/idle.js +370 -0
- package/dist/rules/index.d.ts +16 -0
- package/dist/rules/index.js +14 -0
- package/dist/rules/kit.d.ts +171 -0
- package/dist/rules/kit.js +88 -0
- package/dist/rules/tower.d.ts +214 -0
- package/dist/rules/tower.js +655 -0
- package/dist/runtime/arena.d.ts +86 -0
- package/dist/runtime/arena.js +269 -0
- package/dist/runtime/assets.d.ts +26 -0
- package/dist/runtime/assets.js +35 -0
- package/dist/runtime/camera.d.ts +113 -0
- package/dist/runtime/camera.js +247 -0
- package/dist/runtime/crowd.d.ts +18 -0
- package/dist/runtime/crowd.js +62 -0
- package/dist/runtime/feel/audio.d.ts +150 -0
- package/dist/runtime/feel/audio.js +327 -0
- package/dist/runtime/feel/clock.d.ts +52 -0
- package/dist/runtime/feel/clock.js +80 -0
- package/dist/runtime/feel/flash.d.ts +28 -0
- package/dist/runtime/feel/flash.js +60 -0
- package/dist/runtime/feel/index.d.ts +27 -0
- package/dist/runtime/feel/index.js +16 -0
- package/dist/runtime/feel/kit.d.ts +159 -0
- package/dist/runtime/feel/kit.js +226 -0
- package/dist/runtime/feel/knockback.d.ts +41 -0
- package/dist/runtime/feel/knockback.js +70 -0
- package/dist/runtime/feel/noise.d.ts +6 -0
- package/dist/runtime/feel/noise.js +30 -0
- package/dist/runtime/feel/numbers.d.ts +91 -0
- package/dist/runtime/feel/numbers.js +172 -0
- package/dist/runtime/feel/rumble.d.ts +40 -0
- package/dist/runtime/feel/rumble.js +58 -0
- package/dist/runtime/feel/sound.d.ts +96 -0
- package/dist/runtime/feel/sound.js +176 -0
- package/dist/runtime/feel/spring.d.ts +42 -0
- package/dist/runtime/feel/spring.js +95 -0
- package/dist/runtime/feel/trauma.d.ts +48 -0
- package/dist/runtime/feel/trauma.js +76 -0
- package/dist/runtime/feel/tween.d.ts +66 -0
- package/dist/runtime/feel/tween.js +180 -0
- package/dist/runtime/feel/types.d.ts +30 -0
- package/dist/runtime/feel/types.js +5 -0
- package/dist/runtime/index.d.ts +28 -0
- package/dist/runtime/index.js +26 -0
- package/dist/runtime/input.d.ts +38 -0
- package/dist/runtime/input.js +159 -0
- package/dist/runtime/interpolate.d.ts +6 -0
- package/dist/runtime/interpolate.js +13 -0
- package/dist/runtime/render/animation.d.ts +63 -0
- package/dist/runtime/render/animation.js +128 -0
- package/dist/runtime/render/atmosphere.d.ts +32 -0
- package/dist/runtime/render/atmosphere.js +59 -0
- package/dist/runtime/render/crowd-motion.d.ts +68 -0
- package/dist/runtime/render/crowd-motion.js +163 -0
- package/dist/runtime/render/decals.d.ts +64 -0
- package/dist/runtime/render/decals.js +222 -0
- package/dist/runtime/render/highlight.d.ts +29 -0
- package/dist/runtime/render/highlight.js +110 -0
- package/dist/runtime/render/index.d.ts +35 -0
- package/dist/runtime/render/index.js +22 -0
- package/dist/runtime/render/lighting.d.ts +50 -0
- package/dist/runtime/render/lighting.js +142 -0
- package/dist/runtime/render/particles.d.ts +117 -0
- package/dist/runtime/render/particles.js +289 -0
- package/dist/runtime/render/post.d.ts +92 -0
- package/dist/runtime/render/post.js +191 -0
- package/dist/runtime/render/presets.d.ts +57 -0
- package/dist/runtime/render/presets.js +116 -0
- package/dist/runtime/render/quality.d.ts +113 -0
- package/dist/runtime/render/quality.js +180 -0
- package/dist/runtime/render/rig.d.ts +29 -0
- package/dist/runtime/render/rig.js +107 -0
- package/dist/runtime/render/sprite.d.ts +34 -0
- package/dist/runtime/render/sprite.js +58 -0
- package/dist/runtime/render/stack.d.ts +74 -0
- package/dist/runtime/render/stack.js +132 -0
- package/dist/runtime/render/vat.d.ts +78 -0
- package/dist/runtime/render/vat.js +219 -0
- package/dist/runtime/renderer.d.ts +19 -0
- package/dist/runtime/renderer.js +18 -0
- package/dist/runtime/stats.d.ts +11 -0
- package/dist/runtime/stats.js +29 -0
- package/dist/runtime/styles/handdrawn/ground.d.ts +11 -0
- package/dist/runtime/styles/handdrawn/ground.js +282 -0
- package/dist/runtime/styles/handdrawn/index.d.ts +4 -0
- package/dist/runtime/styles/handdrawn/index.js +94 -0
- package/dist/runtime/styles/handdrawn/ink.d.ts +31 -0
- package/dist/runtime/styles/handdrawn/ink.js +68 -0
- package/dist/runtime/styles/handdrawn/looks.d.ts +18 -0
- package/dist/runtime/styles/handdrawn/looks.js +42 -0
- package/dist/runtime/styles/handdrawn/paint.d.ts +59 -0
- package/dist/runtime/styles/handdrawn/paint.js +129 -0
- package/dist/runtime/styles/handdrawn/palette.d.ts +49 -0
- package/dist/runtime/styles/handdrawn/palette.js +70 -0
- package/dist/runtime/styles/handdrawn/textures.d.ts +27 -0
- package/dist/runtime/styles/handdrawn/textures.js +150 -0
- package/dist/runtime/styles/handdrawn/vfx.d.ts +14 -0
- package/dist/runtime/styles/handdrawn/vfx.js +73 -0
- package/dist/runtime/styles/index.d.ts +9 -0
- package/dist/runtime/styles/index.js +32 -0
- package/dist/runtime/styles/kit.d.ts +193 -0
- package/dist/runtime/styles/kit.js +1 -0
- package/dist/runtime/styles/pixel/index.d.ts +46 -0
- package/dist/runtime/styles/pixel/index.js +167 -0
- package/dist/runtime/styles/ps1/index.d.ts +29 -0
- package/dist/runtime/styles/ps1/index.js +41 -0
- package/dist/runtime/styles/ps1/lights.d.ts +47 -0
- package/dist/runtime/styles/ps1/lights.js +146 -0
- package/dist/runtime/styles/ps1/materials.d.ts +62 -0
- package/dist/runtime/styles/ps1/materials.js +285 -0
- package/dist/runtime/styles/ps1/pipeline.d.ts +52 -0
- package/dist/runtime/styles/ps1/pipeline.js +111 -0
- package/dist/runtime/styles/ps1/surfaces.d.ts +44 -0
- package/dist/runtime/styles/ps1/surfaces.js +228 -0
- package/dist/runtime/styles/toon/index.d.ts +5 -0
- package/dist/runtime/styles/toon/index.js +178 -0
- package/dist/runtime/styles/toon/ink.d.ts +39 -0
- package/dist/runtime/styles/toon/ink.js +73 -0
- package/dist/runtime/styles/toon/look.d.ts +10 -0
- package/dist/runtime/styles/toon/look.js +112 -0
- package/dist/runtime/styles/toon/material.d.ts +74 -0
- package/dist/runtime/styles/toon/material.js +208 -0
- package/dist/runtime/styles/toon/normals.d.ts +26 -0
- package/dist/runtime/styles/toon/normals.js +127 -0
- package/dist/runtime/styles/toon/shadows.d.ts +24 -0
- package/dist/runtime/styles/toon/shadows.js +42 -0
- package/dist/runtime/styles/toon/vfx.d.ts +15 -0
- package/dist/runtime/styles/toon/vfx.js +47 -0
- package/dist/runtime/styles/view.d.ts +35 -0
- package/dist/runtime/styles/view.js +34 -0
- package/dist/runtime/styles/voxel/core.d.ts +114 -0
- package/dist/runtime/styles/voxel/core.js +577 -0
- package/dist/runtime/styles/voxel/geometry.d.ts +27 -0
- package/dist/runtime/styles/voxel/geometry.js +40 -0
- package/dist/runtime/styles/voxel/index.d.ts +21 -0
- package/dist/runtime/styles/voxel/index.js +23 -0
- package/dist/runtime/styles/voxel/kit.d.ts +6 -0
- package/dist/runtime/styles/voxel/kit.js +11 -0
- package/dist/runtime/styles/voxel/look.d.ts +10 -0
- package/dist/runtime/styles/voxel/look.js +16 -0
- package/dist/runtime/styles/voxel/props.d.ts +9 -0
- package/dist/runtime/styles/voxel/props.js +52 -0
- package/dist/runtime/styles/voxel/shapes.d.ts +41 -0
- package/dist/runtime/styles/voxel/shapes.js +83 -0
- package/dist/runtime/styles/voxel/world.d.ts +49 -0
- package/dist/runtime/styles/voxel/world.js +422 -0
- package/llms-full.txt +460 -0
- package/llms.txt +183 -0
- package/package.json +104 -3
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Earn rules as source text: read and rewrite the `earn.rule(...)` lines of a
|
|
3
|
+
* game's src/game.ts.
|
|
4
|
+
*
|
|
5
|
+
* game.ts is the single source of truth for what a game pays. The room
|
|
6
|
+
* server's referee pays the compiled module's `pay`, and the launch
|
|
7
|
+
* registers one RewardVault rule per `earn.rule`, in declaration order
|
|
8
|
+
* (vault rule ids 1, 2, 3...). So the
|
|
9
|
+
* Launch panel never keeps its own copy of a payout: an edit there is a
|
|
10
|
+
* rewrite of game.ts, which goes through the same gate and build as any
|
|
11
|
+
* other change, and what launches is what the build declares.
|
|
12
|
+
*
|
|
13
|
+
* Pure, no imports, so the Net Forge server, the Net Forge site and the
|
|
14
|
+
* tests can all share it.
|
|
15
|
+
*/
|
|
16
|
+
/** The engine's id rule (api/schemas.ts idSchema). */
|
|
17
|
+
export const EARN_RULE_ID = /^[a-z][a-z0-9_]{0,47}$/;
|
|
18
|
+
/** The engine's event-name rule (api/schemas.ts earnRuleSchema.when). */
|
|
19
|
+
export const EARN_EVENT = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/;
|
|
20
|
+
/** Most rules one game declares (the Launch panel's limit too). */
|
|
21
|
+
export const MAX_EARN_RULES = 8;
|
|
22
|
+
/** Highest count a rule may wait for. */
|
|
23
|
+
export const MAX_EARN_N = 100_000;
|
|
24
|
+
/** Highest payout one rule may make in one run, in coins. */
|
|
25
|
+
export const MAX_EARN_PAY = 1_000_000;
|
|
26
|
+
/**
|
|
27
|
+
* Events the sim raises by itself that may pay, per template (engine
|
|
28
|
+
* registry `on(...)` docs). A game's own `emit("...")` events are added from
|
|
29
|
+
* its source. Player-state events (downed, died, revived, levelup) are left
|
|
30
|
+
* out on purpose: they are cheap to farm.
|
|
31
|
+
*/
|
|
32
|
+
export const SIM_EARN_EVENTS = {
|
|
33
|
+
survivor: ["enemy.killed", "boss.killed"],
|
|
34
|
+
arena: ["opponent.killed", "round.top"],
|
|
35
|
+
// Board genres (rules/): what each genre's rules raise.
|
|
36
|
+
collector: ["creature.caught", "creature.defeated", "creature.levelup", "level.five", "level.ten", "warden.defeated", "run.won"],
|
|
37
|
+
"tower-defense": ["enemy.killed", "boss.killed", "wave.cleared", "run.won", "tower.built", "tower.upgraded"],
|
|
38
|
+
idle: ["salt.packed", "crystal.cut", "crystal.shipped", "contract.filled", "plant.built", "plant.upgraded", "shift.ended"],
|
|
39
|
+
platformer: ["player.finished", "race.won", "race.podium", "coin.collected", "checkpoint.reached"],
|
|
40
|
+
cards: ["duel.won", "ai.beaten", "run.won"],
|
|
41
|
+
racer: ["player.finished", "race.won", "race.podium", "coin.collected", "lap.completed", "drift.boosted", "drift.ultra"],
|
|
42
|
+
};
|
|
43
|
+
/** The plain trigger a player reads, from the rule's event and count. */
|
|
44
|
+
export function earnRuleLabel(when, n) {
|
|
45
|
+
switch (when) {
|
|
46
|
+
case "wave.cleared":
|
|
47
|
+
return `Clear wave ${n}`;
|
|
48
|
+
case "boss.killed":
|
|
49
|
+
return n > 1 ? `Beat ${n} bosses` : "Beat the boss";
|
|
50
|
+
case "enemy.killed":
|
|
51
|
+
return `${n.toLocaleString("en-US")} kills in a run`;
|
|
52
|
+
case "opponent.killed":
|
|
53
|
+
return `${n} eliminations in a round`;
|
|
54
|
+
case "streak.five":
|
|
55
|
+
return n > 1 ? `${n} streaks of five` : "Five eliminations in a row";
|
|
56
|
+
case "round.top":
|
|
57
|
+
return n > 1 ? `Top the board ${n} times` : "Top the board in a round";
|
|
58
|
+
case "player.finished":
|
|
59
|
+
return n > 1 ? `Finish ${n} courses` : "Reach the flag";
|
|
60
|
+
case "race.won":
|
|
61
|
+
return n > 1 ? `Win ${n} races` : "First to the flag";
|
|
62
|
+
case "race.podium":
|
|
63
|
+
return n > 1 ? `Top three ${n} times` : "Finish in the top three";
|
|
64
|
+
case "coin.collected":
|
|
65
|
+
return `${n} coins in a round`;
|
|
66
|
+
case "checkpoint.reached":
|
|
67
|
+
return n > 1 ? `Reach ${n} checkpoints` : "Reach a checkpoint";
|
|
68
|
+
case "lap.completed":
|
|
69
|
+
return n > 1 ? `Complete ${n} laps` : "Complete a lap";
|
|
70
|
+
case "drift.boosted":
|
|
71
|
+
return n > 1 ? `${n} drift boosts in a race` : "Land a drift boost";
|
|
72
|
+
case "drift.ultra":
|
|
73
|
+
return "Land a full-charge drift";
|
|
74
|
+
case "race.clean":
|
|
75
|
+
return "Finish without hitting a wall";
|
|
76
|
+
case "climb.clean":
|
|
77
|
+
return "Reach the flag without a fall";
|
|
78
|
+
case "tide.survived":
|
|
79
|
+
return n > 1 ? `Survive ${n} tides` : "Survive a tide";
|
|
80
|
+
case "salt.packed":
|
|
81
|
+
return `Pack ${n.toLocaleString("en-US")} crate${n > 1 ? "s" : ""} in a shift`;
|
|
82
|
+
case "crystal.cut":
|
|
83
|
+
return `Cut ${n} crystal${n > 1 ? "s" : ""} in a shift`;
|
|
84
|
+
case "contract.filled":
|
|
85
|
+
return "Fill the contract";
|
|
86
|
+
case "duel.won":
|
|
87
|
+
return n > 1 ? `Win ${n} duels against people` : "Win a duel against a person";
|
|
88
|
+
case "ai.beaten":
|
|
89
|
+
return n > 1 ? `Beat ${n} gauntlet opponents` : "Beat a gauntlet opponent";
|
|
90
|
+
case "creature.caught":
|
|
91
|
+
return n > 1 ? `Catch ${n} creatures` : "Catch a creature";
|
|
92
|
+
case "creature.defeated":
|
|
93
|
+
return `Win ${n} battle${n > 1 ? "s" : ""}`;
|
|
94
|
+
case "warden.defeated":
|
|
95
|
+
return "Bring down the warden";
|
|
96
|
+
case "level.five":
|
|
97
|
+
return "Raise a creature to level 5";
|
|
98
|
+
case "level.ten":
|
|
99
|
+
return "Raise a creature to level 10";
|
|
100
|
+
case "run.won":
|
|
101
|
+
return n > 1 ? `Win ${n} runs` : "Win the run";
|
|
102
|
+
case "tower.built":
|
|
103
|
+
return `Build ${n} tower${n > 1 ? "s" : ""}`;
|
|
104
|
+
case "tower.upgraded":
|
|
105
|
+
return `${n} tower upgrade${n > 1 ? "s" : ""}`;
|
|
106
|
+
default:
|
|
107
|
+
return `${when.replace(/[._]/g, " ")}${n > 1 ? ` x${n}` : ""}`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** The source with comments blanked to spaces (same length, so indices line up). Strings are kept. */
|
|
111
|
+
function maskComments(src) {
|
|
112
|
+
const out = src.split("");
|
|
113
|
+
let i = 0;
|
|
114
|
+
while (i < src.length) {
|
|
115
|
+
const c = src[i];
|
|
116
|
+
const d = src[i + 1];
|
|
117
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
118
|
+
i++;
|
|
119
|
+
while (i < src.length && src[i] !== c) {
|
|
120
|
+
if (src[i] === "\\")
|
|
121
|
+
i++;
|
|
122
|
+
else if (c !== "`" && src[i] === "\n")
|
|
123
|
+
break;
|
|
124
|
+
i++;
|
|
125
|
+
}
|
|
126
|
+
i++;
|
|
127
|
+
}
|
|
128
|
+
else if (c === "/" && d === "/") {
|
|
129
|
+
while (i < src.length && src[i] !== "\n")
|
|
130
|
+
out[i++] = " ";
|
|
131
|
+
}
|
|
132
|
+
else if (c === "/" && d === "*") {
|
|
133
|
+
const end = src.indexOf("*/", i + 2);
|
|
134
|
+
const stop = end < 0 ? src.length : end + 2;
|
|
135
|
+
for (; i < stop; i++)
|
|
136
|
+
if (src[i] !== "\n")
|
|
137
|
+
out[i] = " ";
|
|
138
|
+
}
|
|
139
|
+
else
|
|
140
|
+
i++;
|
|
141
|
+
}
|
|
142
|
+
return out.join("");
|
|
143
|
+
}
|
|
144
|
+
const CALL = /earn\s*\.\s*rule\s*\(/g;
|
|
145
|
+
const FULL = /^earn\.rule\(\s*"([^"\n]*)"\s*,\s*\{([^{}]*)\}\s*\)\s*;?\s*$/;
|
|
146
|
+
function field(body, name) {
|
|
147
|
+
return new RegExp(`(?:^|[,{\\s])${name}\\s*:\\s*([^,]+?)\\s*(?:,|$)`).exec(body.trim())?.[1] ?? null;
|
|
148
|
+
}
|
|
149
|
+
function parseBody(id, body) {
|
|
150
|
+
const when = /^"([^"]+)"$/.exec(field(body, "when") ?? "")?.[1];
|
|
151
|
+
const nRaw = field(body, "n");
|
|
152
|
+
const payRaw = field(body, "pay");
|
|
153
|
+
const once = /^"(run|day|lifetime)"$/.exec(field(body, "oncePer") ?? "")?.[1];
|
|
154
|
+
const n = nRaw === null ? 1 : /^\d+$/.test(nRaw) ? Number(nRaw) : NaN;
|
|
155
|
+
const pay = payRaw !== null && /^\d+(\.\d+)?$/.test(payRaw) ? Number(payRaw) : NaN;
|
|
156
|
+
const known = body
|
|
157
|
+
.split(",")
|
|
158
|
+
.map((p) => p.split(":")[0]?.trim() ?? "")
|
|
159
|
+
.filter(Boolean);
|
|
160
|
+
if (known.some((k) => !["when", "n", "pay", "oncePer"].includes(k)))
|
|
161
|
+
return { rule: null, problem: `earn rule "${id}" has a field the panel does not know` };
|
|
162
|
+
if (!when || !Number.isFinite(n) || !Number.isFinite(pay) || !once)
|
|
163
|
+
return { rule: null, problem: `earn rule "${id}" is not written with plain values` };
|
|
164
|
+
return { rule: { id, when, n, pay, oncePer: once }, problem: null };
|
|
165
|
+
}
|
|
166
|
+
function statements(src) {
|
|
167
|
+
const masked = maskComments(src);
|
|
168
|
+
const out = [];
|
|
169
|
+
CALL.lastIndex = 0;
|
|
170
|
+
for (let m = CALL.exec(masked); m; m = CALL.exec(masked)) {
|
|
171
|
+
const lineStart = masked.lastIndexOf("\n", m.index) + 1;
|
|
172
|
+
const nl = masked.indexOf("\n", m.index);
|
|
173
|
+
const lineEnd = nl < 0 ? src.length : nl + 1;
|
|
174
|
+
const line = src.slice(m.index, nl < 0 ? src.length : nl).replace(/\/\/.*$/, "").trim();
|
|
175
|
+
const indent = src.slice(lineStart, m.index);
|
|
176
|
+
const full = FULL.exec(line);
|
|
177
|
+
if (indent.length > 0) {
|
|
178
|
+
out.push({ lineStart, lineEnd, rule: null, id: full?.[1] ?? null, problem: "an earn rule is declared inside a block; declare every earn.rule at the top level, one per line" });
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (!full) {
|
|
182
|
+
out.push({ lineStart, lineEnd, rule: null, id: null, problem: "an earn rule is not written on one line as earn.rule(\"id\", { when, n, pay, oncePer })" });
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const id = full[1] ?? "";
|
|
186
|
+
const { rule, problem } = parseBody(id, full[2] ?? "");
|
|
187
|
+
out.push({ lineStart, lineEnd, rule, id, problem });
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
/** The game's earn rules, in declaration order. */
|
|
192
|
+
export function readEarnRules(source) {
|
|
193
|
+
const st = statements(source);
|
|
194
|
+
return { rules: st.flatMap((s) => (s.rule ? [s.rule] : [])), problems: st.flatMap((s) => (s.problem ? [s.problem] : [])) };
|
|
195
|
+
}
|
|
196
|
+
/** Events a game may pay on: the template's sim events and every `emit("...")` in its source. */
|
|
197
|
+
export function earnEvents(source) {
|
|
198
|
+
const masked = maskComments(source);
|
|
199
|
+
const template = /\btemplate\s*:\s*"([a-z-]+)"/.exec(masked)?.[1] ?? "";
|
|
200
|
+
const out = new Set(SIM_EARN_EVENTS[template] ?? []);
|
|
201
|
+
const re = /\bemit\(\s*"([^"]+)"/g;
|
|
202
|
+
for (let m = re.exec(masked); m; m = re.exec(masked))
|
|
203
|
+
if (m[1] && EARN_EVENT.test(m[1]))
|
|
204
|
+
out.add(m[1]);
|
|
205
|
+
return [...out];
|
|
206
|
+
}
|
|
207
|
+
/** Every problem with a rule set, as one plain line each; empty when it can be written. */
|
|
208
|
+
export function checkEarnRules(rules, events) {
|
|
209
|
+
const out = [];
|
|
210
|
+
if (rules.length === 0)
|
|
211
|
+
out.push("A game needs at least one earn rule.");
|
|
212
|
+
if (rules.length > MAX_EARN_RULES)
|
|
213
|
+
out.push(`A game has at most ${MAX_EARN_RULES} earn rules.`);
|
|
214
|
+
const seen = new Set();
|
|
215
|
+
rules.forEach((r, i) => {
|
|
216
|
+
const what = `Earn rule ${i + 1}`;
|
|
217
|
+
if (!EARN_RULE_ID.test(r.id))
|
|
218
|
+
out.push(`${what} has an id that is not lower_snake_case.`);
|
|
219
|
+
if (seen.has(r.id))
|
|
220
|
+
out.push(`${what} repeats the id "${r.id}".`);
|
|
221
|
+
seen.add(r.id);
|
|
222
|
+
if (!events.includes(r.when))
|
|
223
|
+
out.push(`${what} counts "${r.when}", which this game never raises.`);
|
|
224
|
+
if (!Number.isInteger(r.n) || r.n < 1 || r.n > MAX_EARN_N)
|
|
225
|
+
out.push(`${what}: the count is a whole number from 1 to ${MAX_EARN_N.toLocaleString("en-US")}.`);
|
|
226
|
+
if (!(r.pay > 0) || r.pay > MAX_EARN_PAY || Math.round(r.pay * 1e6) / 1e6 !== r.pay)
|
|
227
|
+
out.push(`${what}: the payout is above 0, at most ${MAX_EARN_PAY.toLocaleString("en-US")}, with up to 6 decimals.`);
|
|
228
|
+
});
|
|
229
|
+
return out;
|
|
230
|
+
}
|
|
231
|
+
/** A fresh rule id for an event and count, unique among `taken`. */
|
|
232
|
+
export function newEarnRuleId(when, n, taken) {
|
|
233
|
+
const base = `${when.replace(/[^a-z0-9]+/g, "_")}_${n}`.replace(/^[^a-z]+/, "r_").slice(0, 40);
|
|
234
|
+
let id = base;
|
|
235
|
+
for (let k = 2; taken.includes(id); k++)
|
|
236
|
+
id = `${base}_${k}`;
|
|
237
|
+
return id;
|
|
238
|
+
}
|
|
239
|
+
function payText(p) {
|
|
240
|
+
// Up to 6 decimals, never an exponent.
|
|
241
|
+
return String(Math.round(p * 1e6) / 1e6);
|
|
242
|
+
}
|
|
243
|
+
/** The one line a rule is written as. */
|
|
244
|
+
export function earnRuleLine(r) {
|
|
245
|
+
return `earn.rule(${JSON.stringify(r.id)}, { when: ${JSON.stringify(r.when)}, n: ${r.n}, pay: ${payText(r.pay)}, oncePer: ${JSON.stringify(r.oncePer)} });`;
|
|
246
|
+
}
|
|
247
|
+
export class EarnSourceError extends Error {
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* game.ts with its earn rules replaced by `rules`, in that order. The
|
|
251
|
+
* declarations are written as one block where the first one was, so the
|
|
252
|
+
* order in the file is the order given (the vault's rule ids). Throws
|
|
253
|
+
* EarnSourceError when an existing declaration cannot be read safely
|
|
254
|
+
* (nested, multi-line or computed): those games change their rules in chat.
|
|
255
|
+
*/
|
|
256
|
+
export function writeEarnRules(source, rules) {
|
|
257
|
+
const st = statements(source);
|
|
258
|
+
const problem = st.find((s) => s.problem)?.problem;
|
|
259
|
+
if (problem)
|
|
260
|
+
throw new EarnSourceError(problem);
|
|
261
|
+
const block = rules.map((r) => `${earnRuleLine(r)}\n`).join("");
|
|
262
|
+
if (st.length === 0)
|
|
263
|
+
return `${source.replace(/\n*$/, "\n")}${block}`;
|
|
264
|
+
let out = "";
|
|
265
|
+
let at = 0;
|
|
266
|
+
st.forEach((s, i) => {
|
|
267
|
+
out += source.slice(at, s.lineStart);
|
|
268
|
+
if (i === 0)
|
|
269
|
+
out += block;
|
|
270
|
+
at = s.lineEnd;
|
|
271
|
+
});
|
|
272
|
+
return out + source.slice(at);
|
|
273
|
+
}
|
|
274
|
+
/** Same rules, same order, same values (labels and launch settings aside). */
|
|
275
|
+
export function sameEarnRules(a, b) {
|
|
276
|
+
return a.length === b.length && a.every((r, i) => {
|
|
277
|
+
const o = b[i];
|
|
278
|
+
return !!o && o.id === r.id && o.when === r.when && o.n === r.n && o.pay === r.pay && o.oncePer === r.oncePer;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
/** PLACEHOLDER: the engine's cap on the multiple (api/schemas.ts STAKE_MAX_MULTIPLE). */
|
|
282
|
+
export const MAX_STAKE_MULTIPLE = 2;
|
|
283
|
+
/** The engine's cap on one stake, in coins (api/schemas.ts STAKE_MAX_COINS). */
|
|
284
|
+
export const MAX_STAKE_COINS = 1_000_000;
|
|
285
|
+
const STAKE_CALL = /earn\s*\.\s*stake\s*\(/g;
|
|
286
|
+
const STAKE_FULL = /^earn\.stake\(\s*\{([^{}]*)\}\s*\)\s*;?\s*$/;
|
|
287
|
+
function stakeStatements(src) {
|
|
288
|
+
const masked = maskComments(src);
|
|
289
|
+
const out = [];
|
|
290
|
+
STAKE_CALL.lastIndex = 0;
|
|
291
|
+
for (let m = STAKE_CALL.exec(masked); m; m = STAKE_CALL.exec(masked)) {
|
|
292
|
+
const lineStart = masked.lastIndexOf("\n", m.index) + 1;
|
|
293
|
+
const nl = masked.indexOf("\n", m.index);
|
|
294
|
+
const lineEnd = nl < 0 ? src.length : nl + 1;
|
|
295
|
+
const line = src.slice(m.index, nl < 0 ? src.length : nl).replace(/\/\/.*$/, "").trim();
|
|
296
|
+
if (src.slice(lineStart, m.index).length > 0) {
|
|
297
|
+
out.push({ lineStart, lineEnd, stake: null, problem: "earn.stake is declared inside a block; declare it at the top level, on one line" });
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const body = STAKE_FULL.exec(line)?.[1];
|
|
301
|
+
if (body === undefined) {
|
|
302
|
+
out.push({ lineStart, lineEnd, stake: null, problem: "earn.stake is not written on one line as earn.stake({ min, max, multiple })" });
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const num = (k) => {
|
|
306
|
+
const v = field(body, k);
|
|
307
|
+
return v !== null && /^\d+(\.\d+)?$/.test(v) ? Number(v) : NaN;
|
|
308
|
+
};
|
|
309
|
+
const keys = body.split(",").map((p) => p.split(":")[0]?.trim() ?? "").filter(Boolean);
|
|
310
|
+
const stake = { min: num("min"), max: num("max"), multiple: num("multiple") };
|
|
311
|
+
if (keys.some((k) => !["min", "max", "multiple"].includes(k)))
|
|
312
|
+
out.push({ lineStart, lineEnd, stake: null, problem: "earn.stake has a field the panel does not know" });
|
|
313
|
+
else if (![stake.min, stake.max, stake.multiple].every(Number.isFinite))
|
|
314
|
+
out.push({ lineStart, lineEnd, stake: null, problem: "earn.stake is not written with plain values" });
|
|
315
|
+
else
|
|
316
|
+
out.push({ lineStart, lineEnd, stake, problem: null });
|
|
317
|
+
}
|
|
318
|
+
return out;
|
|
319
|
+
}
|
|
320
|
+
/** The game's buy-in run terms. */
|
|
321
|
+
export function readEarnStake(source) {
|
|
322
|
+
const st = stakeStatements(source);
|
|
323
|
+
const problems = st.flatMap((s) => (s.problem ? [s.problem] : []));
|
|
324
|
+
if (st.length > 1)
|
|
325
|
+
problems.push("earn.stake is declared more than once");
|
|
326
|
+
if (st.length === 0)
|
|
327
|
+
problems.push("the game has no buy-in run: add earn.stake({ min, max, multiple })");
|
|
328
|
+
return { stake: st.length === 1 ? (st[0]?.stake ?? null) : null, problems };
|
|
329
|
+
}
|
|
330
|
+
/** Every problem with stake terms, as one plain line each; empty when they can be written. */
|
|
331
|
+
export function checkEarnStake(s) {
|
|
332
|
+
const out = [];
|
|
333
|
+
const dec = (v) => Math.round(v * 1e6) / 1e6 === v;
|
|
334
|
+
if (!(s.min > 0) || s.min > MAX_STAKE_COINS || !dec(s.min))
|
|
335
|
+
out.push(`The smallest stake is above 0 and at most ${MAX_STAKE_COINS.toLocaleString("en-US")} coins.`);
|
|
336
|
+
if (!(s.max > 0) || s.max > MAX_STAKE_COINS || !dec(s.max))
|
|
337
|
+
out.push(`The largest stake is above 0 and at most ${MAX_STAKE_COINS.toLocaleString("en-US")} coins.`);
|
|
338
|
+
if (s.min > s.max)
|
|
339
|
+
out.push("The smallest stake is above the largest.");
|
|
340
|
+
if (!(s.multiple >= 1) || s.multiple > MAX_STAKE_MULTIPLE || Math.round(s.multiple * 100) / 100 !== s.multiple)
|
|
341
|
+
out.push(`The payout ceiling is 1x to ${MAX_STAKE_MULTIPLE}x the stake.`);
|
|
342
|
+
return out;
|
|
343
|
+
}
|
|
344
|
+
/** The one line the stake is written as. */
|
|
345
|
+
export function earnStakeLine(s) {
|
|
346
|
+
return `earn.stake({ min: ${payText(s.min)}, max: ${payText(s.max)}, multiple: ${payText(s.multiple)} });`;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* game.ts with its buy-in run set to `stake`: the declaration is rewritten
|
|
350
|
+
* in place, or added after the last earn rule when there is none. Throws
|
|
351
|
+
* EarnSourceError when an existing one cannot be read safely.
|
|
352
|
+
*/
|
|
353
|
+
export function writeEarnStake(source, stake) {
|
|
354
|
+
const st = stakeStatements(source);
|
|
355
|
+
const problem = st.find((s) => s.problem)?.problem;
|
|
356
|
+
if (problem)
|
|
357
|
+
throw new EarnSourceError(problem);
|
|
358
|
+
if (st.length > 1)
|
|
359
|
+
throw new EarnSourceError("earn.stake is declared more than once");
|
|
360
|
+
const line = `${earnStakeLine(stake)}\n`;
|
|
361
|
+
const one = st[0];
|
|
362
|
+
if (one)
|
|
363
|
+
return source.slice(0, one.lineStart) + line + source.slice(one.lineEnd);
|
|
364
|
+
const rules = statements(source);
|
|
365
|
+
const last = rules[rules.length - 1];
|
|
366
|
+
if (last)
|
|
367
|
+
return source.slice(0, last.lineEnd) + line + source.slice(last.lineEnd);
|
|
368
|
+
return `${source.replace(/\n*$/, "\n")}${line}`;
|
|
369
|
+
}
|
|
370
|
+
/** Same terms. */
|
|
371
|
+
export function sameEarnStake(a, b) {
|
|
372
|
+
return a === b || (!!a && !!b && a.min === b.min && a.max === b.max && a.multiple === b.multiple);
|
|
373
|
+
}
|
|
374
|
+
/** The game's PvP pot, read only (null when it has none or it is not written with plain values on one line). */
|
|
375
|
+
export function readEarnPot(source) {
|
|
376
|
+
const masked = maskComments(source);
|
|
377
|
+
const m = /^earn\.pot\(\s*\{([^{}\n]*)\}\s*\)\s*;?\s*$/m.exec(masked);
|
|
378
|
+
if (!m?.[1])
|
|
379
|
+
return null;
|
|
380
|
+
const num = (k) => {
|
|
381
|
+
const v = field(m[1] ?? "", k);
|
|
382
|
+
return v !== null && /^\d+(\.\d+)?$/.test(v) ? Number(v) : NaN;
|
|
383
|
+
};
|
|
384
|
+
const pot = { entry: num("entry"), recycle: num("recycle"), enabled: field(m[1] ?? "", "enabled") === "true" };
|
|
385
|
+
return Number.isFinite(pot.entry) && Number.isFinite(pot.recycle) ? pot : null;
|
|
386
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Earn rules from the game module, counted per player from authoritative
|
|
3
|
+
* game events. The room
|
|
4
|
+
* server turns each award into an EarnEvent for its referee, which signs.
|
|
5
|
+
*
|
|
6
|
+
* A rule `{ when, n, oncePer }` pays once when a player's count of `when`
|
|
7
|
+
* reaches `n` in a run. "day" and "lifetime" rules are also paid at most
|
|
8
|
+
* once per run here; RewardVault's per-wallet caps enforce the longer windows.
|
|
9
|
+
*/
|
|
10
|
+
import type { EarnRuleDef } from "../api/schemas.js";
|
|
11
|
+
/** Something that happened in a run, and the players it credits. */
|
|
12
|
+
export interface GameEvent {
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly players: readonly number[];
|
|
15
|
+
/**
|
|
16
|
+
* Counts for score, the kill feed, systems, achievements and missions, but
|
|
17
|
+
* never for earn rules: a kill of a seat bot, a "round.top" with no other
|
|
18
|
+
* person in the round, and anything a system emits while handling one.
|
|
19
|
+
*/
|
|
20
|
+
readonly unpaid?: true;
|
|
21
|
+
/**
|
|
22
|
+
* The event credits each of its players for something that player did
|
|
23
|
+
* themselves (a kill, a pickup, a revive). Every other event is world or
|
|
24
|
+
* fight progress (a boss down, a wave cleared, a round topped, a run won,
|
|
25
|
+
* anything a system emits): it never pays a player whose earning run
|
|
26
|
+
* started after this world began (GameRunner.startRun), since the
|
|
27
|
+
* progress was made before the run existed.
|
|
28
|
+
*/
|
|
29
|
+
readonly own?: true;
|
|
30
|
+
}
|
|
31
|
+
export interface EarnAward {
|
|
32
|
+
readonly player: number;
|
|
33
|
+
/** The earn rule id as declared (`earn.rule(id, ...)`). */
|
|
34
|
+
readonly rule: string;
|
|
35
|
+
readonly units: number;
|
|
36
|
+
}
|
|
37
|
+
export declare class EarnTracker {
|
|
38
|
+
private readonly counts;
|
|
39
|
+
private readonly paid;
|
|
40
|
+
private readonly byEvent;
|
|
41
|
+
constructor(rules: ReadonlyMap<string, EarnRuleDef>);
|
|
42
|
+
record(e: GameEvent): EarnAward[];
|
|
43
|
+
/** A new run: every count and every once-per-run payout starts over. */
|
|
44
|
+
resetRun(): void;
|
|
45
|
+
/** A player left, or their referee run started over: forget their counts and payouts. */
|
|
46
|
+
forget(player: number): void;
|
|
47
|
+
/** Count of an event for a player this run (HUD and tests). */
|
|
48
|
+
count(player: number, event: string): number;
|
|
49
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export class EarnTracker {
|
|
2
|
+
counts = new Map();
|
|
3
|
+
paid = new Map();
|
|
4
|
+
byEvent = new Map();
|
|
5
|
+
constructor(rules) {
|
|
6
|
+
for (const [id, r] of rules) {
|
|
7
|
+
const list = this.byEvent.get(r.when) ?? [];
|
|
8
|
+
list.push([id, r]);
|
|
9
|
+
this.byEvent.set(r.when, list);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
record(e) {
|
|
13
|
+
const rules = this.byEvent.get(e.name);
|
|
14
|
+
if (!rules)
|
|
15
|
+
return [];
|
|
16
|
+
const out = [];
|
|
17
|
+
for (const p of e.players) {
|
|
18
|
+
let c = this.counts.get(p);
|
|
19
|
+
if (!c) {
|
|
20
|
+
c = new Map();
|
|
21
|
+
this.counts.set(p, c);
|
|
22
|
+
}
|
|
23
|
+
const n = (c.get(e.name) ?? 0) + 1;
|
|
24
|
+
c.set(e.name, n);
|
|
25
|
+
for (const [id, r] of rules) {
|
|
26
|
+
if (n < r.n)
|
|
27
|
+
continue;
|
|
28
|
+
let paid = this.paid.get(p);
|
|
29
|
+
if (!paid) {
|
|
30
|
+
paid = new Set();
|
|
31
|
+
this.paid.set(p, paid);
|
|
32
|
+
}
|
|
33
|
+
if (paid.has(id))
|
|
34
|
+
continue;
|
|
35
|
+
paid.add(id);
|
|
36
|
+
out.push({ player: p, rule: id, units: 1 });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
/** A new run: every count and every once-per-run payout starts over. */
|
|
42
|
+
resetRun() {
|
|
43
|
+
this.counts.clear();
|
|
44
|
+
this.paid.clear();
|
|
45
|
+
}
|
|
46
|
+
/** A player left, or their referee run started over: forget their counts and payouts. */
|
|
47
|
+
forget(player) {
|
|
48
|
+
this.counts.delete(player);
|
|
49
|
+
this.paid.delete(player);
|
|
50
|
+
}
|
|
51
|
+
/** Count of an event for a player this run (HUD and tests). */
|
|
52
|
+
count(player, event) {
|
|
53
|
+
return this.counts.get(player)?.get(event) ?? 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @netforge/engine/game: runs a declared game on engine-sim. Used by
|
|
3
|
+
* the room server, the headless harness and template clients; generated
|
|
4
|
+
* game code never imports it (it sees only the declarative API).
|
|
5
|
+
*
|
|
6
|
+
* - compileGame(manifest): prefabs, weapons and upgrades → sim content.
|
|
7
|
+
* - GameRunner: systems on a run clock, game events, earn, run phases.
|
|
8
|
+
* - engineSimAdapter(...): the netcode SimAdapter over engine-sim.
|
|
9
|
+
* - EarnTracker: earn rules counted per player per run.
|
|
10
|
+
*/
|
|
11
|
+
export { compileGame, prefabKind, ARENA_DEFAULTS } from "./compile.js";
|
|
12
|
+
export type { CompiledGame } from "./compile.js";
|
|
13
|
+
export { GameRunner } from "./runner.js";
|
|
14
|
+
export type { KillFeedEntry, RunInfo, RunPhase, RunPlayerInfo, RunnerOptions, RunnerStep } from "./runner.js";
|
|
15
|
+
export { arenaBotInput, arenaBotMemory, sightBlocked } from "./arena-bot.js";
|
|
16
|
+
export type { ArenaBotMemory, ArenaBotOpponent, ArenaBotView, ArenaBotWeapon } from "./arena-bot.js";
|
|
17
|
+
export { EarnTracker } from "./earn.js";
|
|
18
|
+
export type { EarnAward, GameEvent } from "./earn.js";
|
|
19
|
+
export { engineSimAdapter, engineSchema, disposeEngineWorld, F, CROWD_UNITS, PLAYER_UNITS } from "./adapter.js";
|
|
20
|
+
export type { EngineAdapterOptions, EngineRoomWorld } from "./adapter.js";
|
|
21
|
+
export { collectManifest, resetRegistry } from "../api/registry.js";
|
|
22
|
+
export { botInput, HumanBot } from "./bot.js";
|
|
23
|
+
export type { BotView, BotDanger, HumanBotOptions, HumanBotView } from "./bot.js";
|
|
24
|
+
export { Director, curveAt } from "./director.js";
|
|
25
|
+
export { SURVIVOR_VIEWS, DUNGEON_VIEWS, viewsFor, footprint, viewRadii, radiusAt, insideView } from "./view.js";
|
|
26
|
+
export type { CameraView } from "./view.js";
|
|
27
|
+
export { threatOf, laneLength } from "./threats.js";
|
|
28
|
+
export type { ThreatShape } from "./threats.js";
|
|
29
|
+
export { levelTable } from "./compile.js";
|
|
30
|
+
export { profileSpec, openKeepers, ownedCosmetics, ownedGear, metaPrice, metaBonus, packMeta, unpackMeta, todaysGoals, utcDay, skuKind, RESERVED_COMMANDS } from "./profile.js";
|
|
31
|
+
export type { ProfileSpec, ProfileSku, ProfileUnlockState, RunProgress, SkuKind } from "./profile.js";
|
|
32
|
+
export { RaceRunner } from "./race.js";
|
|
33
|
+
export type { RaceCore, RaceInfo, RacePhase, RacePlayerInfo, RaceRunnerOptions, RaceSimEvent, RaceStep } from "./race.js";
|
|
34
|
+
export { compilePlatformer, platformAutopilot, planCourse, routeFor, unroutable, botDelaySeconds, platformAdapter, platformBotInput, platformSchema, PlatformCore, PF, PLATFORMER_DEFAULTS, raceInputCodec } from "./platformer.js";
|
|
35
|
+
export type { CompiledPlatformer, PlanResult, PlatformAdapterOptions, PlatformBotMemory, PlatformRoomWorld } from "./platformer.js";
|
|
36
|
+
export { compileRacer, kartAdapter, kartAutopilot, kartBotInput, kartFromValues, kartSchema, KartCore, KF, RACER_DEFAULTS } from "./racer.js";
|
|
37
|
+
export type { CompiledRacer, KartAdapterOptions, KartRoomWorld } from "./racer.js";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @netforge/engine/game: runs a declared game on engine-sim. Used by
|
|
3
|
+
* the room server, the headless harness and template clients; generated
|
|
4
|
+
* game code never imports it (it sees only the declarative API).
|
|
5
|
+
*
|
|
6
|
+
* - compileGame(manifest): prefabs, weapons and upgrades → sim content.
|
|
7
|
+
* - GameRunner: systems on a run clock, game events, earn, run phases.
|
|
8
|
+
* - engineSimAdapter(...): the netcode SimAdapter over engine-sim.
|
|
9
|
+
* - EarnTracker: earn rules counted per player per run.
|
|
10
|
+
*/
|
|
11
|
+
export { compileGame, prefabKind, ARENA_DEFAULTS } from "./compile.js";
|
|
12
|
+
export { GameRunner } from "./runner.js";
|
|
13
|
+
export { arenaBotInput, arenaBotMemory, sightBlocked } from "./arena-bot.js";
|
|
14
|
+
export { EarnTracker } from "./earn.js";
|
|
15
|
+
export { engineSimAdapter, engineSchema, disposeEngineWorld, F, CROWD_UNITS, PLAYER_UNITS } from "./adapter.js";
|
|
16
|
+
export { collectManifest, resetRegistry } from "../api/registry.js";
|
|
17
|
+
export { botInput, HumanBot } from "./bot.js";
|
|
18
|
+
export { Director, curveAt } from "./director.js";
|
|
19
|
+
export { SURVIVOR_VIEWS, DUNGEON_VIEWS, viewsFor, footprint, viewRadii, radiusAt, insideView } from "./view.js";
|
|
20
|
+
export { threatOf, laneLength } from "./threats.js";
|
|
21
|
+
export { levelTable } from "./compile.js";
|
|
22
|
+
export { profileSpec, openKeepers, ownedCosmetics, ownedGear, metaPrice, metaBonus, packMeta, unpackMeta, todaysGoals, utcDay, skuKind, RESERVED_COMMANDS } from "./profile.js";
|
|
23
|
+
export { RaceRunner } from "./race.js";
|
|
24
|
+
export { compilePlatformer, platformAutopilot, planCourse, routeFor, unroutable, botDelaySeconds, platformAdapter, platformBotInput, platformSchema, PlatformCore, PF, PLATFORMER_DEFAULTS, raceInputCodec } from "./platformer.js";
|
|
25
|
+
export { compileRacer, kartAdapter, kartAutopilot, kartBotInput, kartFromValues, kartSchema, KartCore, KF, RACER_DEFAULTS } from "./racer.js";
|