@downdraft/engine 0.4.2 → 0.4.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.
@@ -363,6 +363,10 @@ export interface GameModule<Sim extends GameSimWorker = GameSimWorker, R extends
363
363
  /** Declarative sim→renderer event handler map. Replaces the switch block.
364
364
  * Ignored for renderer-only games (no sim worker to emit events). */
365
365
  events?: SimEventMap<GameContext<Sim, R>>;
366
+ /** Sim event kinds consumed outside the declarative `events` map (e.g. a
367
+ * session layer subscribing via worker.onSimEvent directly). Listed kinds
368
+ * skip the dev "Unhandled sim event kind" warning. */
369
+ silentEvents?: readonly string[];
366
370
 
367
371
  // ── Save ──
368
372
  /** Save configuration. If omitted, no autosave is wired. */
@@ -576,7 +580,7 @@ export async function startGame<Sim extends GameSimWorker, R extends GameRendere
576
580
  // Event kinds emitted by the engine itself (sim-worker-base lifecycle)
577
581
  // that games are not required to handle — suppress the dev warning for
578
582
  // these so autosave doesn't spam the console when no handler is wired.
579
- const ENGINE_EMITTED_EVENT_KINDS = new Set(["ready", "saved", "loaded", "error"]);
583
+ const ENGINE_EMITTED_EVENT_KINDS = new Set(["ready", "saved", "loaded", "error", ...(module.silentEvents ?? [])]);
580
584
 
581
585
  // 4. Wire event routing from the declarative events map (sim-worker only).
582
586
  // Supports both GameSimWorker.onEvent(cb) and SimWorkerHost's
@@ -366,6 +366,10 @@ export function createStandardAutomationTools(ctx: StandardAutomationContext): M
366
366
  code: { type: "string", description: "Key code (e.g. 'KeyI', 'Tab', 'Escape')" },
367
367
  type: { type: "string", enum: ["keydown", "keyup"], default: "keydown" },
368
368
  repeat: { type: "boolean", default: false },
369
+ ctrlKey: { type: "boolean", default: false },
370
+ shiftKey: { type: "boolean", default: false },
371
+ altKey: { type: "boolean", default: false },
372
+ metaKey: { type: "boolean", default: false },
369
373
  },
370
374
  required: ["key"],
371
375
  },
@@ -377,6 +381,8 @@ export function createStandardAutomationTools(ctx: StandardAutomationContext): M
377
381
  const ev = new KeyboardEvent(type, {
378
382
  key, code, keyCode: domKeyCodeFor(key),
379
383
  bubbles: true, cancelable: true, repeat: !!params.repeat,
384
+ ctrlKey: !!params.ctrlKey, shiftKey: !!params.shiftKey,
385
+ altKey: !!params.altKey, metaKey: !!params.metaKey,
380
386
  } as KeyboardEventInit);
381
387
  // Real KeyboardEvent ignores keyCode in the init dict — force it.
382
388
  try { Object.defineProperty(ev, "keyCode", { value: domKeyCodeFor(key) }); } catch { /* polyfill already set it */ }
@@ -14,7 +14,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
14
14
  // embedded — package-native.mjs stages them at <execDir>/dd-assets/
15
15
  // <repo-rel>, so a $bunfs callerDir maps onto the staged tree. The keys
16
16
  // stay identical because both trees share the same relative structure.
17
- const BUNFS_ROOT_RE = /^\/\$bunfs\/root\/|^B:\\~BUN\\root\\/i;
17
+ const BUNFS_ROOT_RE = /^\/\$bunfs\/root\/?|^B:\\~BUN\\root\\?/i;
18
18
 
19
19
  function mapCallerDir(callerDir: string): string {
20
20
  if (!BUNFS_ROOT_RE.test(callerDir)) return callerDir;
@@ -6,6 +6,8 @@ export interface CanvasResizeHandler {
6
6
 
7
7
  export class CanvasResizeWatcher {
8
8
  private dpr = 1;
9
+ /** Explicit override via setDpr — 0 means "track the host's live ratio". */
10
+ private dprOverride = 0;
9
11
  private onSurfaceResize = () => this.resize();
10
12
 
11
13
  constructor(
@@ -19,11 +21,21 @@ export class CanvasResizeWatcher {
19
21
  }
20
22
 
21
23
  private resize(): void {
24
+ // Re-read devicePixelRatio on every resize — on the native host it's a
25
+ // live query (the current monitor's scale factor), so this self-heals
26
+ // when the window crosses between differently-scaled monitors. A cached
27
+ // ratio silently diverges from the surface's clientWidth (which divides
28
+ // the physical extent by the live factor), sizing the swapchain to the
29
+ // wrong extent — wgpu then reports Outdated on every acquire forever.
30
+ const live = (typeof window !== "undefined" ? window.devicePixelRatio : 0) || 0;
31
+ this.dpr = this.dprOverride > 0 ? this.dprOverride : (live > 0 ? live : this.dpr);
22
32
  this.handler.onResize(this.canvas.clientWidth, this.canvas.clientHeight, this.dpr);
23
33
  }
24
34
 
35
+ /** Pin the ratio explicitly (tests, forced-DPR harnesses). 0 resumes
36
+ * live tracking. */
25
37
  setDpr(dpr: number): void {
26
- this.dpr = dpr;
38
+ this.dprOverride = dpr;
27
39
  this.resize();
28
40
  }
29
41