npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

euchre-game-notation

v1.6.0

Published

Euchre Game Notation (EGN) file format specification, JSON schemas, and TypeScript utilities.

Readme

Euchre Game Notation (EGN) Specification

Euchre Game Notation (.egn) is an open-source, platform-agnostic file format specification designed to capture the complete chronological flow of a competitive Euchre game.

Inspired by Chess PGN (Portable Game Notation), EGN provides a highly optimized, structured canvas to record the details of a Euchre game.


🃏 What is Euchre?

Euchre is a fast-paced, trick-taking card game traditionally played by four players in two partnerships using a standard 24-card deck (consisting of 9, 10, Jack, Queen, King, and Ace of each suit). The game revolves around calling a "trump" suit, in which the Jacks (known as Bowers) become the highest-ranking cards. Players bid to establish the trump suit and attempt to win at least three of the five tricks in each hand, earning points for their team.

For a comprehensive guide on gameplay, scoring, and card rankings, see the Standard Euchre Rules.


🃏 What is the purpose of creating an open-source Euchre Game Notation standard?

Just as PGN (Portable Game Notation) revolutionized chess commentary, study, and software tools—and Hand History standards enabled advanced analytics and broadcast overlays for poker—Euchre Game Notation (EGN) was created to establish a universal, open standard for Euchre.

EGN provides a shared, machine-readable game format that enables seamless cross-communication across an entire ecosystem of tools:

  • 🎮 Web & Desktop Replayers: Step through hands, study historical matches, and explore alternative "what-if" strategic lines.
  • 📊 Analyzers & Engine Solvers: Evaluate bidding decisions, trick play efficiency, and EV (expected value) of various decisions.
  • 🎬 Video Overlay Renderers: Automatically convert logged matches into broadcast-quality visual overlays for YouTube videos, live streams, and tournament broadcasts.
  • 🏆 Tournament & League Platforms: Standardize match reporting, player standings, and hand archival across clubs, leagues, and online platforms.

By bridging digital apps, analysis tools, and video rendering pipelines under a unified standard, EGN aims to elevate Euchre content, commentary, and competitive play to the next level and bring the rich analytical depth enjoyed by games like Chess and Poker to the world of Euchre.


Latest Release

  • Current npm package: 1.6.0
  • Schema family: 1.6 (EGN) / 1.2 (EMN)
  • Highlights: Converted phaseNumber to optional across deal phases, canonical baseline hash preservation, and added gameplay validation engine.

See changelog.md for full release details.


💡 Core Philosophy: Deterministic Minimalism

Unlike ad-hoc database schemas or nested JSON models that duplicate real-time game states, EGN operates on a philosophy of strict rule-engine deduction.

