Events

All HTMLGameKit events bubble and are composed (they cross shadow DOM boundaries). This means you can dispatch them from any depth in the component tree and <game-shell> will catch them.

Round Lifecycle

Game mechanics communicate with the shell by dispatching custom DOM events. The shell catches them, updates signals, and drives scene transitions:

1. Scene enters "playing"
   → Timer starts (if present)
   → Game mechanic sets up the round

2. Player interacts
   → GameRoundPassEvent(score, feedback)   success
   OR GameRoundFailEvent(reason, retry)    failure
   OR GameTimerExpiredEvent                time ran out

3. Shell catches the event, updates signals
   → scene → "between"
   → Director computes next difficulty
   → After betweenDelay, round increments, scene → "playing"

4. After all rounds (or GameCompleteEvent)
   → scene → "result"

Event Overview

Event Fired by Caught by
game-round-pass Your game Shell
game-round-fail Your game Shell
game-timer-tick <game-timer> Your game (optional)
game-timer-expired <game-timer> Shell
game-timer-countdown <game-timer> <game-audio> (optional)
game-stat-update Your game Shell
game-lifecycle Shell Your entry script
game-start-request Intro button Shell
game-restart-request Result button Shell
game-complete Your game Shell
game-pause-request Any child Shell
game-resume-request Any child Shell
game-next-round Any child Shell
game-practice-start Any child Shell
game-trophy-unlock <game-trophy> Shell
game-tile-input <game-tile-input> <game-audio>
game-tile-submit <game-tile-input> Your game
pending-task Any component Shell
game-preference-change <game-preferences> Your entry script

Import

import {
  GameRoundPassEvent,
  GameRoundFailEvent,
  GameTimerTickEvent,
  GameTimerExpiredEvent,
  GameTimerCountdownEvent,
  GameStatUpdateEvent,
  GameLifecycleEvent,
  GameStartRequestEvent,
  GameRestartRequestEvent,
  GameCompleteEvent,
  GamePauseRequestEvent,
  GameResumeRequestEvent,
  GameNextRoundEvent,
  GamePracticeStartEvent,
  GameTileInputEvent,
  GameTileSubmitEvent,
  PendingTaskEvent,
} from "htmlgamekit";

// Trophy and preference events are exported alongside their components:
import { GameTrophyUnlockEvent } from "htmlgamekit"; // dispatched by <game-trophy>
import { GamePreferenceChangeEvent } from "htmlgamekit"; // dispatched by <game-preferences>

GameRoundPassEvent

Signals that the player completed a round successfully.

new GameRoundPassEvent(score, feedback?)

Event name: "game-round-pass"

Parameters:

  • scorenumber — The numeric score for this round.
  • feedbackstring (optional) — A short feedback string to show the player (e.g. "Nice!", "342ms").
.score
number — The round score.
.feedback
string | undefined — Optional feedback text.
// Player answered correctly in 342ms
this.dispatchEvent(new GameRoundPassEvent(342, "342ms"));

GameRoundFailEvent

Signals that the player failed a round.

new GameRoundFailEvent(reason?, retry?)

Event name: "game-round-fail"

Parameters:

  • reasonstring (optional) — A message explaining the failure (e.g. "Too slow", "Wrong answer").
  • retryboolean (optional, default false) — If true, the round does not count against the player's total. The round number stays the same and the player tries again. If false, the round is consumed (counts as a failed attempt).
.reason
string | undefined — Failure reason.
.retry
boolean — Whether this is a non-counting retry.
// Counting failure — round is consumed
this.dispatchEvent(new GameRoundFailEvent("Too slow"));

// Non-counting retry — player tries the same round again
this.dispatchEvent(new GameRoundFailEvent("False start!", true));

GameTimerTickEvent

Fired by <game-timer> on each tick to report remaining time.

new GameTimerTickEvent(remaining, fraction)

Event name: "game-timer-tick"

