@forgeax/engine-project 0.1.2
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/LICENSE +202 -0
- package/README.md +82 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/ac14-migration.test.d.ts +2 -0
- package/dist/__tests__/ac14-migration.test.d.ts.map +1 -0
- package/dist/__tests__/errors.test-d.d.ts +2 -0
- package/dist/__tests__/errors.test-d.d.ts.map +1 -0
- package/dist/__tests__/errors.test.d.ts +2 -0
- package/dist/__tests__/errors.test.d.ts.map +1 -0
- package/dist/__tests__/loader.test.d.ts +2 -0
- package/dist/__tests__/loader.test.d.ts.map +1 -0
- package/dist/__tests__/resolve.test.d.ts +2 -0
- package/dist/__tests__/resolve.test.d.ts.map +1 -0
- package/dist/__tests__/schema.test.d.ts +2 -0
- package/dist/__tests__/schema.test.d.ts.map +1 -0
- package/dist/__tests__/structural.test.d.ts +2 -0
- package/dist/__tests__/structural.test.d.ts.map +1 -0
- package/dist/errors.d.ts +84 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +291 -0
- package/dist/index.mjs.map +1 -0
- package/dist/loader.d.ts +99 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/paths.d.ts +3 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/schema.d.ts +216 -0
- package/dist/schema.d.ts.map +1 -0
- package/package.json +65 -0
- package/src/__tests__/ac14-migration.test.ts +124 -0
- package/src/__tests__/errors.test-d.ts +77 -0
- package/src/__tests__/errors.test.ts +254 -0
- package/src/__tests__/fixtures/games/cow-level/forge.json +7 -0
- package/src/__tests__/fixtures/games/cow-survivor/forge.json +8 -0
- package/src/__tests__/fixtures/games/fps/forge.json +7 -0
- package/src/__tests__/fixtures/games/hellforge/forge.json +24 -0
- package/src/__tests__/fixtures/games/shoot-opt/forge.json +6 -0
- package/src/__tests__/fixtures/games/spin-cube/forge.json +6 -0
- package/src/__tests__/loader.test.ts +364 -0
- package/src/__tests__/resolve.test.ts +160 -0
- package/src/__tests__/schema.test.ts +317 -0
- package/src/__tests__/structural.test.ts +175 -0
- package/src/errors.ts +129 -0
- package/src/index.ts +37 -0
- package/src/loader.ts +344 -0
- package/src/paths.ts +7 -0
- package/src/schema.ts +149 -0
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const GuidString: z.ZodEffects<z.ZodString, string, string>;
|
|
3
|
+
export declare const PluginRealmSchema: z.ZodEnum<["host", "engine", "build"]>;
|
|
4
|
+
export interface GameProjectPluginEntry {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly config?: unknown;
|
|
8
|
+
readonly group?: boolean | null | undefined;
|
|
9
|
+
readonly disabled?: boolean | null | undefined;
|
|
10
|
+
readonly inject?: readonly string[] | Readonly<Record<string, unknown>> | null | undefined;
|
|
11
|
+
readonly realm?: z.infer<typeof PluginRealmSchema> | undefined;
|
|
12
|
+
}
|
|
13
|
+
export declare const GameProjectPluginEntrySchema: z.ZodType<GameProjectPluginEntry>;
|
|
14
|
+
export declare const GameProjectPluginsSchema: z.ZodEffects<z.ZodArray<z.ZodType<GameProjectPluginEntry, z.ZodTypeDef, GameProjectPluginEntry>, "many">, GameProjectPluginEntry[], GameProjectPluginEntry[]>;
|
|
15
|
+
export declare const GameProjectSchema: z.ZodObject<{
|
|
16
|
+
/** Stable game slug; matches the `.forgeax/games/<id>/` directory name. */
|
|
17
|
+
id: z.ZodString;
|
|
18
|
+
/** Human-facing display name (may contain any unicode). */
|
|
19
|
+
name: z.ZodString;
|
|
20
|
+
/** forge.json schema version, e.g. "1.0.0" — required so loaders can gate on format. */
|
|
21
|
+
schemaVersion: z.ZodString;
|
|
22
|
+
/** Optional bootstrap hook: entry module relative to game root (e.g. "main.ts", "src/main.ts"). */
|
|
23
|
+
entry: z.ZodOptional<z.ZodString>;
|
|
24
|
+
/** Optional realm-local ExecutionBootstrapEntry module relative to game root. */
|
|
25
|
+
executionEntry: z.ZodOptional<z.ZodString>;
|
|
26
|
+
/** Optional GUID of the scene to load first; must be a real scene asset GUID (GuidString-validated). */
|
|
27
|
+
defaultScene: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
28
|
+
/** Optional physics backend: enum tag ('3d'/'2d'/'rapier-3d'/'rapier-2d') or boolean on/off. */
|
|
29
|
+
physics: z.ZodOptional<z.ZodUnion<[z.ZodEnum<["3d", "2d", "rapier-3d", "rapier-2d"]>, z.ZodBoolean]>>;
|
|
30
|
+
/** Optional flag requesting pointer-lock (FPS-style mouse capture) at play time. */
|
|
31
|
+
pointerLock: z.ZodOptional<z.ZodBoolean>;
|
|
32
|
+
/** Optional input scheme hint consumed by play-runtime (e.g. "fps"). */
|
|
33
|
+
input: z.ZodOptional<z.ZodString>;
|
|
34
|
+
/** Optional editor preview config (skin scene/clips for the launcher card). */
|
|
35
|
+
preview: z.ZodOptional<z.ZodObject<{
|
|
36
|
+
skin: z.ZodObject<{
|
|
37
|
+
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
38
|
+
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
39
|
+
clipDefault: z.ZodOptional<z.ZodString>;
|
|
40
|
+
scale: z.ZodOptional<z.ZodNumber>;
|
|
41
|
+
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
42
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
43
|
+
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
44
|
+
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
45
|
+
clipDefault: z.ZodOptional<z.ZodString>;
|
|
46
|
+
scale: z.ZodOptional<z.ZodNumber>;
|
|
47
|
+
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
48
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
49
|
+
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
50
|
+
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
51
|
+
clipDefault: z.ZodOptional<z.ZodString>;
|
|
52
|
+
scale: z.ZodOptional<z.ZodNumber>;
|
|
53
|
+
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
54
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
55
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
56
|
+
skin: z.ZodObject<{
|
|
57
|
+
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
58
|
+
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
59
|
+
clipDefault: z.ZodOptional<z.ZodString>;
|
|
60
|
+
scale: z.ZodOptional<z.ZodNumber>;
|
|
61
|
+
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
62
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
63
|
+
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
64
|
+
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
65
|
+
clipDefault: z.ZodOptional<z.ZodString>;
|
|
66
|
+
scale: z.ZodOptional<z.ZodNumber>;
|
|
67
|
+
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
68
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
69
|
+
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
70
|
+
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
71
|
+
clipDefault: z.ZodOptional<z.ZodString>;
|
|
72
|
+
scale: z.ZodOptional<z.ZodNumber>;
|
|
73
|
+
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
74
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
75
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
76
|
+
skin: z.ZodObject<{
|
|
77
|
+
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
78
|
+
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
79
|
+
clipDefault: z.ZodOptional<z.ZodString>;
|
|
80
|
+
scale: z.ZodOptional<z.ZodNumber>;
|
|
81
|
+
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
82
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
83
|
+
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
84
|
+
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
85
|
+
clipDefault: z.ZodOptional<z.ZodString>;
|
|
86
|
+
scale: z.ZodOptional<z.ZodNumber>;
|
|
87
|
+
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
88
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
89
|
+
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
90
|
+
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
91
|
+
clipDefault: z.ZodOptional<z.ZodString>;
|
|
92
|
+
scale: z.ZodOptional<z.ZodNumber>;
|
|
93
|
+
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
94
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
95
|
+
}, z.ZodTypeAny, "passthrough">>>;
|
|
96
|
+
/** Native DeepSeek Harness Entry tree, plus the ForgeaX build-only realm selector. */
|
|
97
|
+
plugins: z.ZodOptional<z.ZodEffects<z.ZodArray<z.ZodType<GameProjectPluginEntry, z.ZodTypeDef, GameProjectPluginEntry>, "many">, GameProjectPluginEntry[], GameProjectPluginEntry[]>>;
|
|
98
|
+
/** Optional Digital Life NPC Brain policy consumed by the server adapter. */
|
|
99
|
+
npc: z.ZodOptional<z.ZodObject<{
|
|
100
|
+
model: z.ZodOptional<z.ZodString>;
|
|
101
|
+
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
102
|
+
budget: z.ZodOptional<z.ZodObject<{
|
|
103
|
+
maxCallsPerMinute: z.ZodOptional<z.ZodNumber>;
|
|
104
|
+
maxTokensPerMinute: z.ZodOptional<z.ZodNumber>;
|
|
105
|
+
maxConcurrent: z.ZodOptional<z.ZodNumber>;
|
|
106
|
+
}, "strict", z.ZodTypeAny, {
|
|
107
|
+
maxCallsPerMinute?: number | undefined;
|
|
108
|
+
maxTokensPerMinute?: number | undefined;
|
|
109
|
+
maxConcurrent?: number | undefined;
|
|
110
|
+
}, {
|
|
111
|
+
maxCallsPerMinute?: number | undefined;
|
|
112
|
+
maxTokensPerMinute?: number | undefined;
|
|
113
|
+
maxConcurrent?: number | undefined;
|
|
114
|
+
}>>;
|
|
115
|
+
}, "strict", z.ZodTypeAny, {
|
|
116
|
+
model?: string | undefined;
|
|
117
|
+
maxTokens?: number | undefined;
|
|
118
|
+
budget?: {
|
|
119
|
+
maxCallsPerMinute?: number | undefined;
|
|
120
|
+
maxTokensPerMinute?: number | undefined;
|
|
121
|
+
maxConcurrent?: number | undefined;
|
|
122
|
+
} | undefined;
|
|
123
|
+
}, {
|
|
124
|
+
model?: string | undefined;
|
|
125
|
+
maxTokens?: number | undefined;
|
|
126
|
+
budget?: {
|
|
127
|
+
maxCallsPerMinute?: number | undefined;
|
|
128
|
+
maxTokensPerMinute?: number | undefined;
|
|
129
|
+
maxConcurrent?: number | undefined;
|
|
130
|
+
} | undefined;
|
|
131
|
+
}>>;
|
|
132
|
+
}, "strict", z.ZodTypeAny, {
|
|
133
|
+
id: string;
|
|
134
|
+
name: string;
|
|
135
|
+
schemaVersion: string;
|
|
136
|
+
entry?: string | undefined;
|
|
137
|
+
executionEntry?: string | undefined;
|
|
138
|
+
defaultScene?: string | undefined;
|
|
139
|
+
physics?: boolean | "3d" | "2d" | "rapier-3d" | "rapier-2d" | undefined;
|
|
140
|
+
pointerLock?: boolean | undefined;
|
|
141
|
+
input?: string | undefined;
|
|
142
|
+
preview?: z.objectOutputType<{
|
|
143
|
+
skin: z.ZodObject<{
|
|
144
|
+
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
145
|
+
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
146
|
+
clipDefault: z.ZodOptional<z.ZodString>;
|
|
147
|
+
scale: z.ZodOptional<z.ZodNumber>;
|
|
148
|
+
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
149
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
150
|
+
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
151
|
+
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
152
|
+
clipDefault: z.ZodOptional<z.ZodString>;
|
|
153
|
+
scale: z.ZodOptional<z.ZodNumber>;
|
|
154
|
+
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
155
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
156
|
+
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
157
|
+
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
158
|
+
clipDefault: z.ZodOptional<z.ZodString>;
|
|
159
|
+
scale: z.ZodOptional<z.ZodNumber>;
|
|
160
|
+
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
161
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
162
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
163
|
+
plugins?: GameProjectPluginEntry[] | undefined;
|
|
164
|
+
npc?: {
|
|
165
|
+
model?: string | undefined;
|
|
166
|
+
maxTokens?: number | undefined;
|
|
167
|
+
budget?: {
|
|
168
|
+
maxCallsPerMinute?: number | undefined;
|
|
169
|
+
maxTokensPerMinute?: number | undefined;
|
|
170
|
+
maxConcurrent?: number | undefined;
|
|
171
|
+
} | undefined;
|
|
172
|
+
} | undefined;
|
|
173
|
+
}, {
|
|
174
|
+
id: string;
|
|
175
|
+
name: string;
|
|
176
|
+
schemaVersion: string;
|
|
177
|
+
entry?: string | undefined;
|
|
178
|
+
executionEntry?: string | undefined;
|
|
179
|
+
defaultScene?: string | undefined;
|
|
180
|
+
physics?: boolean | "3d" | "2d" | "rapier-3d" | "rapier-2d" | undefined;
|
|
181
|
+
pointerLock?: boolean | undefined;
|
|
182
|
+
input?: string | undefined;
|
|
183
|
+
preview?: z.objectInputType<{
|
|
184
|
+
skin: z.ZodObject<{
|
|
185
|
+
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
186
|
+
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
187
|
+
clipDefault: z.ZodOptional<z.ZodString>;
|
|
188
|
+
scale: z.ZodOptional<z.ZodNumber>;
|
|
189
|
+
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
190
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
191
|
+
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
192
|
+
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
193
|
+
clipDefault: z.ZodOptional<z.ZodString>;
|
|
194
|
+
scale: z.ZodOptional<z.ZodNumber>;
|
|
195
|
+
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
196
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
197
|
+
sceneGuid: z.ZodOptional<z.ZodString>;
|
|
198
|
+
clipGuids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
199
|
+
clipDefault: z.ZodOptional<z.ZodString>;
|
|
200
|
+
scale: z.ZodOptional<z.ZodNumber>;
|
|
201
|
+
pos: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
202
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
203
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
204
|
+
plugins?: GameProjectPluginEntry[] | undefined;
|
|
205
|
+
npc?: {
|
|
206
|
+
model?: string | undefined;
|
|
207
|
+
maxTokens?: number | undefined;
|
|
208
|
+
budget?: {
|
|
209
|
+
maxCallsPerMinute?: number | undefined;
|
|
210
|
+
maxTokensPerMinute?: number | undefined;
|
|
211
|
+
maxConcurrent?: number | undefined;
|
|
212
|
+
} | undefined;
|
|
213
|
+
} | undefined;
|
|
214
|
+
}>;
|
|
215
|
+
export type GameProject = z.infer<typeof GameProjectSchema>;
|
|
216
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,UAAU,2CAOtB,CAAC;AAqCF,eAAO,MAAM,iBAAiB,wCAAsC,CAAC;AAerE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;IAC3F,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,GAAG,SAAS,CAAC;CAChE;AAED,eAAO,MAAM,4BAA4B,EAAE,CAAC,CAAC,OAAO,CAAC,sBAAsB,CAW1E,CAAC;AAEF,eAAO,MAAM,wBAAwB,+JAiBjC,CAAC;AAKL,eAAO,MAAM,iBAAiB;IAE1B,2EAA2E;;IAE3E,2DAA2D;;IAE3D,wFAAwF;;IAExF,mGAAmG;;IAEnG,iFAAiF;;IAEjF,wGAAwG;;IAExG,gGAAgG;;IAEhG,oFAAoF;;IAEpF,wEAAwE;;IAExE,+EAA+E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAE/E,sFAAsF;;IAEtF,6EAA6E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGtE,CAAC;AAGZ,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@forgeax/engine-project",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"description": "Game-project SSOT — zod schema + injectable loader + resolve layer for forge.json, the authoritative game manifest contract.",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"main": "./dist/index.mjs",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@forgeax/engine-pack": "0.1.2",
|
|
26
|
+
"zod": "^3.25.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^20.14.0",
|
|
30
|
+
"tsup": "^8.3.5",
|
|
31
|
+
"typescript": "^5.8.3",
|
|
32
|
+
"vitest": "4.1.5"
|
|
33
|
+
},
|
|
34
|
+
"forgeax": {
|
|
35
|
+
"metrics": {
|
|
36
|
+
"bundle-size": {
|
|
37
|
+
"enabled": true,
|
|
38
|
+
"path": "dist/index.mjs",
|
|
39
|
+
"compression": "gzip"
|
|
40
|
+
},
|
|
41
|
+
"fps": {
|
|
42
|
+
"enabled": false,
|
|
43
|
+
"reason": "pure data/contract package; no runtime canvas or frame loop"
|
|
44
|
+
},
|
|
45
|
+
"bench": {
|
|
46
|
+
"enabled": false,
|
|
47
|
+
"reason": "no perf-critical hot path in contract layer; throughput not instrumented"
|
|
48
|
+
},
|
|
49
|
+
"gate": {
|
|
50
|
+
"enabled": false,
|
|
51
|
+
"reason": "package-level gate scripts declared in engine CI yaml directly; no standalone binary gate"
|
|
52
|
+
},
|
|
53
|
+
"spike-report": {
|
|
54
|
+
"enabled": false,
|
|
55
|
+
"reason": "not a spike package"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsup",
|
|
61
|
+
"typecheck": "tsc -b",
|
|
62
|
+
"test": "vitest run",
|
|
63
|
+
"test:watch": "vitest"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// ac14-migration.test.ts — w23: AC-14 integration — all 6 migrated forge.json pass loadGameProject
|
|
2
|
+
//
|
|
3
|
+
// Loads each migrated game's forge.json via node:fs readFileSync adapter,
|
|
4
|
+
// asserts r.ok===true, verifies schemaVersion/defaultScene/entry per D-3 GUID table.
|
|
5
|
+
// This is the R-3 risk closure evidence (plan-strategy section 4 R-3, section 5.4).
|
|
6
|
+
//
|
|
7
|
+
// Fixture approach: 6 migrated forge.json copies live under __tests__/fixtures/games/
|
|
8
|
+
// (plan w23 description secondary path: the live paths under packages/games/ use a
|
|
9
|
+
// Chinese directory name that triggers the engine english-only gate; fixtures avoid
|
|
10
|
+
// the CJK path literal while testing the same post-migration forge.json content).
|
|
11
|
+
|
|
12
|
+
import { readFileSync } from 'node:fs';
|
|
13
|
+
import { dirname, resolve } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { describe, expect, it } from 'vitest';
|
|
16
|
+
import { loadGameProject } from '../loader.js';
|
|
17
|
+
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = dirname(__filename);
|
|
20
|
+
const fixturesDir = resolve(__dirname, 'fixtures/games');
|
|
21
|
+
|
|
22
|
+
function fsRead(filePath: string): (path: string) => Promise<string> {
|
|
23
|
+
return async () => readFileSync(filePath, 'utf-8');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface GameFixture {
|
|
27
|
+
name: string;
|
|
28
|
+
dir: string;
|
|
29
|
+
hasDefaultScene: boolean;
|
|
30
|
+
expectedDefaultScene?: string;
|
|
31
|
+
expectedEntry?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 6 migrated games per D-3 GUID table (plan-strategy section 2)
|
|
35
|
+
// Fixture dirs use romanized names (original Chinese dir -> cow-level)
|
|
36
|
+
const GAMES: GameFixture[] = [
|
|
37
|
+
{
|
|
38
|
+
name: 'hellforge',
|
|
39
|
+
dir: 'hellforge',
|
|
40
|
+
hasDefaultScene: true,
|
|
41
|
+
expectedDefaultScene: '15acc839-d847-527c-8284-bfb36d7c50de',
|
|
42
|
+
expectedEntry: 'main.ts',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'cow-survivor',
|
|
46
|
+
dir: 'cow-survivor',
|
|
47
|
+
hasDefaultScene: true,
|
|
48
|
+
expectedDefaultScene: '7b4d43d4-5b19-5903-8966-f89671d21565',
|
|
49
|
+
expectedEntry: 'main.ts',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'spin-cube',
|
|
53
|
+
dir: 'spin-cube',
|
|
54
|
+
hasDefaultScene: false,
|
|
55
|
+
expectedEntry: 'main.ts',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'cow-level',
|
|
59
|
+
dir: 'cow-level',
|
|
60
|
+
hasDefaultScene: false,
|
|
61
|
+
expectedEntry: 'main.ts',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'fps',
|
|
65
|
+
dir: 'fps',
|
|
66
|
+
hasDefaultScene: false,
|
|
67
|
+
expectedEntry: 'main.ts',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'shoot-opt',
|
|
71
|
+
dir: 'shoot-opt',
|
|
72
|
+
hasDefaultScene: false,
|
|
73
|
+
expectedEntry: 'src/main.ts',
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
describe('AC-14 — migrated game forge.json validation', () => {
|
|
78
|
+
for (const game of GAMES) {
|
|
79
|
+
const forgePath = resolve(fixturesDir, game.dir, 'forge.json');
|
|
80
|
+
|
|
81
|
+
describe(game.name, () => {
|
|
82
|
+
it('loads successfully (r.ok === true)', async () => {
|
|
83
|
+
const result = await loadGameProject(fsRead(forgePath));
|
|
84
|
+
expect(result.ok).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('has schemaVersion === "1.0.0"', async () => {
|
|
88
|
+
const result = await loadGameProject(fsRead(forgePath));
|
|
89
|
+
expect(result.ok).toBe(true);
|
|
90
|
+
if (result.ok) {
|
|
91
|
+
expect(result.value.schemaVersion).toBe('1.0.0');
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (game.hasDefaultScene) {
|
|
96
|
+
it(`has defaultScene === "${game.expectedDefaultScene}"`, async () => {
|
|
97
|
+
const result = await loadGameProject(fsRead(forgePath));
|
|
98
|
+
expect(result.ok).toBe(true);
|
|
99
|
+
if (result.ok) {
|
|
100
|
+
expect(result.value.defaultScene).toBe(game.expectedDefaultScene);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
} else {
|
|
104
|
+
it('has no defaultScene (undefined)', async () => {
|
|
105
|
+
const result = await loadGameProject(fsRead(forgePath));
|
|
106
|
+
expect(result.ok).toBe(true);
|
|
107
|
+
if (result.ok) {
|
|
108
|
+
expect(result.value.defaultScene).toBeUndefined();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (game.expectedEntry) {
|
|
114
|
+
it(`has entry === "${game.expectedEntry}"`, async () => {
|
|
115
|
+
const result = await loadGameProject(fsRead(forgePath));
|
|
116
|
+
expect(result.ok).toBe(true);
|
|
117
|
+
if (result.ok) {
|
|
118
|
+
expect(result.value.entry).toBe(game.expectedEntry);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { GameProjectError as GameProjectErrorType } from '../index.js';
|
|
2
|
+
import { GameProjectError } from '../index.js';
|
|
3
|
+
|
|
4
|
+
const positiveErrors: GameProjectErrorType[] = [
|
|
5
|
+
new GameProjectError({
|
|
6
|
+
code: 'forge-missing',
|
|
7
|
+
expected: 'forge.json to exist',
|
|
8
|
+
hint: 'create forge.json',
|
|
9
|
+
detail: { path: 'forge.json' },
|
|
10
|
+
}),
|
|
11
|
+
new GameProjectError({
|
|
12
|
+
code: 'forge-read-failed',
|
|
13
|
+
expected: 'forge.json to be readable',
|
|
14
|
+
hint: 'retry the project reader',
|
|
15
|
+
detail: { path: 'forge.json', cause: 'transient' },
|
|
16
|
+
}),
|
|
17
|
+
new GameProjectError({
|
|
18
|
+
code: 'forge-parse-failed',
|
|
19
|
+
expected: 'valid JSON',
|
|
20
|
+
hint: 'fix JSON',
|
|
21
|
+
detail: { path: 'forge.json', rawMessage: 'Unexpected token' },
|
|
22
|
+
}),
|
|
23
|
+
new GameProjectError({
|
|
24
|
+
code: 'forge-schema-invalid',
|
|
25
|
+
expected: 'schema-valid JSON',
|
|
26
|
+
hint: 'fix fields',
|
|
27
|
+
detail: { path: 'forge.json', zodErrors: [] },
|
|
28
|
+
}),
|
|
29
|
+
new GameProjectError({
|
|
30
|
+
code: 'forge-unknown-field',
|
|
31
|
+
expected: 'known fields',
|
|
32
|
+
hint: 'remove the field',
|
|
33
|
+
detail: { path: 'forge.json', fieldNames: ['scenes'] },
|
|
34
|
+
}),
|
|
35
|
+
new GameProjectError({
|
|
36
|
+
code: 'forge-guid-malformed',
|
|
37
|
+
expected: 'a valid GUID',
|
|
38
|
+
hint: 'use a scene GUID',
|
|
39
|
+
detail: { field: 'defaultScene', rawInput: 'not-a-guid' },
|
|
40
|
+
}),
|
|
41
|
+
new GameProjectError({
|
|
42
|
+
code: 'forge-scene-unresolved',
|
|
43
|
+
expected: 'a resolvable scene',
|
|
44
|
+
hint: 'check the scene pack',
|
|
45
|
+
detail: { guid: '15acc839-d847-527c-8284-bfb36d7c50de' },
|
|
46
|
+
}),
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
new GameProjectError({
|
|
50
|
+
code: 'forge-guid-malformed',
|
|
51
|
+
expected: 'a valid GUID',
|
|
52
|
+
hint: 'use a scene GUID',
|
|
53
|
+
// @ts-expect-error code/detail pairs remain correlated.
|
|
54
|
+
detail: { path: 'forge.json' },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
function describe(error: GameProjectErrorType): string {
|
|
58
|
+
switch (error.code) {
|
|
59
|
+
case 'forge-missing':
|
|
60
|
+
return error.detail.path;
|
|
61
|
+
case 'forge-read-failed':
|
|
62
|
+
return error.detail.cause;
|
|
63
|
+
case 'forge-parse-failed':
|
|
64
|
+
return error.detail.rawMessage;
|
|
65
|
+
case 'forge-schema-invalid':
|
|
66
|
+
return String(error.detail.zodErrors.length);
|
|
67
|
+
case 'forge-unknown-field':
|
|
68
|
+
return error.detail.fieldNames.join(',');
|
|
69
|
+
case 'forge-guid-malformed':
|
|
70
|
+
return error.detail.rawInput;
|
|
71
|
+
case 'forge-scene-unresolved':
|
|
72
|
+
return error.detail.guid;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
void positiveErrors;
|
|
77
|
+
void describe;
|