diff options
| author | eunhhu <[email protected]> | 2026-09-24 17:14:54 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-24 17:14:54 +0100 |
| commit | b1ce1927022d374198441afafeb27199fc8a052a (patch) | |
| tree | 6021f175f4c67ed03b44240eaa2f90d846f12f6f /prototypes/live-rules/runtime.mjs | |
| parent | d34f9426d5317a1948f12f229439946728bc342a (diff) | |
| download | pure-b1ce1927022d374198441afafeb27199fc8a052a.tar.gz pure-b1ce1927022d374198441afafeb27199fc8a052a.zip | |
Establish native micro runtime checkpoint
Diffstat (limited to 'prototypes/live-rules/runtime.mjs')
| -rw-r--r-- | prototypes/live-rules/runtime.mjs | 100 |
1 files changed, 100 insertions, 0 deletions
diff --git a/prototypes/live-rules/runtime.mjs b/prototypes/live-rules/runtime.mjs new file mode 100644 index 0000000..9c854ea --- /dev/null +++ b/prototypes/live-rules/runtime.mjs @@ -0,0 +1,100 @@ +// Node adapter only. All rule evaluation, physics and persistent state live in Zig. +import { readFile } from 'node:fs/promises'; + +export const fields = [ + 'tick', 'y', 'vy', 'distance', 'obstacleX', 'passed', 'hits', 'jumpsUsed', + 'running', 'revision', 'lastReason', 'lastSource', 'totalInputs', + 'acceptedJumps', 'mode', 'impulse', 'enabled', 'grounded', 'obstacleHeight', +]; +const signed = new Set(['y', 'vy', 'obstacleX']); + +export async function loadCore() { + const binary = await readFile(new URL('./web/core.wasm', import.meta.url)); + const module = await WebAssembly.compile(binary); + if (WebAssembly.Module.imports(module).length !== 0) throw new Error('Core must not import a host runtime.'); + const instance = await WebAssembly.instantiate(module, {}); + instance.exports.reset(); + return instance.exports; +} + +export function snapshot(core) { + const state = Object.fromEntries(fields.map((name, id) => { + const value = core.get(id); + return [name, signed.has(name) ? value : value >>> 0]; + })); + return { + state, + decision: core.node_value(0) === -1 ? null : { + tick: core.decision_meta(0) >>> 0, + revision: core.decision_meta(1) >>> 0, + y: core.decision_meta(2), + jumps: core.decision_meta(3) >>> 0, + nodes: Array.from({ length: 6 }, (_, i) => core.node_value(i)), + }, + history: Array.from({ length: core.history_count() }, (_, i) => ({ + tick: core.history_get(i, 0) >>> 0, + kind: core.history_get(i, 1), + detail: core.history_get(i, 2) >>> 0, + revision: core.history_get(i, 3) >>> 0, + })), + }; +} + +export function storage(core) { + const keys = ['rules', 'state', 'decision', 'history', 'engine', 'graph', 'logicalTotal']; + return { + layoutBytes: Object.fromEntries(keys.map((key, i) => [key, core.bytes(i)])), + wasmLinearMemoryBytes: core.memory.buffer.byteLength, + note: 'Type-layout sizes; constant graph may be compiled into code. Not browser heap or RSS.', + }; +} + +export function parseCommand(line) { + const tokens = line.trim().split(/\s+/); + const name = tokens[0]; + if (!name) return { op: 'status' }; + if (['status', 'inspect', 'memory', 'reset', 'jump', 'help', 'quit'].includes(name)) { + if (tokens.length !== 1) throw new Error(`${name}: 인수가 필요하지 않습니다.`); + return { op: name }; + } + if (name === 'step') { + if (tokens.length > 2) throw new Error('사용법: step [1..600]'); + const count = tokens.length === 1 ? 1 : integer(tokens[1], 1, 600); + return { op: 'step', count }; + } + if (name === 'rules') { + if (tokens.length !== 4 || !['ground', 'double'].includes(tokens[1]) || !['on', 'off'].includes(tokens[3])) { + throw new Error('사용법: rules ground|double 6..16 on|off'); + } + return { op: 'rules', mode: tokens[1] === 'double' ? 1 : 0, + impulse: integer(tokens[2], 6, 16), enabled: tokens[3] === 'on' ? 1 : 0 }; + } + throw new Error('알 수 없는 명령입니다. help를 입력하세요.'); +} + +function integer(text, min, max) { + if (!/^\d+$/.test(text)) throw new Error(`${min}..${max} 범위의 정수를 입력하세요.`); + const value = Number(text); + if (!Number.isSafeInteger(value) || value < min || value > max) { + throw new Error(`${min}..${max} 범위의 정수를 입력하세요.`); + } + return value; +} + +export function execute(core, command) { + switch (command.op) { + case 'jump': core.input(2); break; + case 'step': + for (let n = 0; n < command.count; n += 1) core.step(); + break; + case 'rules': + if (core.apply_rules(command.mode, command.impulse, command.enabled) !== 0) { + throw new Error('규칙 전체를 거절했습니다. 이전 규칙과 상태를 유지합니다.'); + } + break; + case 'reset': core.reset(); break; + case 'status': case 'inspect': case 'memory': break; + default: throw new Error('실행할 수 없는 명령입니다.'); + } + return snapshot(core); +} |
