Links
Tags
Creators
Details
1.2.4
Compatibility
Required content
Changes
👥 Multiplayer Collaboration / 多人协作
All 7 blocks now support real-time collaborative graph editing — multiple players can edit the same node graph simultaneously. / 全部 7 种方块支持多人实时协作编辑同一节点图。
| Feature / 功能 | Description / 说明 |
|---|---|
| 🖱️ Live Cursors / 实时光标 | Colored crosshairs with player names / 彩色十字准星 + 玩家名 |
| 📦 Remote Node Drag / 远程拖拽节点 | Smooth lerp animation on remote moves / 远程移动平滑插值动画 |
| 🔗 Wire Preview / 连线预览 | Live bezier curve while dragging wires / 拖拽连线时实时贝塞尔曲线 |
| 👤 Player List / 玩家列表 | Right-side vertical list, host highlighted / 右侧竖向列表,房主高亮 |
| 🔒 Node Lock / 节点锁定 | IMAGE nodes protected during pixel editing / 像素编辑时自动锁定 |
| ⚡ Join/Leave / 加入离开 | Appear immediately on open, disappear on close / 打开即现,关闭即消 |
| ✏️ Op-Based Editing / 操作同步 | GraphOp + OpExecutor model, server-authoritative ID allocation / 服务器权威 ID 分配 |
⚡ Architecture Refactoring / 架构重构
| Change / 改动 | Description / 说明 |
|---|---|
| 🖥️ Server-Authoritative Eval / 服务端评估 | Client-side GraphEvaluator removed. All evaluation runs server-side; results synced via ClientboundGraphEvalPacket + EvalSnapshot. Fixes PRIVATE_IN/BUS_IN always returning 0 on client. |
| 🏗️ Unified BE Base / 统一 BE 基类 | SyncedGraphBlockEntity consolidates ~200 lines duplicated across 7 BEs (BUS lifecycle, RedstoneLink, NBT, sync, EvalSnapshot). |
| 📦 Blob Data Channel / Blob 通道 | BlobDataPacket + BlobRegistry for chunked large data. SET_IMAGE_PIXELS from Base64 to direct int[]. |
🔧 Debug Toolchain / 调试工具链
| Tool / 工具 | Description / 说明 |
|---|---|
| 📶 Signal Generator / 信号发生器 | Test signal source. Manual curve mode (draggable control points, X-clamped, server-sorted) or custom f(x) formula (all math functions, auto full-width paren conversion). Frequency-generate (auto-cycling X) or input-driven (drag sky-blue marker). Auto-scale Y axis with ±5 outlier clipping. |
| 📊 Signal Probe / 信号探针 | Real-time monitor with 100-tick trend chart. Auto-scale Y axis with outlier clipping. Right-click freeze/unfreeze/clear. |
📌 View Bookmarks / 视角书签
- ★ button (bottom-right, above ▼) toggles bookmark panel / ★按钮开关书签面板
[+]/Ctrl+Msave current view,[↺]/Homereset to origin[✎]rename,[✕]delete,[→]or click name to jump with 200ms ease-in-out transition- Drag name area to reorder, synced via
MOVE_BOOKMARKop - Click outside naming dialog to cancel; Esc handled by unified popup stack
- Multiplayer-synced via
ADD_BOOKMARK/REMOVE_BOOKMARK/RENAME_BOOKMARK/MOVE_BOOKMARKops
🐛 Fixes & Polish / 修复与打磨
- 🔧 Graph Init —
onLoad()bumps generation to force full recompile on first tick. - 🚌 BUS Channel —
registerChannels()no longer requiresbandCount()>0; empty-band channels register so BUS_IN reads immediately. - 🎨 Color Picker UX — ESC closes picker + panels together. Duplicated nodes get
sortB = original+1. - 📝 Bilingual Comments — All
graph/,blocks/,network/source comments now Chinese+English. - 🐛 Encapsulation DEBUG Visibility —
EvalSnapshotnow captures sub-evaluator outputs + debugTimes. Signal Generator (blue X marker) and Signal Probe work correctly inside encapsulation sub-graphs. / 封装内信号发生器(蓝色X标记线)和探针现在正确显示。 - ⌨️ Esc Key Delegation — Esc now closes sub-UI (bookmark rename, export/import dialog, color picker) before closing the entire editor screen. / Esc 先关闭子 UI 再关整个编辑界面。
- 🔒 Soft-Lock Scope — Node locking now scoped by
ownerNodeId. Selecting a node inside encapsulation no longer falsely locks main-graph nodes with the same ID. / 封装内选中节点不再误锁主图同 ID 节点。 - 🖱️ Cursor Scope Isolation — Remote player cursors are now filtered by scope; cursors inside encapsulation are hidden from main-graph view and vice versa. / 远端光标按作用域隔离。
- 🟡 ENCAPSULATION Occupant Highlight — Golden border + player name label on ENCAPSULATION nodes in the main graph when other players are editing inside. / 主图中被占用的封装节点显示金色边框+玩家名。
- 📝 ENCAP I/O Rename Sync — Renaming
ENCAP_INPUT/ENCAP_OUTPUTnow sendsSET_DISPLAY_TEXTop for server sync + undo support. / 封装I/O改名现在同步到服务端并支持撤销。 - 📋 Ctrl+D Copy Fix — Copy now uses server-authoritative ID allocation (
ADD_NODE_REQUEST→ ACK); data ops are deferred until all real IDs assigned. Sub-graph content recursively synced for ENCAPSULATION nodes. Fixes "empty node on other clients". / 复制走服务端权威ID分配,封装子图递归同步。 - 📐 Manual Curve Fixed Y-Axis — Signal Generator manual curve mode now uses fixed Y range
[-1.1, 1.1]; auto-scaling retained for formula mode. Control points clamped to visible range and rendered above border. / 手动曲线Y轴固定,控制点钳制+边框上方渲染。
🔗 Stable PinId Refactoring / 稳定引脚ID重构
Connections now bind to stable string pin identifiers instead of positional integer indices. Pin insertion, deletion, or reordering no longer breaks existing connections — they follow the pin by name.
| System / 系统 | pinId Source / pinId 来源 | Before / 修复前 | After / 修复后 |
|---|---|---|---|
| FORMULA inputs | Variable name (e.g. A, B, x) |
Adding/removing variables shifted pin indices — connections broke or pointed to wrong pins | Connections follow variable names; ensureScriptParsed() eliminates lazy-parse race conditions |
| FORMULA outputs | @output label (e.g. result, angle) |
Output reordering broke downstream connections | Connections track output labels; "out0" default handled correctly |
| ENCAPSULATION I/O | Sub-node ID (sorted by Y, then ID) | Dragging ENCAP_INPUT/OUTPUT nodes changed pin order — external connections silently shifted | pinId = sub-node ID, invariant under drag; parent cache rebuilt after sub-graph structural edits |
| BUS bands | Band name (e.g. band_0, band_1) |
Inserting/removing/reordering bands cleared all connections or caused index drift | Only connections on actually-deleted bands are removed; reordered bands preserved |
Key changes:
NodeConnectiongainsfromPinId/toPinIdfields;save()/load()backward-compatibleGraphNodeaddsinputPinIndex(id)/outputPinIndex(id)/inputPinId(i)/outputPinId(i)— pinId↔index resolution per node typeNodeGraph.rebuildInputCache()resolves all pinIds to current indices, prunes stale connectionsGraphMigrationV3→V4: one-time NBT upgrade converting integer pins to stable pinIds for FORMULA, ENCAP, BUS, and generic nodes (recursive into sub-graphs)GraphEvaluatorBUS evaluation and ENCAP pin injection now match by pinId rather than cache positionBusChannelHelper.syncBandsFromServeronly disconnects actually-removed bands (by name), preserving reordered bandsNbtVersions.DATA_VERSIONbumped 3→4- Eliminates ~200 lines of REWIRE/reconnect complexity from the v1.2.5 roadmap — pin reordering is now free
Related docs: docs/v1.2.4-pin-id-stability-plan.md
🧠 Relay Nodes / 继电器节点
Two new logic nodes for conditional signal routing — available in both Blueprint and Program Computers.
| Node / 节点 | Description / 说明 |
|---|---|
| Relay A / 继电器A | SPDT (双掷) — 3 inputs (A, B, Contact), 2 outputs. Contact ≤0.5 → A输出=A, B输出=0; Contact >0.5 → A输出=0, B输出=B. Mutually exclusive throws like a physical relay. |
| Relay B / 继电器B | SPST (单掷) — 3 inputs (A, B, Contact), 1 output. Contact >0.5 → out=B; else → out=A. Merged single-throw variant. |
Both use the standard >0.5 threshold consistent with BOOL/GATE/OR. No parameters, pure combinatorial logic — compatible with multiplayer collaboration and encapsulation out of the box.
🪑 Sable Sub-Level Control Seat Camera / Sable 子关卡控制座椅相机
Two camera modes for Control Seat riders inside Sable rotating structures. Camera orientation computed client-side from block FACING + Sable render-pose quaternion — bypasses entity yaw sync (unreliable for retain_in_sub_level entities).
Sable 旋转结构上控制座椅的两种相机模式。座椅世界朝向由客户端根据方块 FACING + Sable 渲染姿态四元数实时计算,不依赖 entity yaw 同步。
| Mode / 模式 | Camera / 相机 | Output / 输出 |
|---|---|---|
| FIXED (default) | Locked to seat world orientation (yaw + pitch). ControlSeatCameraMixin disables Sable camera rotation to prevent double-rotation. / 锁定到座椅世界朝向(偏航+俯仰) |
Joystick mx/my from raw mouse delta / 摇杆来自鼠标增量 |
| VIEW_DIFFERENCE | Free camera, mouse-controlled. Sable does not rotate the view. / 自由相机,鼠标控制 | vy = playerYaw - seatWorldYaw, vp = playerPitch - seatWorldPitch |
ControlSeatBlockEntitySable: fixed missingsetYRot()— entity yaw now properly updates bothyRotandyRotO. / 修复缺失的 setYRot()。ControlSeatEntity: manualsetPosskipped inside sub-levels (Sable handles positioning). / 子关卡内跳过手动 setPos。ControlSeatCameraMixin: registered inrequired:falsesable mixin config — silently skipped without Sable. / 无 Sable 时静默跳过。
🎮 MOUSE_JOYSTICK Absolute Mode / 鼠标摇杆绝对值模式
Per-node toggle in the edit panel switches between incremental (direct mouse delta, default) and absolute (accumulated stick position with memory, clamped to [-1,1]).
编辑区提供每节点独立开关,在增量(默认,鼠标位移即输出)和绝对值(累积摇杆位置,停手保持)之间切换。
- Uses
TOGGLE_BOOLop pipeline — server-authoritative, undo-supported, multiplayer-synced. / 复用 BOOL 开关流水线。 - Absolute accumulation uses
ABS_SCALE=1/6per tick for smooth control. / 绝对值每 tick 累积系数 1/6。 - Stick position persists across GUI open/close. / 开菜单再关闭位置保持。
- Both x and y axes accumulate independently. / X/Y 轴独立累积。
🔧 Signal Generator Auto-Scale Fix / 信号发生器自动缩放修复
- Root cause:
computeVisibleRange()used fixed ±5 clipping, squashing large-range formulas (e.g.x*360) into a ~6-unit Y window while rendering used raw values → curve painted to chart corners looking like an inverse-proportional function. - Fix: Replaced ±5 hard clipping with percentile-based robust range (p1–p99). Only extreme outliers (|v| ≥ 1e6) and NaN/Inf are filtered. Both
DEBUG_SIGNAL_GENandDEBUG_PROBEcharts use the same logic. - Y-axis range label: Chart top-right now shows
min … max(e.g.0.0 … 360.0) so the scale is immediately visible. - Cache: Formula compilation cache (
debugFormulaRpn) properly invalidated on formula edits — chart refreshes instantly.
🎨 Formula Editor UX / 公式编辑器体验
Syntax Highlighting / 语法高亮 — Real-time token-based colouring with 9 categories: functions (yellow), constants (pink), identifiers (cyan), numbers (orange), operators/parens (grey), comments (green), @output/assignment (purple), unknown (red underline). Token cache avoids per-frame re-parsing. / 实时词法彩色标注,9 种分类:函数(黄)、常量(粉)、标识符(青)、数字(橙)、运算符/括号(灰)、注释(绿)、@output/赋值(紫)、未知(红色下划线)。Token 缓存避免每帧重复解析。
Autocomplete Popup / 自动补全候选框 — Type identifier characters to trigger filtered dropdown (functions, named constants, current variables); type @ for immediate @output. Keyboard: ↑↓ navigate, Tab/Enter accept, Esc dismiss. Mouse: click any candidate to accept. Popup renders above all pins (z-layer C=5.5) with zoom-aware scaling — text and layout scale proportionally with graph zoom. Deleted variables immediately disappear from suggestions. / 输入标识符触发过滤候选框;输入 @ 立即建议 @output。键盘导航/接受/关闭,点击候选项接受。候选框渲染在所有引脚上方(C=5.5 层),支持缩放感知。删除变量立即从候选消失。
Real-Time Validation / 实时校验 — Red ⚠ badge on FORMULA node title bar; hover for tooltip list. Checks: bracket matching, unknown function, wrong arity, invalid assignment (errors), duplicate outputs, @output without identifier (warnings). Red border on the edit box when errors present. Validation cached after NBT reload to avoid per-frame re-parse. / 红色 ⚠ 徽章 + 悬停工具提示。校验:括号匹配、未知函数、参数数错误、无效赋值(错误),重复输出名、@output 缺变量(警告)。输入框红色边框。NBT 重载后缓存避免每帧重解析。
Named Constants / 命名常量 — (PI) and (E) in grouping parentheses are literal constants (π / e). Bare PI/E or inside function calls like sin(PI) are variable references (create input pins). Consistent across extractVariables(), compile(), and tokenize(). / (PI)/(E) 在分组括号内为字面常量。裸 PI/E 或函数调用内 sin(PI) 视为变量。三种解析入口行为一致。
Robustness / 健壮性 — Mouse drag selection restored (hlPos sync scoped). SET_FORMULA self-skip prevents EditState recreation → no focus loss during typing. Formula responder re-fetches graph node each keystroke (handles NBT sync between keystrokes). Connection cleanup corrected to use inputs()/outputs() clamps. Screen-width hardcoded 1920 replaced with getGuiScaledWidth(). Null safety in suggestion filtering. / 拖拽选区修复、SET_FORMULA 自跳、按键间图引用重获取、连线清理用 clamp 值、屏幕宽度动态获取、候选过滤 null 防护。
Tests / 测试 — 27 new unit tests covering tokenize, extractVariables, compile/evaluate, validate, parseScript, countFunctionArgs, and edge cases. / 27 个新单元测试。
Optional dependencies
Projects on Modrinth are automatically available through a Maven repository for use with JVM build tools such as Gradle. To learn more about the Modrinth Maven API, click here.
Note: When available, you should use the creator's maven repo instead as it will have transitive dependency information that the Modrinth Maven API does not. You may also end up with duplicate dependencies if you use a mix of Modrinth and non-Modrinth Maven repositories for your dependencies, because the group identifier will be different when served through the Modrinth Maven API.
Maven coordinates:
Version ID:
build.gradle:
repositories {
exclusiveContent {
forRepository {
maven {
name = "Modrinth"
url = "https://api.modrinth.com/maven"
}
}
// forRepositories(fg.repository) // Uncomment when using ForgeGradle
filter {
includeGroup "maven.modrinth"
}
}
}
// Standard Gradle dependency
dependencies {
implementation "maven.modrinth:1bUvJALH:46E2jV1F"
}
// Legacy Loom dependency
dependencies {
modImplementation "maven.modrinth:1bUvJALH:46E2jV1F"
}

