logo
By Lawrence

5 minutes

Tune the Game

One component left, and it is the smallest in the course:

        "GameConfig": {}

That is the eighty-second component placement in the scene, and it completes the shipped game.

An invisible input, made visible

src/components/GameConfig/GameConfig.defs.ts
export interface GameConfigInput {
  /**
   * Test mode: drop the orbital fleet / wave schedule down to a single
   * hand-test target so per-part collision + destruction can be verified
   * without chasing a fleet.
   */
  testMode?: boolean;
  /** Which level / wave schedule to run. Resolved by the WaveDirector. */
  level?: string;
  /**
   * Allow the GameConfigSystem to flip `testMode` on from a `?test=1` /
   * `?mode=test` URL parameter. Off in headless tests so the URL of the runner
   * can't leak into the run.
   */
  honorUrlParams?: boolean;
}

The game this series is ported from read ?test=1 off window.location in its app shell, before the world was built. That works, and it has a cost that only shows up later: the mode was not visible to anything in the world. No debug panel could show it, no test could set it, and nothing could save it. It was a fact about the browser rather than a fact about the game.

src/components/GameConfig/GameConfig.ts
private resolve(entity: Entity): void {
  const config = entity.get(GameConfigComponent);
  if (!config || this.resolved.has(config)) return;
  this.resolved.add(config);

  // URL override: a `?test` query flips testMode on (never off), mirroring the
  // old App.tsx. Guarded so a headless test runner's URL can't leak in.
  if (config.honorUrlParams && typeof window !== 'undefined') {
    const search = window.location?.search;
    if (urlRequestsTestMode(search)) config.testMode = true;
  }

  this.eventBus.emit(GameConfigEvents.READY, {
    testMode: config.testMode,
    level: config.level,
  });
}

Reading the URL is input from outside, no different from a keyboard system reading window. So it is guarded twice: on a window existing, and on the scene opting in with honorUrlParams. A headless test runner's own URL can never leak into a run.

Continue reading

Unlock the Full Course

Every lesson, the runnable examples, and the finished build — yours to keep.

$9one-time

Was this page helpful?

We read every note — tell us what's working and what isn't.

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search