miaoda-game-devkit 0.10.0 → 0.10.1

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.
@@ -1,8 +1,92 @@
1
1
  // src/lint/setup.ts
2
2
  import { afterEach } from "vitest";
3
3
 
4
- // src/testing/jsdom-canvas.ts
4
+ // src/testing/jsdom-webgl.ts
5
+ var CONSTANT_NAME = /^[A-Z][A-Z0-9_]*$/;
6
+ var TEXTURE_UNIT_LIMIT = 32;
7
+ var TEXTURE_SIZE_LIMIT = 4096;
8
+ var VIEWPORT_LIMIT = 4096;
9
+ function parameterValue(name) {
10
+ if (name === "VERSION") return "WebGL 2.0 (miaoda-game-devkit stub)";
11
+ if (name === "SHADING_LANGUAGE_VERSION") return "WebGL GLSL ES 3.00 (stub)";
12
+ if (name === "VENDOR" || name === "RENDERER") return "miaoda-game-devkit";
13
+ if (name === "MAX_VIEWPORT_DIMS") {
14
+ return new Int32Array([VIEWPORT_LIMIT, VIEWPORT_LIMIT]);
15
+ }
16
+ if (name === "VIEWPORT" || name === "SCISSOR_BOX") {
17
+ return new Int32Array([0, 0, VIEWPORT_LIMIT, VIEWPORT_LIMIT]);
18
+ }
19
+ if (name.includes("MAX_") && name.includes("SIZE")) return TEXTURE_SIZE_LIMIT;
20
+ if (name.startsWith("MAX_")) return TEXTURE_UNIT_LIMIT;
21
+ if (name.endsWith("_BITS")) return 8;
22
+ return 0;
23
+ }
24
+ function createWebGLContext(canvas, attributes) {
25
+ const constants = /* @__PURE__ */ new Map();
26
+ const constantNames = /* @__PURE__ */ new Map();
27
+ const state = {
28
+ canvas,
29
+ drawingBufferWidth: canvas.width,
30
+ drawingBufferHeight: canvas.height
31
+ };
32
+ const method = (name) => {
33
+ if (name === "getContextAttributes") return () => attributes;
34
+ if (name === "getExtension") return () => null;
35
+ if (name === "getSupportedExtensions") return () => [];
36
+ if (name === "getParameter") {
37
+ return (pname) => parameterValue(constantNames.get(pname) ?? "UNKNOWN");
38
+ }
39
+ if (name === "getShaderPrecisionFormat") {
40
+ return () => ({ rangeMin: 127, rangeMax: 127, precision: 23 });
41
+ }
42
+ if (name === "getShaderInfoLog" || name === "getProgramInfoLog") return () => "";
43
+ if (name === "getError") return () => 0;
44
+ if (name === "isContextLost") return () => false;
45
+ if (name.startsWith("get") && name.endsWith("Parameter")) return () => true;
46
+ if (name.startsWith("get")) return () => null;
47
+ if (name.startsWith("create") || name.startsWith("getUniformLocation")) {
48
+ return () => ({});
49
+ }
50
+ return () => void 0;
51
+ };
52
+ return new Proxy(state, {
53
+ get(target, property) {
54
+ if (property in target) return target[property];
55
+ if (typeof property !== "string") return void 0;
56
+ if (CONSTANT_NAME.test(property)) {
57
+ const existing = constants.get(property);
58
+ if (existing !== void 0) return existing;
59
+ const value = constants.size + 1;
60
+ constants.set(property, value);
61
+ constantNames.set(value, property);
62
+ return value;
63
+ }
64
+ const implementation = method(property);
65
+ target[property] = implementation;
66
+ return implementation;
67
+ }
68
+ });
69
+ }
5
70
  var contexts = /* @__PURE__ */ new WeakMap();
