aboutsummaryrefslogtreecommitdiff
path: root/utils/equal.ts
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-04-22 06:51:57 +0530
committerBobby <[email protected]>2026-04-22 06:51:57 +0530
commit4dfbf7a13120b3b64521fce6de7d5d3f76cd2084 (patch)
tree351d2ab064442e29e76a74c7b4fa53c1048fb66b /utils/equal.ts
parent4383213a23e27903e1fed270f1dbcc116644c7fc (diff)
downloadhollowdark-4dfbf7a13120b3b64521fce6de7d5d3f76cd2084.tar.xz
hollowdark-4dfbf7a13120b3b64521fce6de7d5d3f76cd2084.zip
Add shared utilities: Result, assert, deepEqual, common types, log
Foundational helpers referenced by most subsequent code. Small, pure, no dependencies on anything in the project. utils/result.ts Result<T, E> = Ok<T> | Err<E> for recoverable failures at system boundaries (save/load, manifest fetch, content validation). Internal pure logic still uses throw for programmer errors. ok / err / isOk / isErr / mapResult / mapErr / unwrap / unwrapOr. utils/assert.ts assert (with type-narrowing asserts), assertNever for exhaustive-switch termination, assertDefined for narrowing T | null | undefined → T. utils/equal.ts Structural deepEqual for tests comparing simulation snapshots. Handles plain objects, arrays, Map, Set, Date, primitives. Does not handle cyclic graphs (simulation state is a tree). utils/types.ts Common type aliases: NonEmptyArray, JsonValue, ElementOf, DeepReadonly, Brand<T, B> for nominal / branded types (PersonId vs RelationshipId). utils/log.ts Structured logging wrapping console. Developer- facing only; the game ships with no runtime telemetry. utils/index.ts Public re-exports. 40 unit tests in tests/unit/utils/ cover Result constructors and combinators, assert variants, and deepEqual's primitive / array / object / Map / Set / Date paths plus a realistic simulation-snapshot comparison.
Diffstat (limited to 'utils/equal.ts')
-rw-r--r--utils/equal.ts55
1 files changed, 55 insertions, 0 deletions
diff --git a/utils/equal.ts b/utils/equal.ts
new file mode 100644
index 0000000..8e12216
--- /dev/null
+++ b/utils/equal.ts
@@ -0,0 +1,55 @@
+/**
+ * Structural deep equality, used by determinism tests to compare
+ * simulation snapshots. Handles plain objects, arrays, Maps, Sets, Dates,
+ * and primitives. Does not handle class instances with custom equality
+ * or cyclic object graphs — simulation state is a tree, not a graph.
+ */
+export function deepEqual(a: unknown, b: unknown): boolean {
+ if (Object.is(a, b)) return true
+
+ if (typeof a !== typeof b) return false
+ if (a === null || b === null) return false
+ if (typeof a !== 'object') return false
+
+ if (a instanceof Date || b instanceof Date) {
+ return a instanceof Date && b instanceof Date && a.getTime() === b.getTime()
+ }
+
+ if (Array.isArray(a) || Array.isArray(b)) {
+ if (!Array.isArray(a) || !Array.isArray(b)) return false
+ if (a.length !== b.length) return false
+ for (let i = 0; i < a.length; i++) {
+ if (!deepEqual(a[i], b[i])) return false
+ }
+ return true
+ }
+
+ if (a instanceof Map || b instanceof Map) {
+ if (!(a instanceof Map) || !(b instanceof Map)) return false
+ if (a.size !== b.size) return false
+ for (const [key, value] of a) {
+ if (!b.has(key) || !deepEqual(value, b.get(key))) return false
+ }
+ return true
+ }
+
+ if (a instanceof Set || b instanceof Set) {
+ if (!(a instanceof Set) || !(b instanceof Set)) return false
+ if (a.size !== b.size) return false
+ for (const value of a) {
+ if (!b.has(value)) return false
+ }
+ return true
+ }
+
+ const aObj = a as Record<string, unknown>
+ const bObj = b as Record<string, unknown>
+ const aKeys = Object.keys(aObj)
+ const bKeys = Object.keys(bObj)
+ if (aKeys.length !== bKeys.length) return false
+ for (const key of aKeys) {
+ if (!Object.prototype.hasOwnProperty.call(bObj, key)) return false
+ if (!deepEqual(aObj[key], bObj[key])) return false
+ }
+ return true
+}