# Phase 17 — Minimal, Clean, Small (Blueprint)

Purpose: ship a tiny, beautiful vertical slice that runs Core‑13 IR end‑to‑end, is observable, and easy to extend. Keep everything additive and simple.

1) Scope (MVP)
- IR: Core‑13 only (Loop IR placeholder only).
- Exec: Wrap existing VM with a tiny `ExecEngine` adapter.
- Remote: One transport only — NDJSON over stdio (`nyash-engine-core13`).
- CLI: `run`, `ir-emit`, `ir-run`. Trace: Enter/Exit only.

2) ExecEngine (tiny)
- Value: `Int(i64) | Bool(bool) | Str(String) | Box(u64) | None`.
- Trait:
  - `load(&Core13Module) -> ModuleHandle`
  - `get(&ModuleHandle, func: &str) -> FuncHandle`
  - `call(&FuncHandle, args: &[Value]) -> Result<Value>`
- Adapter: `VMEngine` delegates to existing VM (`execute_module`), converts Value <-> NyashBox.

3) Core‑13 IR JSON (minimal)
- module: `{ schema:1, name:"m", funcs:[ { name, params:[], ret:"i64|bool|string|box|void", blocks:[...] } ] }`
- block: `{ id:0, inst:[...], term:{...} }`
- inst (13 only): `Const, BinOp, Compare, Jump, Branch, Return, Phi, Call, BoxCall, ExternCall, TypeOp, Safepoint, Barrier`.
- example Const: `{ op:"Const", dst:1, ty:"i64", value:42 }`
- example Return: `{ term:"Return", value:1 }`

4) NDJSON protocol (stdio)
- Common: every request has `op,id,schema(=1)`; every response `{ok:true|false,id,...}`; unknown keys ignored.
- Ops (MVP): `load_module`, `call`, `ping`, `trace_sub` (Enter/Exit only).
- Requests:
  - load_module: `{op:"load_module",id:1,ir:"core13",format:"json",bytes:"<base64>"}`
  - call: `{op:"call",id:2,module_id:1,func:"main",args:[]}`
  - ping: `{op:"ping",id:3}`
  - trace_sub: `{op:"trace_sub",id:4,mask:["EnterFunc","ExitFunc"],flush_ms:50?}`
- Responses:
  - `{ok:true,id:1,module_id:1,features:["interp","trace"],ir:"core13"}`
  - `{ok:true,id:2,value:42,events_dropped:0}`
  - `{ok:true,id:3,now:1725600000}`
  - `{ok:true,id:4}` (events then stream as separate lines)
- Events (lines): `{"event":"EnterFunc","func":"main"}` / `{"event":"ExitFunc","func":"main"}`.

5) CLI UX
- `nyash run --engine=vm apps/hello.hako`
- `nyash run --engine=remote --exe ./nyash-engine-core13 apps/hello.hako`
- `nyash ir-emit --ir=core13 --format=json apps/hello.hako > out.json`
- `nyash ir-run --engine=vm < out.json`

6) Milestones
- M1: ExecEngine + VMAdapter + `run --engine=vm` (tests: add/if/string.length)
- M2: Core‑13 serde/verify + `ir-emit`/`ir-run` (round‑trip + 1 exec test)
- M3: `nyash-engine-core13` NDJSON (load/call/ping/trace_sub) + `run --engine=remote` parity

7) Design rules (beauty & simplicity)
- Additive evolution only: version fields present (`schema`), unknown keys ignored.
- Deterministic JSON: stable key order where practical to make diffs readable.
- Naming: short, explicit; avoid redundancy; keep method ids optional.
- Observability first: ship with Enter/Exit trace; branch/extern later.

Appendix: Two request/response examples
1) Load then call
REQ: `{ "op":"load_module", "id":1, "schema":1, "ir":"core13", "format":"json", "bytes":"<base64>" }`
RESP:`{ "ok":true, "id":1, "module_id":1, "features":["interp","trace"], "ir":"core13" }`
REQ: `{ "op":"call", "id":2, "schema":1, "module_id":1, "func":"main", "args":[] }`
RESP:`{ "ok":true, "id":2, "value":42, "events_dropped":0 }`

2) Trace subscribe and ping
REQ: `{ "op":"trace_sub", "id":10, "schema":1, "mask":["EnterFunc","ExitFunc"] }`
RESP:`{ "ok":true, "id":10 }`
EVT: `{ "event":"EnterFunc", "func":"main" }`
EVT: `{ "event":"ExitFunc", "func":"main" }`
REQ: `{ "op":"ping", "id":11, "schema":1 }`
RESP:`{ "ok":true, "id":11, "now":1725600000 }`

