/** * Global config singleton. * * Initialized exactly once in main.js from the JSON files in /data, * then any module in the game can import it and read values: * * import { config } from '../config/Config.js'; * const thrust = config.get('ship.thrust', 900); * const menu = config.section('menu', {}); * * Conventions (see docs/PROJECT_NOTES.md): * - tunable values live in data/*.json, never hardcoded in code; * - add a new data file by listing it in data/manifest.json. */ class Config { constructor() { this.data = {}; } /** @param {Record} data keyed by config file name (no .json) */ init(data) { this.data = data ?? {}; return this; } /** @returns {boolean} true if a top-level section (a file in /data) exists */ has(name) { return Object.prototype.hasOwnProperty.call(this.data, name); } /** * @returns {object} the value at `name` if it's an object — dotted * paths allowed (`section('systems.types')` works) — else `fallback`. */ section(name, fallback = {}) { const value = this.get(name, undefined); return (value && typeof value === 'object') ? value : fallback; } /** * Dotted-path getter: config.get('menu.buttons.newGame.label', 'New Game') * @returns {*} the value at `path`, or `fallback` if any segment is missing */ get(path, fallback = undefined) { if (!path) return fallback; let node = this.data; for (const part of String(path).split('.')) { if (node == null || typeof node !== 'object') return fallback; node = node[part]; } return node === undefined ? fallback : node; } } export const config = new Config();