Files
glitch_automata_lab/backend/admin/src/caRuntime.ts
T

135 lines
5.0 KiB
TypeScript
Raw Normal View History

2026-07-01 12:10:12 +02:00
import type { BoundaryCondition, CaClass, Cells, JsonObject, SceneParams } from './types.js'
export const DEFAULT_BOUNDARY_CONDITION: BoundaryCondition = 'wrap'
export const DEFAULT_GRID_SIZE = 24
export function isPlainObject(value: unknown): value is JsonObject {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
}
export function statesEqual(left: CaClass['states'] | undefined, right: CaClass['states'] | undefined) {
if (left === undefined || right === undefined) return false
if (Array.isArray(left) || Array.isArray(right)) {
if (!Array.isArray(left) || !Array.isArray(right)) return false
return JSON.stringify([...left].sort()) === JSON.stringify([...right].sort())
}
return left === right
}
export function supportsCaClass(runtime: { supported_classes?: CaClass[]; supportedClasses?: CaClass[] }, caClass: CaClass | undefined) {
if (!caClass) return false
const supportedClasses = runtime.supported_classes ?? runtime.supportedClasses ?? []
return supportedClasses.some(
(supportedClass) =>
supportedClass.dimensions === caClass.dimensions &&
statesEqual(supportedClass.states, caClass.states) &&
neighborhoodsCompatible(supportedClass, caClass)
)
}
function neighborhoodsCompatible(supportedClass: CaClass, caClass: CaClass) {
if (!supportedClass.neighborhoodId || !caClass.neighborhoodId) return true
return supportedClass.neighborhoodId === caClass.neighborhoodId
}
export function getCaClassFromParams(params: JsonObject | undefined): CaClass | undefined {
const caClass = params?.caClass
if (!isPlainObject(caClass)) return undefined
const dimensions = typeof caClass.dimensions === 'number' ? caClass.dimensions : undefined
const states = caClass.states
if (!dimensions || (typeof states !== 'number' && !Array.isArray(states))) return undefined
return {
neighborhoodId: typeof caClass.neighborhoodId === 'string' ? caClass.neighborhoodId : undefined,
dimensions,
states
}
}
export function numericGridSize(value: unknown, dimensions: number, fallbackSize = DEFAULT_GRID_SIZE) {
const grid = isPlainObject(value) ? value : {}
const size = Array.isArray(grid.size) ? grid.size : []
const x = typeof size[0] === 'number' ? size[0] : fallbackSize
const y = dimensions >= 2 && typeof size[1] === 'number' ? size[1] : 1
const z = dimensions >= 3 && typeof size[2] === 'number' ? size[2] : 1
return [x, y, z] as const
}
export function seededRandom(seed: string) {
let state = 2166136261
for (let index = 0; index < seed.length; index += 1) {
state ^= seed.charCodeAt(index)
state = Math.imul(state, 16777619)
}
return () => {
state += 0x6d2b79f5
let t = state
t = Math.imul(t ^ (t >>> 15), t | 1)
t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
}
}
2026-07-06 23:11:42 +02:00
export function resolveRngSeed(simulation: JsonObject | undefined, fallback: string) {
if (typeof simulation?.rng_seed === 'string' && simulation.rng_seed.trim()) return simulation.rng_seed
if (typeof simulation?.seed === 'string' && simulation.seed.trim()) return simulation.seed
return fallback
}
2026-07-01 12:10:12 +02:00
export function isBoundaryCondition(value: unknown): value is BoundaryCondition {
return value === 'wrap' || value === 'mirror' || value === 'fixed'
}
export function getBoundaryCondition(params: SceneParams | undefined): BoundaryCondition {
return resolveBoundaryCondition(params?.simulation?.grid)
}
export function resolveBoundaryCondition(grid: JsonObject | undefined): BoundaryCondition {
if (isBoundaryCondition(grid?.boundary)) return grid.boundary
if (grid?.wrap === false) return 'fixed'
return DEFAULT_BOUNDARY_CONDITION
}
export function wrapIndex(index: number, size: number) {
return ((index % size) + size) % size
}
export function mirrorIndex(index: number, size: number) {
if (size <= 1) return 0
const period = (size - 1) * 2
const wrapped = wrapIndex(index, period)
return wrapped <= size - 1 ? wrapped : period - wrapped
}
export function resolveBoundaryIndex(index: number, size: number, boundary: BoundaryCondition) {
if (size <= 0) return null
if (boundary === 'wrap') return wrapIndex(index, size)
if (boundary === 'mirror') return mirrorIndex(index, size)
return index >= 0 && index < size ? index : null
}
export function cloneCells(cells: Cells): Cells {
return cells.map((row) => [...row])
}
export function assertCellsShape(cells: Cells, label = 'cells') {
const width = cells[0]?.length ?? 0
for (const row of cells) {
if (row.length !== width) throw new Error(`${label} must be rectangular`)
for (const value of row) {
if (typeof value !== 'boolean') throw new Error(`${label} must contain boolean cell states`)
}
}
}
export function assertSameCellsShape(reference: Cells, next: Cells, label = 'engine output') {
assertCellsShape(reference, 'input cells')
assertCellsShape(next, label)
if (next.length !== reference.length || (next[0]?.length ?? 0) !== (reference[0]?.length ?? 0)) {
throw new Error(`${label} must preserve the input grid dimensions`)
}
}