分步教程:grayscale
examples/grayscale 是架构计划里的“第一个证明”:
gradient -> grayscale -> pixelate -> output.ppm本教程在仓库根目录下进行,所有输出都是实际运行得到的。
第 1 步:读懂三个 Kernel
Section titled “第 1 步:读懂三个 Kernel”kernels/gradient.ent——一个生成器,没有输入,所以场景必须给它定义域:
/// A generator: no inputs, so the scene must give the node a domain.kernel image.gradient@1 { requires { field.write; } param depth: Float = 0.5; output result: Image; compute { let uv = coord() / (domain() - 1.0); return vec4(uv, depth, 1.0); }}kernels/grayscale.ent——按亮度权重转成灰度,amount 控制混合程度,fn luma 是 Kernel 内部的函数:
/// Edit the weights or `amount` while `entelechy run --watch` is running.kernel image.grayscale@1 { requires { field.read; field.write; } input source: Image; param amount: Float = 1.0; output result: Image; fn luma(c: Vec4) -> Float { return dot(c.rgb, vec3(0.2126, 0.7152, 0.0722)); } compute { let c = sample(source); let g = luma(c); return mix(c, vec4(g, g, g, c.a), amount); }}kernels/pixelate.ent——用 fetch 读取任意位置(块的原点),而不只是当前元素:
kernel image.pixelate@1 { requires { field.read; field.write; } input source: Image; param size: Int = 8; output result: Image; compute { let block = float(max(size, 1)); let origin = floor(coord() / block) * block; return fetch(source, origin); }}第 2 步:场景
Section titled “第 2 步:场景”// gradient -> grayscale -> pixelateuse "kernels/";
node source: image.gradient@1 { domain = vec2(128, 128);}
node gray: image.grayscale@1 { source <- source.result; amount = 1.0;}
node blocks: image.pixelate@1 { source <- gray.result; size = 8;}
output result <- blocks.result;第 3 步:运行
Section titled “第 3 步:运行”entelechy run examples/grayscale/scene.esn --out output.pngscene loadedpublished image.gradient@1 g1 from examples/grayscale/kernels/gradient.entpublished image.grayscale@1 g2 from examples/grayscale/kernels/grayscale.entpublished image.pixelate@1 g3 from examples/grayscale/kernels/pixelate.entscene loadedevaluated in 1.241085ms: executed [gray+source, blocks], reused 0 -> output.png每个 Kernel 发布时得到一个代数(g1、g2…),之后的每次替换都会递增。

第 4 步:跑测试
Section titled “第 4 步:跑测试”entelechy replay examples/grayscale/tests/grayscale.replayexamples/grayscale/tests/grayscale.replay: ok (1 frames, 5 checks)测试文件检查了几个像素的颜色:同一块内的像素同色,越往右下角越亮。
scene ../scene.esn
# The gradient in gray, in blocks of eight: a block's pixels are one# gray, the next block's another, lighter toward the far corner.frame 0.0expect 0 0 = 0.036 0.036 0.036 0.002expect 7 7 = 0.036 0.036 0.036 0.002expect 8 0 = 0.049 0.049 0.049 0.002expect 64 64 = 0.504 0.504 0.504 0.002expect 127 127 = 0.913 0.913 0.913 0.002第 5 步:看执行计划
Section titled “第 5 步:看执行计划”entelechy graph examples/grayscale/scene.esn// plan: 2 group(s)// gray + source: fused.gray@1 vec2(128, 128)// blocks: image.pixelate@1// gray runs apart: `blocks` reads it neither at its own element (`sample`) nor at one offset of a rowgray 只在当前元素处 sample 了 source,于是两者被融合成一个 Kernel;blocks 用 fetch 读取别处的像素,所以 gray 必须先单独算完。graph 会说明每个“分开运行”的原因。
第 6 步:追踪一个像素
Section titled “第 6 步:追踪一个像素”entelechy trace examples/grayscale/scene.esn --node gray --at 64 64gray (image.grayscale@1), element (64, 64), as the evaluation at time 0 runs it c: Vec4 = vec4(0.503937, 0.503937, 0.5, 1) g: Float = 0.50365275 call luma #1 -> result: Float = 0.50365275 -> result: Vec4 = vec4(0.50365275, 0.50365275, 0.50365275, 1)第 7 步:边运行边改
Section titled “第 7 步:边运行边改”entelechy run examples/grayscale/scene.esn --out output.png --watch在它运行时依次尝试:
- 把
scene.esn中的amount = 1.0;改成0.5并保存——画面重新计算,带一半颜色。 - 在
grayscale.ent里写一个错误(例如把权重改成nope)——会看到rejected …:8:59: \nope` is not defined`,输出保持不变。 - 把
pixelate.ent的size默认值改为16——注意场景里写了size = 8;,节点的值优先于 Kernel 默认值。
第 8 步:保存中间结果
Section titled “第 8 步:保存中间结果”entelechy run examples/grayscale/scene.esn --save gray.result=gray.png --out output.pngsaved gray.result -> gray.png--save <节点.端口>=<文件> 可以导出任何节点的输出,格式由扩展名决定(.png、.ppm、.field、.wav、.csv、.json…)。这也是“制作资源”的方式。

第 9 步:换后端并计时
Section titled “第 9 步:换后端并计时”entelechy run examples/grayscale/scene.esn --backend cpu --out output.png --profile gray 0.952 ms cpu blocks 0.798 ms cpu--backend 可选 cpu(参考解释器)、jit(默认)、gpu。没有 GPU 适配器时,--backend gpu 会直接报错 gpu unavailable: No suitable graphics adapter found…;Player 默认的 best 会在没有设备时回退到 JIT。见后端。
第 10 步:让 Agent 来改
Section titled “第 10 步:让 Agent 来改”entelechy serve examples/grayscale/scene.esn然后在 stdin 上逐行输入(→ 是请求,← 是应答;这里在副本 gp/ 上操作):
→ {"id":2,"op":"publish","source":"kernel image.pixelate@1 { requires { field.read; field.write; } input source: Image; param size: Int = 16; output result: Image; compute { let b = float(max(size, 1)); return fetch(source, floor(coord() / b) * b); } }","expect":3}← {"id":2,"ok":{"generation":4,"id":"image.pixelate@1"}}→ {"id":3,"op":"compose","edits":[{"node":"blocks","member":"size","text":"size = 4;"}]}← {"id":3,"ok":{"events":[{"kind":"scene_loaded"}],"path":"gp/scene.esn","version":"bdced0a164ed8778","written":true}}publish 带 expect: 3 表示“只有当前代数仍是 3 时才替换”;compose 用 edits 修改了场景文件中 blocks 节点的 size,并真的写回了磁盘。完整说明见 Agent 协议。
- Kernel 按 id 绑定,场景只负责连线;
- 融合、计划、追踪都可以直接观察;
- 错误的改动被拒绝而世界照常运行;
- 回放就是测试;
- 同一个程序可以在三个后端上运行,也可以被 Agent 通过协议修改。

