Disclosure: I maintain AzerothSurvivors.app, an independent fan reference for Aethus's game. This article and its illustrative code were prepared with AI assistance. The code below is a standalone example, not extracted game code or a claim about the site's implementation.
A stat table can be numerically tidy and still be misleading. A cooldown of zero, an unavailable cooldown, and an effect activated by an event should not all render as 0 s.
The problem is especially easy to introduce while converting loosely structured data for a static page. A default such as value || 0 silently treats missing information as a known zero. Converting everything to a number before checking it can also erase the distinction. MDN documents that Number.isFinite() checks the supplied value without first coercing a string or null to a number.
For a small reference, I prefer making the states explicit at the boundary and formatting them only after validation.
Three states, not three guesses
In this example, a numeric record must contain a finite JavaScript number and an explicit unit. An unknown record renders as "Not recorded". A triggered record requires a short description of its event instead of pretending that it has a periodic interval.
These are presentation states. They do not determine whether a real game effect has an internal cooldown, whether the source is correct, or how a modifier is applied. A source can contain both an event trigger and a numeric cooldown; model those as separate fields rather than forcing one to replace the other.
const units = new Set(['s', '%', 'damage', 'targets']);
export function formatStat(record) {
if (!record || typeof record !== 'object' || Array.isArray(record)) {
throw new TypeError('Expected a stat record');
}
switch (record.kind) {
case 'unknown':
return 'Not recorded';
case 'number': {
if (!Number.isFinite(record.value)) {
throw new TypeError('Expected a finite number, without coercion');
}
if (!units.has(record.unit)) {
throw new TypeError('Unknown unit');
}
return `${record.value} ${record.unit}`;
}
case 'triggered': {
if (typeof record.event !== 'string' || !record.event.trim()) {
throw new TypeError('Expected a nonempty event description');
}
return `Triggered: ${record.event.trim()}`;
}
default:
throw new TypeError('Unknown stat kind');
}
}
The unit names are illustrative. Adapt the allowed set to your own schema. The function deliberately performs no percentage conversion, no rounding, and no automatic substitution of missing fields. A value of 5 with unit % means 5 % here, not an inferred fraction to multiply by 100.
Test the distinction at the boundary
Save the function as format-stat.mjs and the following as format-stat.test.mjs, then run node format-stat.test.mjs. These twelve checks cover the formatter, not combat balance or the accuracy of a game's data.
import assert from 'node:assert/strict';
import { formatStat } from './format-stat.mjs';
assert.equal(formatStat({ kind: 'number', value: 0, unit: 's' }), '0 s');
assert.equal(formatStat({ kind: 'unknown' }), 'Not recorded');
assert.equal(formatStat({ kind: 'triggered', event: ' on hit ' }), 'Triggered: on hit');
assert.equal(formatStat({ kind: 'number', value: 1e-7, unit: 's' }), '1e-7 s');
assert.equal(formatStat({ kind: 'number', value: 5, unit: '%' }), '5 %');
assert.throws(() => formatStat({ value: 0, unit: 's' }), TypeError);
assert.throws(() => formatStat({ kind: 'number', value: '0', unit: 's' }), TypeError);
assert.throws(() => formatStat({ kind: 'number', value: NaN, unit: 's' }), TypeError);
assert.throws(() => formatStat({ kind: 'number', value: Infinity, unit: 's' }), TypeError);
assert.throws(() => formatStat({ kind: 'triggered', event: ' ' }), TypeError);
assert.throws(() => formatStat({ kind: 'number', value: 1, unit: 'mystery' }), TypeError);
assert.throws(() => formatStat(null), TypeError);
console.log('12 formatter checks passed');
Notice the small positive number. Rounding it to an integer would show zero, which communicates a different value. A production interface can choose a more readable numeric format, but that choice should be tested separately from the meaning of an unavailable value.
Keep provenance beside the presentation
A formatter cannot fix an undocumented source. Keep a version label and the source scope with the data. In the Azeroth Survivors abilities reference, the baseline is explicitly labelled; the equipment reference serves a different lookup purpose. Links between the two do not make a displayed baseline equivalent to measured damage during a run.
For a browser view, insert the returned label as text rather than interpreting event descriptions as HTML. If a record fails validation, report the affected record to the editor instead of quietly turning it into zero. That gives a missing field a repair path and keeps a clean-looking table from making an unsupported claim.
The fan reference is not affiliated with Aethus or Blizzard Entertainment and does not host or redistribute game files.
Top comments (0)