An .egn file purposefully strips out easily calculated metrics—such as trick winners, scoring mutations, or whose turn it is to lead. A compliant parsing engine hydrates this data into a full game state by applying the deterministic rules of Euchre to four foundational variables:

  1. The initial environment (Who the dealer is and what card is turned up).
  2. The bidding calls (Sequential decisions mapped clockwise from the dealer's left).
  3. The chronological play stream (An array-of-arrays mapping card drops exactly as they hit the table).
  4. Annotations (Optional infrastructure to log mid-hand annotations for commentary or events like renegs or misdeals).

🛠️ File Structure Example (EGN v1.6)

Under the hood, an .egn file utilizes human-readable, web-native JSON structural primitives:

{
  "fileType": "Euchre Game Notation",
  "version": "1.6",
  "metadata": {
    "gameId": "egn_m_20260528_01",
    "title": "WEC Finals",
    "description": "Championship bracket game recorded live from local venue stream.",
    "date": "2026-05-17T19:00:00Z",
    "teamNames": ["Midwest Aces", "Great Lakes Loners"],
    "players": ["Player0", "Player1", "Player2", "Player3"],
    "initialScore": [0, 0],
    "ruleset": {
      "std": true,
      "canadian": false,
      "loner_lead": "LEFT_OF_DEALER"
    } 
  },
  "deals": [
    {
      "dealNumber": 0,
      "initialState": {
        "dealer": 3,
        "upCard": "Jd"
      },
      "phases": [
        {
          "type": "EUCHRE_BIDDING",
          "calls": ["Pass", "Pass", "Pass", "Order"],
          "isAlone": false,
          "discard": "9s",
          "callAnnotations": {
            "3": ["[?]Should go alone here although it wouldn't have worked this time."]
          }
        },
        {
          "type": "TRICK_PLAY",
          "tricks": [
            ["Ac", "Tc", "9c", "Kc"],
            ["Ah", "Kh", "Th", "Qd"],
            ["Jd", "9d", "Ad", "Kd"],
            ["Jh", "Td", "Ks", "Ts"],
            ["Qc", "Qs", "Js", "Jc"]
          ]
        }
      ]
    },
    {
      "dealNumber": 1,
      "initialState": {
        "dealer": 0,
        "upCard": "Ah"
      },
      "phases": [
        {
          "type": "EUCHRE_BIDDING",
          "calls": ["Pass", "Pass", "Order"],
          "isAlone": true,
          "discard": "Kd"
        },
        {
          "type": "TRICK_PLAY",
          "tricks": [
            ["9d", "Ad", "Ah"],
            ["Qc", "Ac", "9h"],
            ["Jh", "Jc", "Th"],
            ["Jd", "Qs", "Qh"],
            ["As", "Js", "Ts"]
          ],
          "playAnnotations": {
            "0": ["[!!]Brillian lead of next here on the S3 loner. Only way to stop it!"]
          }
        }
      ]
    },
    {
      "dealNumber": 2,
      "initialState": {
        "dealer": 1,
        "upCard": "9s"
      },
      "phases": [
        {
          "type": "EUCHRE_BIDDING",
          "calls": [
            "Pass", "Pass", "Pass", "Pass", "c"
          ],
          "isAlone": false,
          "callAnnotations": {
            "4": ["[!!]Amazing next call here for the march!"]
          }
        },
        {
          "type": "TRICK_PLAY",
          "tricks": [
            ["Ac","Tc","9c","Kc"],
            ["9h","Jh","Ah","Th"],
            ["Ad","Ts","Qs","Td"],
            ["Jh","Qh","Qc","Qd"],
            ["Ks","Kd","9d","Kh"]
          ]
        }
      ],
      "alternativeLines": [
        {
          "branchIndex": 4,
          "phases": [
            {
              "type": "EUCHRE_BIDDING",
              "calls": ["Pass", "d"],
              "isAlone": false
            },
            {
              "type": "TRICK_PLAY",
              "tricks": [
                ["Ac", "Tc", "9c", "Kc"],
                ["Qc", "Qd", "Ad", "Ts"],
                ["Ah", "Th", "9h", "Td"],
                ["Jd", "9d", "Qh", "Qs"],
                ["Td", "Jh", "Kh", "Ks"]
              ]
            }
          ]
        }
      ]
    }
  ]
}

🃏 Card Representation

In EGN, cards are primarily represented by a two-character string indicating their rank (Capital letter) and suit (Lowercase letter) (e.g., "9c", "Ts", "Jd", "Qh", "Kc", "As"). However, the specification also allows for the following alternative and special representations:

  • N: Can be used as an alternative notation for the 9 rank (e.g., "Nc" for the 9 of Clubs).
  • R and L: Can be used to explicitly denote the Right and Left Bowers. It is important to note that the Left Bower ("L") always represents the Jack of the same color as the called trump suit.
  • Xx: Used to denote an unknown rank or suit (e.g., "Xc" for an unknown Club, "Jx" for an unknown Jack, or "Xx" for a completely unknown card). This is especially useful for incomplete game logs or hidden cards.
  • tngh: Used to denote generic trump ("t"), next ("n"), green suit 1 ("g") and green suit 2 ("h") for when the actual suit doesn't matter to your scenario
  • B (Joker/Benny): In rulesets that include a Joker (the "Best Bower" or "Benny"), "B" represents the Joker.

📝 Notation Details

When implementing or parsing EGN, keep the following details in mind:

  1. Unknown/Hidden Information (discard): Depending on how the game data was recorded (e.g., manually transcribed from a live stream vs. exported from a fully observable digital game engine), the dealer's discard might not be known. This properties are optional in the specification and can be omitted if the data is unavailable.

  2. Trick Order and Lead Determination: The tricks array records cards strictly in the chronological order they were dropped on the table. It does not explicitly state which player led each trick. Instead, the lead for the very first trick can be defaulted to the active player to the left of the dealer (except on loners when loner_lead is set to LEFT_OF_LONER). For all subsequent tricks, the lead is implicitly determined by calculating the winner of the prior trick using standard Euchre rules. This aligns with EGN's philosophy of deterministic minimalism.

  3. Annotations: Optional commentary infrastructure for bidding or trick play. Annotations are defined under callAnnotations (for bidding phases) and playAnnotations (for trick play phases) as a map from a decision/trick index to an array of strings (e.g., {"3": ["Order Up on Jacks", "Maker went alone"]}). Annotation strings may optionally begin with a label tag to classify the quality of a decision:

    | Label | Meaning | |--------|--------------------------------------------| | [??] | Blunder — a severely bad decision | | [?] | Mistake — a clear error | | [?!] | Dubious — questionable, probably wrong | | [!?] | Interesting — creative, but debatable | | [!] | Great play — a solid, correct decision | | [!!] | Brilliant play — an excellent decision |

    For example: "[??]Throwing trump here lost the hand." or "[!!]Perfect lone call.". See docs/annotations.md for full details.

  4. Alternative Lines (Branching): Analysis networks or theoretical branching plays can be specified via the optional alternativeLines array on any deal. Each alternative line contains a branchIndex (a 0-based decision index representing the point of deviation from the main game flow) and a phases list containing alternative Bidding or TrickPlay phases representing the sequence of alternate actions. For replayer implementation details, see docs/replayer-logic.md.

  5. Flexible Metadata Fields:

    • Player Count: The players array supports any number of player names (instead of being strictly restricted to a size of 4) to accommodate different gameplay variants or incomplete logs.
    • Player Objects with External ID Tracking: Players can be specified as simple strings or as rich player objects containing name and external ID mappings. This enables tracking the same player across multiple platforms and systems:
      {
        "players": [
          "Simple Player Name",
          {
            "name": "Player Name",
            "playerIds": [
              { "id": "player-123", "source": "euchre-site" },
              { "id": "tournament-2026-P5", "source": "tournament-registry" }
            ]
          }
        ]
      }
      This format supports any number of external ID systems, allowing for unified player identification across Euchre websites, tournament registrations, chat community IDs, or custom platform identifiers. See docs/player-tracking.md for implementation details.
    • Team Names & Partnerships (teamNames): An optional 2-element tuple of strings ([Team 0/2 Name, Team 1/3 Name]) defining custom team names for the North/South and East/West partnerships:
      {
        "teamNames": ["Midwest Aces", "Great Lakes Loners"],
        "players": ["Alice", "Bob", "Cheryl", "David"]
      }
    • Flexible Date Formats: The date property supports date-only strings (e.g., 2026-08-11), ISO-8601 date-time strings with a timezone offset (e.g., 2026-05-17T19:00:00Z or +05:30), or local timezone-less formats (e.g., 2026-05-30T03:51 or 2026-06-02 19:02).
  6. Variant Rulesets: For extensive documentation on alternate rules and regional variations supported in EGN, see docs/alternate-rules.md.


💾 Binary Serialization (Condensed vs. Expanded)

EGN supports two binary serialization modes for storage optimization. Binary files use the .egnb (EGN Binary) extension and include a magic byte header that enables automatic format detection without external metadata. For complete technical specifications including all version details, see the Condensed Binary Format Specification.

🔍 File Extensions

| Extension | Format | Description | |---|---|---| | .egnb | Condensed (default) | Base64URL bitpacked deals — most compact | | .expanded.egnb | Expanded | Full Protobuf-serialized EGN structure |

🔍 Automatic Format Detection

All .egnb files begin with a single-byte magic byte that identifies the format:

  • 0x00 — Expanded format (Protobuf)
  • 0x01 — Condensed format (Base64URL bitpacked deals)

When converting binary files to JSON, the CLI automatically detects the format from this header:

# Format auto-detected from magic byte
egn-convert game.egnb game.json
# Output: "Format auto-detected: condensed" (or "expanded")

This allows seamless round-trip conversion without requiring the user to specify the format. Legacy binary files without a magic byte will default to condensed format.

⚡ Condensed Mode (Default)

In condensed mode, the deals list is replaced by an array of Base64URL-encoded bitpacked strings. The format is highly optimized:

  • Bitpacked Serialization: The default format utilizes a bitstream header, dealer index, up-card, bidding calls, and played cards, and can preserve game state details like the discard card and playerCards (initial hands).
  • Support for Variant Rulesets: Supports variable player counts (1-8 players), alternate deck sizes (24, 28, 32, or 36 cards), and the aloneDefender feature.
  • Backward Compatibility: Fully compatible with older legacy and standard bitpacked stream versions. The parser automatically detects and decodes these formats.

For detailed bit-level specifications of all format versions, version detection logic, and encoding examples, see docs/binary-format.md.

🔍 Expanded Mode

In expanded mode, the entire EGN JSON structure (including metadata, annotations, and alternative lines) is serialized directly into binary using Protobuf for maximum compatibility and ease of integration with other systems.


⏱️ Partial Games

This format can be used to denote partial games by indicating the initial score in the metadata section and only including the actions up to the point you want to highlight in the game.


🔐 Baseline EGNs

A baseline EGN is a deterministic, analysis-free version of a game record. It contains only the essential game flow information by stripping all optional metadata:

  • Removes callAnnotations (bidding comments)
  • Removes playAnnotations (trick play comments)
  • Removes alternativeLines (branching analysis)
  • Preserves all game-critical data (bidding, play, ruleset, player info)

Baseline EGNs are useful for:

  • Deduplication: Identify duplicate game records across systems by comparing baseline hashes
  • Fair Comparison: Compare game records without being influenced by subjective annotations
  • Validation: Create canonical hashes of game records for verification and auditing
  • Archival: Store lightweight versions for long-term record keeping

Each baseline EGN has a deterministic SHA256 hash that remains identical regardless of the source format (condensed or expanded binary). Use the egn-baseline CLI tool to generate baseline versions and hashes. For more details, see docs/determinism-of-egn.md.


🚀 Applications & Ecosystem

EGN is developed under the NextSuit Labs brand (copyrighted by Write Words - Make Magic LLC). The following companion applications leverage the EGN standard to analyze, render, and record Euchre games:

  • Replayer: Interactive desktop-grade replay and analysis board. Load .egn files, scrub through bidding and card drops, toggle player perspectives, edit annotations, and study alternate theoretical branching lines.
  • Logger: Real-time speed logger allowing tournament watchers or game coordinators to record live games and immediately compile valid EGN logs.
  • Renderer: High-fidelity transparent overlay rendering studio and canvas editor for OBS live-stream overlays and broadcast video production.

🛠️ Command-Line Tools

The package includes several command-line utilities for working with EGN files:

Installation

Install the package globally using npm:

npm install -g euchre-game-notation

Or run tools directly using npx:

npx egn-convert --help

egn-convert — Format Conversion

Converts between human-readable JSON (.egn) files and optimized binary representations.

# JSON to condensed binary (default, most compact)
egn-convert game.egn game.egnb

# JSON to expanded binary (Protobuf)
egn-convert game.egn game.expanded.egnb --expanded

# Binary back to JSON
egn-convert game.egnb game.egn

egn-bitpack-deal — Deal Bitpacking

Compresses specific deals from an EGN file into Base64URL-encoded bitstream strings for analysis or storage.

# Bitpack all deals
egn-bitpack-deal game.egn

# Bitpack specific deals (comma-separated indices)
egn-bitpack-deal game.egn --deals 0,2,5

egn-baseline — Baseline Generation & Hashing

Extracts "baseline" EGN files by removing all analysis-only properties (callAnnotations, playAnnotations, alternativeLines) and generates deterministic SHA256 hashes. Useful for deduplication, validation, and comparing game records without analysis metadata.

# Extract baseline and hash a game
egn-baseline game.egn

# Hash only (without output file)
egn-baseline game.egn --hash

For details on baseline EGNs and their use cases, see docs/determinism-of-egn.md.

egn-upgrade — Version Migration

Upgrades older EGN files to the current v1.6 format by automatically renaming snake_case properties to camelCase, removing redundant fields, and updating the version string.

# Upgrade in-place
egn-upgrade old-game.egn

# Upgrade to new file
egn-upgrade old-game.egn new-game.egn

emn-match-combine — Match Series Combination

Combines multiple sub-EGN files into a unified Euchre Match Notation (.emn) file with automatic player deduplication, master ID assignment (p-01, p-02), seat mapping, and format-aware result calculations.

# Combine games into a Best of 3 match
emn-match-combine game1.egn game2.egn game3.egn -o match.emn --format BEST_OF_N --target 3 --title "League Finals"

# Combine progressive rounds into a single match file
emn-match-combine round1.egn round2.egn round3.egn -o progressive.emn --format PROGRESSIVE --target 3 --title "Weekly Progressive"

emn-match-extract — Match Series Extraction

Extracts one or all sub-EGN files from a unified Euchre Match Notation (.emn) file, automatically restoring seat-mapped player names from the master registry.

# Extract all games from a match to the directory "extracted_games"
emn-match-extract match.emn -o ./extracted_games

# Extract only Game 1 (index 0) to "game1.egn"
emn-match-extract match.emn -g 0 -o game1.egn

💻 Programmatic API Usage

Install the package locally in your project:

npm install euchre-game-notation

1. Schema Validation

Ensure an EGN JSON object conforms strictly to the specification:

import { validateEgn, isEgnFile, EgnFile } from "euchre-game-notation";

const parsedData = JSON.parse(egnJsonString);

// Option A: Detailed validation result
const result = validateEgn(parsedData);
if (result.isValid) {
  console.log("Valid EGN file!");
} else {
  console.error("Schema validation errors:", result.errors);
}

// Option B: TypeScript Type Guard
if (isEgnFile(parsedData)) {
  const egn: EgnFile = parsedData; // Typecasted automatically
  console.log(`Loaded game: ${egn.metadata.title}`);
}

2. Serialization & Format Conversion

Convert between JSON strings and binary representations programmatically:

import { convertEgnJsonToBin, convertBinToEgnJson } from "euchre-game-notation";

// Convert EGN JSON string to a condensed binary file
convertEgnJsonToBin(egnJsonString, "path/to/output.egnb", true);

// Convert a condensed binary file back to a pretty-printed EGN JSON string
const decodedJsonString = convertBinToEgnJson("path/to/output.egnb", true);

For browser and other in-memory runtimes, use the data-oriented exports that avoid filesystem access while preserving the same conversion logic:

import {
  convertBinDataToEgnFile,
  convertEgnFileToBinData,
  detectBinaryFormatFromData,
  type EgnFile,
} from "euchre-game-notation";

const egnFile: EgnFile = JSON.parse(egnJsonString);

// Encode an EgnFile object into .egnb bytes.
const binaryData = convertEgnFileToBinData(egnFile, true);

// Inspect the magic byte when needed.
const format = detectBinaryFormatFromData(binaryData);

// Decode .egnb bytes back into an EgnFile object.
const decodedEgnFile = convertBinDataToEgnFile(binaryData);

Available conversion exports:

  • convertEgnJsonToBin / convertBinToEgnJson: file-based helpers for Node.js and CLI-style workflows.
  • convertEgnJsonToBinData / convertBinDataToEgnJson: JSON-string helpers that work entirely in memory.
  • convertEgnFileToBinData / convertBinDataToEgnFile: browser-friendly object and byte-array helpers.
  • detectBinaryFormat / detectBinaryFormatFromData: magic-byte detection for files or in-memory bytes.

All converter entry points now enforce the EGN schema at the conversion boundary:

  • Binary decode validates the reconstructed EgnFile before returning it.
  • Binary encode validates the input EgnFile before serializing it.
  • Annotation maps reject non-numeric keys such as __proto__, constructor, and prototype.
  • Binary inputs and outputs are capped at 8 MiB to reduce denial-of-service risk from oversized payloads.

If you need to handle untrusted binary uploads in a browser or service, these helpers now fail fast on malformed, non-conformant, or oversized inputs instead of returning partially trusted data.

3. Euchre Match Notation (EMN) Combining & Extraction

Scope: EMN only supports standard 4-player, 2-team Euchre formats. Each game in a match maps exactly 4 seats ([North, East, South, West]) — North+South form Team 0 and East+West form Team 1. Progressive Euchre (rotating partnerships) is supported via the num_deals ruleset, but the 4-seat structure is always required.

For programmatic EMN match creation or extraction (both in Node.js and browser/web-app environments):

import { emn, type EgnFile } from "euchre-game-notation";

// 1. Combine multiple upgraded EGN files into a single EMN match file structure
const emnFile = emn.combineEgnToEmn(egnFilesArray, {
  format: "BEST_OF_N",
  target: 3,
  title: "Championship Series"
});

// 2. Extract a single EGN game (0-based) from an EMN file (replaces master player IDs with names)
const singleGameEgn: EgnFile = emn.extractEgnFromEmn(emnFile, 0);

// 3. Extract all EGN games from an EMN file
const allGamesEgns: EgnFile[] = emn.extractAllEgnsFromEmn(emnFile);

// 4. Convert EMN match file to Protobuf binary (.emnb) and back
const emnBinaryBytes: Uint8Array = emn.convertEmnFileToBinData(emnFile);
const decodedEmnFile: emn.EmnFile = emn.convertBinDataToEmnFile(emnBinaryBytes);

4. Rules, Scoring & Gameplay Validation Engine (Web & Node)

Simulate game flows, determine trick winners, calculate score changes, verify gameplay legality (reneges, duplicates, sit-out plays), and compute final match outcomes:

import { 
  calculateFinalScore, 
  addFinalScoreToEgn, 
  calculateDealScoreChange, 
  compileDealSteps,
  determineTrump,
  determineMaker,
  determineLeadSeat,
  validateGameplay,
  validateDealGameplay
} from "euchre-game-notation";

// 1. Calculate the cumulative final score across all deals:
const finalScore = calculateFinalScore(egnFile); // e.g. [10, 9]

// 2. Inject or update metadata.finalScore:
const updatedEgn = addFinalScoreToEgn(egnFile);

// 3. Validate gameplay rules (reneges, duplicate cards, sit-out plays, trick sizes):
const validation = validateGameplay(egnFile);
if (!validation.isValid) {
  console.error("Gameplay rule violations:", validation.violations);
}

// 4. Calculate score delta for a single deal:
const [team0Delta, team1Delta] = calculateDealScoreChange(deal, egnFile.metadata);

// 5. Compile chronological step-by-step table card states:
const steps = compileDealSteps(deal, egnFile.metadata);

CLI Utility: egn-engine

The package includes a built-in CLI tool to play through deals, verify gameplay legality, and add or verify finalScore:

# Display calculated game score
npx egn-engine game.egn

# Update the file in-place with the finalScore
npx egn-engine game.egn --in-place

# Check if existing finalScore matches simulated play result
npx egn-engine game.egn --check

# Validate gameplay rules (reneges, duplicate cards, trick counts)
npx egn-engine game.egn --validate-gameplay

🔒 Security & Safe Rendering Guidelines

The EGN schema guarantees type correctness, structural validity, and field length limits (such as a maximum of 128 characters for a game title). However, the schema does not restrict or sanitize HTML/XML characters (such as < and >) in free-text fields (including title, description, player names, and commentary annotations). Avoiding these character restrictions at the schema level is intentional; it allows users to write natural mathematical comparisons (e.g., score < 10) and structural arrows (e.g., ->) in game commentary and names.

Consequently, client applications (replayers, loggers, overlay renderers, and web portals) are responsible for performing context-aware output encoding:

  • Vanilla Javascript: Use safe DOM properties like element.textContent rather than element.innerHTML when displaying titles, player names, or descriptions.
  • Modern Frameworks (React, Vue, etc.): Standard text interpolation (e.g., {player.name} in React) automatically escapes text content and should be preferred.
  • Markdown Commentary: If rendering descriptions or annotations as markdown, parse the markdown and sanitize the resulting HTML using a robust library (such as DOMPurify) before placing it in the DOM.
  • Attributes and Links: Properly escape attributes if injecting user content (e.g., <span data-name="...">), and validate URL schemes for any links or sources to prevent javascript: injection.