Parameters:

  • remainingnumber — Seconds remaining.
  • fractionnumber — Value between 0 and 1 representing the proportion of time elapsed (0 = start, 1 = expired).
.remaining
number — Seconds left on the timer.
.fraction
number — Fraction of total time elapsed (0–1).
shell.addEventListener("game-timer-tick", (e) => {
  if (e.remaining < 3) {
    urgencyIndicator.classList.add("warning");
  }
});

GameTimerExpiredEvent

Fired when the round timer reaches zero.

new GameTimerExpiredEvent()

Event name: "game-timer-expired"

Takes no parameters. The shell catches this event and treats it as a round failure.

// Typically fired internally by <game-timer>, but you can dispatch it manually:
this.dispatchEvent(new GameTimerExpiredEvent());

GameStatUpdateEvent

Updates an arbitrary stat in the game state's stats map.

new GameStatUpdateEvent(key, value)

Event name: "game-stat-update"

Parameters:

  • keystring — The stat key to update.
  • valueany — The new value.
.key
string — Stat key.
.value
any — Stat value.
// Track how many hints the player used
this.dispatchEvent(new GameStatUpdateEvent("hintsUsed", 3));

The shell merges this into the stats signal, so any component observing it will see the updated value.


GameLifecycleEvent

Fired by <game-shell> on every scene transition. This is the primary event for reacting to game scene changes.

new GameLifecycleEvent(action, state)

Event name: "game-lifecycle"

Parameters:

  • actionstring — The new scene name (e.g. "playing", "between", "result").
  • stateobject — A snapshot of the full game state after the transition.
.action
string — The triggering action.
.state
object — Full state snapshot.
.scene
string — Convenience alias for e.state.scene.
document.querySelector("game-shell").addEventListener("game-lifecycle", (e) => {
  if (e.action === "result") {
    console.log("Game over! Final score:", e.state.score);
  }
  if (e.state.scene === "playing") {
    console.log(`Round ${e.state.round} of ${e.state.rounds}`);
  }
});

GameStartRequestEvent

Signals that the player wants to start a game. Dispatched by the intro overlay (<div when-some-scene="intro" data-overlay>) when the user clicks a start button. The shell catches this and calls .start().

new GameStartRequestEvent()

Event name: "game-start-request"

Takes no parameters.

// Typically dispatched by the intro overlay, but you can trigger it manually:
this.dispatchEvent(new GameStartRequestEvent());

GameRestartRequestEvent

Signals that the player wants to restart the game. Dispatched by the result overlay (<div when-some-scene="result" data-overlay>) when the user clicks a restart button. The shell catches this, resets state, and starts a new game.

new GameRestartRequestEvent()

Event name: "game-restart-request"

Takes no parameters.

// Typically dispatched by the result overlay, but you can trigger it manually:
this.dispatchEvent(new GameRestartRequestEvent());

GameCompleteEvent

Signals that the game should end immediately, skipping any remaining rounds. The shell catches this, optionally sets the final score, and transitions to the result scene.

new GameCompleteEvent(score?)

Event name: "game-complete"

Parameters:

  • scorenumber (optional) — If provided, overrides the shell's accumulated score before entering the result scene.
.score
number | undefined — Optional final score override.
// End the game early with a specific score
this.dispatchEvent(new GameCompleteEvent(42));

// End the game early, keeping the accumulated score
this.dispatchEvent(new GameCompleteEvent());

GamePauseRequestEvent

Requests that the shell pause the game. The shell catches this and transitions from playing to paused.

new GamePauseRequestEvent()

Event name: "game-pause-request"

Takes no parameters.

this.dispatchEvent(new GamePauseRequestEvent());

GameResumeRequestEvent

Requests that the shell resume a paused game. The shell catches this and transitions from paused back to playing.

new GameResumeRequestEvent()

Event name: "game-resume-request"

Takes no parameters.