71
+ function getJSDOMWebGLContext(canvas, options) {
72
+ const existing = contexts.get(canvas);
73
+ if (existing) return existing;
74
+ const attributes = {
75
+ alpha: true,
76
+ antialias: false,
77
+ depth: true,
78
+ premultipliedAlpha: true,
79
+ preserveDrawingBuffer: false,
80
+ stencil: false,
81
+ ...typeof options === "object" && options !== null ? options : {}
82
+ };
83
+ const context = createWebGLContext(canvas, attributes);
84
+ contexts.set(canvas, context);
85
+ return context;
86
+ }
87
+
88
+ // src/testing/jsdom-canvas.ts
89
+ var contexts2 = /* @__PURE__ */ new WeakMap();
6
90
  function createCanvasContext(canvas) {
7
91
  const imageData = (width = 1, height = 1) => ({
8
92
  data: new Uint8ClampedArray(width * height * 4),
@@ -49,12 +133,15 @@ function createCanvasContext(canvas) {
49
133
  return context;
50
134
  }
51
135
  function installJSDOMCanvasContext() {
52
- HTMLCanvasElement.prototype.getContext = function getContext(contextId) {
136
+ HTMLCanvasElement.prototype.getContext = function getContext(contextId, options) {
137
+ if (contextId === "webgl" || contextId === "webgl2") {
138
+ return getJSDOMWebGLContext(this, options);
139
+ }
53
140
  if (contextId !== "2d") return null;
54
- const existing = contexts.get(this);
141
+ const existing = contexts2.get(this);
55
142
  if (existing) return existing;
56
143
  const context = createCanvasContext(this);
57
- contexts.set(this, context);
144
+ contexts2.set(this, context);
58
145
  return context;
59
146
  };
60
147
  }
@@ -1452,7 +1452,36 @@ function resolvePhaser3BrowserEntry(projectRoot) {
1452
1452
  return void 0;
1453
1453
  }
1454
1454
  }
1455
+ var COVERAGE_INCLUDE = [
1456
+ "src/game/core/**/*.{ts,tsx}",
1457
+ "src/game/runtime/**/*.{ts,tsx}"
1458
+ ];
1459
+ var COVERAGE_EXCLUDE = [
1460
+ "src/game/{core,runtime}/**/*.d.ts",
1461
+ "src/game/{core,runtime}/**/*.{spec,test}.{ts,tsx}"
1462
+ ];
1463
+ var GATE_SCAN_EXCLUDE = [
1464
+ "src/**/*.d.ts",
1465
+ "src/**/*.{spec,test}.{ts,tsx}",
1466
+ "src/game/example/**"
1467
+ ];
1468
+ function assertCoverageScopeIsPopulated(projectRoot) {
1469
+ const scan = (pattern) => (0, import_node_fs3.globSync)(pattern, { cwd: projectRoot, exclude: GATE_SCAN_EXCLUDE });
1470
+ if (COVERAGE_INCLUDE.flatMap(scan).length > 0) return;
1471
+ const misplaced = scan("src/**/{core,runtime}/**/*.{ts,tsx}");
1472
+ if (misplaced.length === 0) return;
1473
+ throw new Error(
1474
+ [
1475
+ "The coverage gate matched no source file, so it proves nothing.",
1476
+ "Deterministic rules belong in src/game/core/; the loop, scheduling,",
1477
+ "Controller, and Telemetry belong in src/game/runtime/.",
1478
+ "Move these files under those two directories:",
1479
+ ...misplaced.slice(0, 10).map((file) => ` ${file}`)
1480
+ ].join("\n")
1481
+ );
1482
+ }
1455
1483
  function defineReactGameVitestConfig(options) {
1484
+ assertCoverageScopeIsPopulated(options.projectRoot);
1456
1485
  const phaser3BrowserEntry = resolvePhaser3BrowserEntry(options.projectRoot);
1457
1486
  return (0, import_config.defineConfig)({
1458
1487
  // Keep discovery and dependency resolution anchored to the generated app even
@@ -1495,14 +1524,8 @@ function defineReactGameVitestConfig(options) {
1495
1524
  clearMocks: true,
1496
1525
  coverage: {
1497
1526
  provider: "v8",
1498
- include: [
1499
- "src/game/core/**/*.{ts,tsx}",
1500
- "src/game/runtime/**/*.{ts,tsx}"
1501
- ],
1502
- exclude: [
1503
- "src/game/{core,runtime}/**/*.d.ts",
1504
- "src/game/{core,runtime}/**/*.{spec,test}.{ts,tsx}"
1505
- ],
1527
+ include: COVERAGE_INCLUDE,
1528
+ exclude: COVERAGE_EXCLUDE,
1506
1529
  reporter: ["text-summary"],
1507
1530
  thresholds: {
1508
1531
  perFile: true,
@@ -1,5 +1,5 @@
1
1
  // src/react-vitest-config.ts
2
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
2
+ import { existsSync as existsSync3, globSync, readFileSync as readFileSync2 } from "fs";
3
3
  import { dirname as dirname2, resolve as resolve3 } from "path";
4
4
  import { defineConfig } from "vitest/config";
5
5
 
@@ -1418,7 +1418,36 @@ function resolvePhaser3BrowserEntry(projectRoot) {
1418
1418
  return void 0;
1419
1419
  }
1420
1420
  }
1421
+ var COVERAGE_INCLUDE = [
1422
+ "src/game/core/**/*.{ts,tsx}",
1423
+ "src/game/runtime/**/*.{ts,tsx}"
1424
+ ];
1425
+ var COVERAGE_EXCLUDE = [
1426
+ "src/game/{core,runtime}/**/*.d.ts",
1427
+ "src/game/{core,runtime}/**/*.{spec,test}.{ts,tsx}"
1428
+ ];
1429
+ var GATE_SCAN_EXCLUDE = [
1430
+ "src/**/*.d.ts",
1431
+ "src/**/*.{spec,test}.{ts,tsx}",
1432
+ "src/game/example/**"
1433
+ ];
1434
+ function assertCoverageScopeIsPopulated(projectRoot) {
1435
+ const scan = (pattern) => globSync(pattern, { cwd: projectRoot, exclude: GATE_SCAN_EXCLUDE });
1436
+ if (COVERAGE_INCLUDE.flatMap(scan).length > 0) return;
1437
+ const misplaced = scan("src/**/{core,runtime}/**/*.{ts,tsx}");
1438
+ if (misplaced.length === 0) return;
1439
+ throw new Error(
1440
+ [
1441
+ "The coverage gate matched no source file, so it proves nothing.",
1442
+ "Deterministic rules belong in src/game/core/; the loop, scheduling,",
1443
+ "Controller, and Telemetry belong in src/game/runtime/.",
1444
+ "Move these files under those two directories:",
1445
+ ...misplaced.slice(0, 10).map((file) => ` ${file}`)
1446
+ ].join("\n")
1447
+ );
1448
+ }
1421
1449
  function defineReactGameVitestConfig(options) {
1450
+ assertCoverageScopeIsPopulated(options.projectRoot);
1422
1451
  const phaser3BrowserEntry = resolvePhaser3BrowserEntry(options.projectRoot);
1423
1452
  return defineConfig({
1424
1453
  // Keep discovery and dependency resolution anchored to the generated app even
@@ -1461,14 +1490,8 @@ function defineReactGameVitestConfig(options) {
1461
1490
  clearMocks: true,
1462
1491
  coverage: {
1463
1492
  provider: "v8",
1464
- include: [
1465
- "src/game/core/**/*.{ts,tsx}",
1466
- "src/game/runtime/**/*.{ts,tsx}"
1467
- ],
1468
- exclude: [
1469
- "src/game/{core,runtime}/**/*.d.ts",
1470
- "src/game/{core,runtime}/**/*.{spec,test}.{ts,tsx}"
1471
- ],
1493
+ include: COVERAGE_INCLUDE,
1494
+ exclude: COVERAGE_EXCLUDE,
1472
1495
  reporter: ["text-summary"],
1473
1496
  thresholds: {
1474
1497
  perFile: true,
@@ -184,8 +184,92 @@ function runtimeFailureError(code, value) {
184
184
  return error;
185
185
  }
186
186
 
187
- // src/testing/jsdom-canvas.ts
187
+ // src/testing/jsdom-webgl.ts
188
+ var CONSTANT_NAME = /^[A-Z][A-Z0-9_]*$/;
189
+ var TEXTURE_UNIT_LIMIT = 32;
190
+ var TEXTURE_SIZE_LIMIT = 4096;
191
+ var VIEWPORT_LIMIT = 4096;
192
+ function parameterValue(name) {
193
+ if (name === "VERSION") return "WebGL 2.0 (miaoda-game-devkit stub)";
194
+ if (name === "SHADING_LANGUAGE_VERSION") return "WebGL GLSL ES 3.00 (stub)";
195
+ if (name === "VENDOR" || name === "RENDERER") return "miaoda-game-devkit";
196
+ if (name === "MAX_VIEWPORT_DIMS") {
197
+ return new Int32Array([VIEWPORT_LIMIT, VIEWPORT_LIMIT]);
198
+ }
199
+ if (name === "VIEWPORT" || name === "SCISSOR_BOX") {
200
+ return new Int32Array([0, 0, VIEWPORT_LIMIT, VIEWPORT_LIMIT]);
201
+ }
202
+ if (name.includes("MAX_") && name.includes("SIZE")) return TEXTURE_SIZE_LIMIT;
203
+ if (name.startsWith("MAX_")) return TEXTURE_UNIT_LIMIT;
204
+ if (name.endsWith("_BITS")) return 8;
205
+ return 0;
206
+ }
207
+ function createWebGLContext(canvas, attributes) {
208
+ const constants = /* @__PURE__ */ new Map();
209
+ const constantNames = /* @__PURE__ */ new Map();
210
+ const state = {
211
+ canvas,
212
+ drawingBufferWidth: canvas.width,
213
+ drawingBufferHeight: canvas.height
214
+ };
215
+ const method = (name) => {
216
+ if (name === "getContextAttributes") return () => attributes;
217
+ if (name === "getExtension") return () => null;
218
+ if (name === "getSupportedExtensions") return () => [];
219
+ if (name === "getParameter") {
220
+ return (pname) => parameterValue(constantNames.get(pname) ?? "UNKNOWN");
221
+ }
222
+ if (name === "getShaderPrecisionFormat") {
223
+ return () => ({ rangeMin: 127, rangeMax: 127, precision: 23 });
224
+ }
225
+ if (name === "getShaderInfoLog" || name === "getProgramInfoLog") return () => "";
226
+ if (name === "getError") return () => 0;
227
+ if (name === "isContextLost") return () => false;
228
+ if (name.startsWith("get") && name.endsWith("Parameter")) return () => true;
229
+ if (name.startsWith("get")) return () => null;
230
+ if (name.startsWith("create") || name.startsWith("getUniformLocation")) {
231
+ return () => ({});
232
+ }
233
+ return () => void 0;
234
+ };
235
+ return new Proxy(state, {
236
+ get(target, property) {
237
+ if (property in target) return target[property];
238
+ if (typeof property !== "string") return void 0;
239
+ if (CONSTANT_NAME.test(property)) {
240
+ const existing = constants.get(property);
241
+ if (existing !== void 0) return existing;
242
+ const value = constants.size + 1;
243
+ constants.set(property, value);
244
+ constantNames.set(value, property);
245
+ return value;
246
+ }
247
+ const implementation = method(property);
248
+ target[property] = implementation;
249
+ return implementation;
250
+ }
251
+ });
252
+ }
188
253
  var contexts = /* @__PURE__ */ new WeakMap();
254
+ function getJSDOMWebGLContext(canvas, options) {
255
+ const existing = contexts.get(canvas);
256
+ if (existing) return existing;
257
+ const attributes = {
258
+ alpha: true,
259
+ antialias: false,
260
+ depth: true,
261
+ premultipliedAlpha: true,
262
+ preserveDrawingBuffer: false,
263
+ stencil: false,
264
+ ...typeof options === "object" && options !== null ? options : {}
265
+ };
266
+ const context = createWebGLContext(canvas, attributes);
267
+ contexts.set(canvas, context);
268
+ return context;
269
+ }
270
+
271
+ // src/testing/jsdom-canvas.ts
272
+ var contexts2 = /* @__PURE__ */ new WeakMap();
189
273
  function createCanvasContext(canvas) {
190
274
  const imageData = (width = 1, height = 1) => ({
191
275
  data: new Uint8ClampedArray(width * height * 4),
@@ -232,12 +316,15 @@ function createCanvasContext(canvas) {
232
316
  return context;
233
317
  }
234
318
  function installJSDOMCanvasContext() {
235
- HTMLCanvasElement.prototype.getContext = function getContext(contextId) {
319
+ HTMLCanvasElement.prototype.getContext = function getContext(contextId, options) {
320
+ if (contextId === "webgl" || contextId === "webgl2") {
321
+ return getJSDOMWebGLContext(this, options);
322
+ }
236
323
  if (contextId !== "2d") return null;
237
- const existing = contexts.get(this);
324
+ const existing = contexts2.get(this);
238
325
  if (existing) return existing;
239
326
  const context = createCanvasContext(this);
240
- contexts.set(this, context);
327
+ contexts2.set(this, context);
241
328
  return context;
242
329
  };
243
330
  }
@@ -182,8 +182,92 @@ function runtimeFailureError(code, value) {
182
182
  return error;
183
183
  }
184
184
 
185
- // src/testing/jsdom-canvas.ts
185
+ // src/testing/jsdom-webgl.ts
186
+ var CONSTANT_NAME = /^[A-Z][A-Z0-9_]*$/;
187
+ var TEXTURE_UNIT_LIMIT = 32;
188
+ var TEXTURE_SIZE_LIMIT = 4096;
189
+ var VIEWPORT_LIMIT = 4096;
190
+ function parameterValue(name) {
191
+ if (name === "VERSION") return "WebGL 2.0 (miaoda-game-devkit stub)";
192
+ if (name === "SHADING_LANGUAGE_VERSION") return "WebGL GLSL ES 3.00 (stub)";
193
+ if (name === "VENDOR" || name === "RENDERER") return "miaoda-game-devkit";
194
+ if (name === "MAX_VIEWPORT_DIMS") {
195
+ return new Int32Array([VIEWPORT_LIMIT, VIEWPORT_LIMIT]);
196
+ }
197
+ if (name === "VIEWPORT" || name === "SCISSOR_BOX") {
198
+ return new Int32Array([0, 0, VIEWPORT_LIMIT, VIEWPORT_LIMIT]);
199
+ }
200
+ if (name.includes("MAX_") && name.includes("SIZE")) return TEXTURE_SIZE_LIMIT;
201
+ if (name.startsWith("MAX_")) return TEXTURE_UNIT_LIMIT;
202
+ if (name.endsWith("_BITS")) return 8;
203
+ return 0;
204
+ }
205
+ function createWebGLContext(canvas, attributes) {
206
+ const constants = /* @__PURE__ */ new Map();
207
+ const constantNames = /* @__PURE__ */ new Map();
208
+ const state = {
209
+ canvas,
210
+ drawingBufferWidth: canvas.width,
211
+ drawingBufferHeight: canvas.height
212
+ };
213
+ const method = (name) => {
214
+ if (name === "getContextAttributes") return () => attributes;
215
+ if (name === "getExtension") return () => null;
216
+ if (name === "getSupportedExtensions") return () => [];
217
+ if (name === "getParameter") {
218
+ return (pname) => parameterValue(constantNames.get(pname) ?? "UNKNOWN");
219
+ }
220
+ if (name === "getShaderPrecisionFormat") {
221
+ return () => ({ rangeMin: 127, rangeMax: 127, precision: 23 });
222
+ }
223
+ if (name === "getShaderInfoLog" || name === "getProgramInfoLog") return () => "";
224
+ if (name === "getError") return () => 0;
225
+ if (name === "isContextLost") return () => false;
226
+ if (name.startsWith("get") && name.endsWith("Parameter")) return () => true;
227
+ if (name.startsWith("get")) return () => null;
228
+ if (name.startsWith("create") || name.startsWith("getUniformLocation")) {
229
+ return () => ({});
230
+ }
231
+ return () => void 0;
232
+ };
233
+ return new Proxy(state, {
234
+ get(target, property) {
235
+ if (property in target) return target[property];
236
+ if (typeof property !== "string") return void 0;
237
+ if (CONSTANT_NAME.test(property)) {
238
+ const existing = constants.get(property);
239
+ if (existing !== void 0) return existing;
240
+ const value = constants.size + 1;
241
+ constants.set(property, value);
242
+ constantNames.set(value, property);
243
+ return value;
244
+ }
245
+ const implementation = method(property);
246
+ target[property] = implementation;
247
+ return implementation;
248
+ }
249
+ });
250
+ }
186
251
  var contexts = /* @__PURE__ */ new WeakMap();
252
+ function getJSDOMWebGLContext(canvas, options) {
253
+ const existing = contexts.get(canvas);
254
+ if (existing) return existing;
255
+ const attributes = {
256
+ alpha: true,
257
+ antialias: false,
258
+ depth: true,
259
+ premultipliedAlpha: true,
260
+ preserveDrawingBuffer: false,
261
+ stencil: false,
262
+ ...typeof options === "object" && options !== null ? options : {}
263
+ };
264
+ const context = createWebGLContext(canvas, attributes);
265
+ contexts.set(canvas, context);
266
+ return context;
267
+ }
268
+
269
+ // src/testing/jsdom-canvas.ts
270
+ var contexts2 = /* @__PURE__ */ new WeakMap();
187
271
  function createCanvasContext(canvas) {
188
272
  const imageData = (width = 1, height = 1) => ({
189
273
  data: new Uint8ClampedArray(width * height * 4),
@@ -230,12 +314,15 @@ function createCanvasContext(canvas) {
230
314
  return context;
231
315
  }
232
316
  function installJSDOMCanvasContext() {
233
- HTMLCanvasElement.prototype.getContext = function getContext(contextId) {
317
+ HTMLCanvasElement.prototype.getContext = function getContext(contextId, options) {
318
+ if (contextId === "webgl" || contextId === "webgl2") {
319
+ return getJSDOMWebGLContext(this, options);
320
+ }
234
321
  if (contextId !== "2d") return null;
235
- const existing = contexts.get(this);
322
+ const existing = contexts2.get(this);
236
323
  if (existing) return existing;
237
324
  const context = createCanvasContext(this);
238
- contexts.set(this, context);
325
+ contexts2.set(this, context);
239
326
  return context;
240
327
  };
241
328
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miaoda-game-devkit",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Shared React and Phaser game lint plus deterministic testing tools for Miaoda games",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",