--- name: runtime description: 元景 W-10 运行时执行引擎模块(前端 JS 引擎 + 可选服务端辅助)。世界运行时初始化、实体状态、脚本执行调度、事件分发、定时器、碰撞检测、暂停恢复、错误降级、独立世界本地引擎。开发/修改 runtime 相关功能前必读。 --- # runtime 模块(W-10 运行时执行引擎) ## 架构 ``` wwwroot/runtime.js 前端核心引擎(RuntimeEngine 类,事件循环 tick 驱动) wwwroot/play.html 游戏运行页(画布 + 日志 + 控件,验收入口) wwwroot/index.ui 模块入口页(bricks) wwwroot/api/*.dspy 可选服务端辅助(load_world / runtime_status / runtime_event / runtime_control) runtime/runtime_service.py 服务端辅助实现(ServerEnv 注册,无自有数据表,只读复用 world 模块) scripts/load_path.py RBAC 路径注册(any/logined 分层,禁通配符) ``` - 运行时引擎核心在**前端 JS**(浏览器本地执行,事件循环 + 实体状态 + 脚本执行)。 - 服务端辅助是**可选**的:`/runtime/api/load_world.dspy` 从 world 模块读取世界与实体; 后端不可用/查无数据时前端自动降级**独立模式**(W-10i),本地构建演示世界继续运行。 - 本模块为交互层模块,**无自有数据表**(无 models/、json/、init/)。 ## 功能点映射(W-10a ~ W-10i) | 功能点 | 实现位置 | 验收口径 | |--------|----------|----------| | W-10a 世界运行时初始化(构建运行态实体树) | runtime.js `initWorld/buildTree` + api/load_world.dspy | 开始玩即初始化实体树,实体可点击 | | W-10b 实体运行时状态管理 | `makeEntity` / state 机(idle/active/done/failed) | 点击后实体 state 变化并绘制 | | W-10c 脚本运行时执行调度 | `executeScript/executeAction`(与 script_engine 编译产物 actions 对接) | 点击触发脚本动作执行 | | W-10d 事件分发(路由到对应脚本) | `dispatchEvent/processEvents/_routeEvent` | click/timer/collide 事件路由到实体绑定脚本 | | W-10e 定时器管理 | `addTimer/processTimers`(tick 驱动,非 setInterval) | 定时器按间隔触发脚本 | | W-10f 碰撞检测 | `checkCollisions`(AABB 两两检测) | 移动后实体碰撞触发 onCollide | | W-10g 运行时暂停/恢复 | `pause/resume/stop` + api/runtime_control.dspy | 暂停后 tick 挂起,恢复继续 | | W-10h 错误处理与降级 | `handleError`(try/catch 捕获,单脚本失败不整页崩溃) | 故意抛错脚本 → 日志记录错误,引擎继续运行 | | W-10i 独立世界本地引擎 | `_localWorldEntities` + initWorld 降级分支 | 后端不可用仍可本地构建世界运行 | ## 脚本对接契约(script_engine 编译产物格式) ```json { "id": "script_click", "name": "点击响应", "actions": [ {"op": "log", "message": "..."}, {"op": "set_state", "state": "active"}, {"op": "move", "dx": 12, "dy": 0}, {"op": "emit", "event": "active_changed", "entity_id": "hero", "payload": {}}, {"op": "delay", "ms": 1000}, {"op": "fail", "message": "故意抛错"} ]} ``` 实体绑定脚本:`props.scripts = ["script_click", ...]`;碰撞回调:`props.onCollide = "script_id"`。 ## DSPY 端点 - `GET /runtime/api/load_world.dspy?world_id=xxx` → `{ok, degraded, mode, world, entities}` - `GET /runtime/api/runtime_status.dspy?runtime_id=xxx` → `{runtime_id, running, paused, mode, entities, events, errors}` - `POST /runtime/api/runtime_event.dspy`(event_type/entity_id/payload)→ `{ok, queued}` - `POST /runtime/api/runtime_control.dspy`(action=start/pause/resume)→ `{ok, action, running, paused}` ## RBAC 路径(scripts/load_path.py 已注册) - `any`:`/runtime/runtime.js` - `logined`:`/runtime`、`/runtime/index.ui`、`/runtime/play.html`、`/runtime/api/*.dspy`(4 个显式) ## 陷阱 1. **dspy 禁项**:dspy 内禁 import / f-string / print / uuid —— 全部用 ServerEnv 注入全局 (`load_world_runtime` 等),`return` 显式返回。 2. **三处同步注册**:新增服务端函数必须同步 ① runtime_service.py ② runtime/__init__.py ③ runtime/init.py `env.xxx = xxx`,漏一处 → NameError。 3. **取库名禁硬编码**:服务端辅助用 `ServerEnv().get_module_dbname('world')`,禁止写死 DBNAME。 4. **DictObject 不可 dict()**:读 world 记录用 `_row_to_dict` 逐字段 getattr。 5. **前端引擎不依赖 setInterval**:定时器/帧循环用 `setTimeout` 自调度 + tick 驱动, bricks script actiontype 禁止 fetch/setInterval(play.html 是独立 HTML,不受此限)。 6. **降级优先**:服务端任何异常都返回 `ok=True, degraded=True`,绝不抛 500 —— 前端据此切独立模式。