this.dispatchEvent(new GameResumeRequestEvent());

GameNextRoundEvent

Requests that the shell skip the between-round delay and advance to the next round immediately. Only takes effect when the current scene is between.

new GameNextRoundEvent()

Event name: "game-next-round"

Takes no parameters.

this.dispatchEvent(new GameNextRoundEvent());

GamePracticeStartEvent

Signals that the player wants to enter practice mode. The shell catches this and sets scene to "practice".

new GamePracticeStartEvent()

Event name: "game-practice-start"

Takes no parameters.

// Dispatch from a "Practice" button handler:
this.dispatchEvent(new GamePracticeStartEvent());

GameTileInputEvent

Dispatched by <game-tile-input> on every character change (type or delete). Used by <game-audio> with trigger="input" to play keystroke sounds.

new GameTileInputEvent(value, position)

Event name: "game-tile-input"

Parameters:

  • valuestring — The current input string.
  • positionnumber — Index of the last changed character.
.value
string — The current input string.
.position
number — Index of the last changed character.
tileInput.addEventListener("game-tile-input", (e) => {
  console.log(`Input: "${e.value}", position: ${e.position}`);
});

GameTimerCountdownEvent

Fired by <game-timer> on each whole-second tick of the countdown. Used by <game-audio> with trigger="countdown" to play a ticking sound once per second.

new GameTimerCountdownEvent(seconds)

Event name: "game-timer-countdown"

Parameters:

  • secondsnumber — The whole number of seconds remaining.
.seconds
number — Whole seconds remaining.
shell.addEventListener("game-timer-countdown", (e) => {
  if (e.seconds <= 3) urgencyIndicator.classList.add("warning");
});

GameTileSubmitEvent

Dispatched by <game-tile-input> when the player presses Enter with a complete word.

new GameTileSubmitEvent(value)

Event name: "game-tile-submit"

Parameters:

  • valuestring — The submitted word.
.value
string — The submitted word.
tileInput.addEventListener("game-tile-submit", (e) => {
  const guess = e.value.toLowerCase();
  // Validate and process the guess
});

GameTrophyUnlockEvent

Dispatched by <game-trophy> when a trophy is unlocked (either via .unlock() or auto-unlock conditions). Exported from "htmlgamekit" alongside GameTrophy.

new GameTrophyUnlockEvent(id, name)

Event name: "game-trophy-unlock"

Parameters:

  • id -- string -- The trophy's id attribute.
  • name -- string -- The trophy's name attribute.
.trophyId
string -- The trophy's id.
.trophyName
string -- The trophy's display name.
shell.addEventListener("game-trophy-unlock", (e) => {
  console.log(`Unlocked: ${e.trophyName} (${e.trophyId})`);
});

GamePreferenceChangeEvent

Dispatched by <game-preferences> when a preference value changes.

new GamePreferenceChangeEvent(key, value)

Event name: "game-preference-change"

Parameters:

  • key -- string -- The preference key.
  • value -- any -- The new value (boolean for toggles, number for ranges).
.key
string -- The preference key.
.value
any -- The new value.
shell.addEventListener("game-preference-change", (e) => {
  if (e.key === "dark-mode") {
    document.body.classList.toggle("dark", e.value);
  }
});

PendingTaskEvent

Signals that an asynchronous task is in progress. Used by components such as <game-score-form> and <game-leaderboard> to expose in-flight promises to any external loading coordinator (e.g. a loading indicator). The shell itself does not queue or await these tasks.

new PendingTaskEvent(complete)

Event name: "pending-task"

Parameters:

  • completePromise — A promise that resolves when the task is done.
.complete
Promise — The pending promise.
// Delay the next round until a sound effect finishes
const audioPromise = playSound("correct.mp3");
this.dispatchEvent(new PendingTaskEvent(audioPromise));
this.dispatchEvent(new GameRoundPassEvent(100, "Correct!"));