node-lab-mcp 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +156 -56
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -25646,18 +25646,20 @@ var AcademySink = class extends BenchNode {
25646
25646
  }
25647
25647
  };
25648
25648
  var ACADEMY_IN = [
25649
- { type: 0 /* Power */, name: "POWER IN 1" },
25650
- { type: 0 /* Power */, name: "POWER IN 2" },
25651
- { type: 1 /* Number */, name: "DATA IN X" },
25652
- { type: 1 /* Number */, name: "DATA IN Y" },
25653
- { type: 1 /* Number */, name: "DATA IN Z" }
25649
+ { type: 1 /* Number */, name: "DATA IN X1" },
25650
+ { type: 1 /* Number */, name: "DATA IN X2" },
25651
+ { type: 1 /* Number */, name: "DATA IN X3" },
25652
+ { type: 1 /* Number */, name: "DATA IN X4" },
25653
+ { type: 1 /* Number */, name: "DATA IN X5" },
25654
+ { type: 1 /* Number */, name: "DATA IN X6" }
25654
25655
  ];
25655
25656
  var ACADEMY_OUT = [
25656
- { type: 0 /* Power */, name: "POWER OUT 1" },
25657
- { type: 0 /* Power */, name: "POWER OUT 2" },
25658
- { type: 1 /* Number */, name: "DATA OUT A" },
25659
- { type: 1 /* Number */, name: "DATA OUT B" },
25660
- { type: 1 /* Number */, name: "DATA OUT C" }
25657
+ { type: 1 /* Number */, name: "DATA OUT A1" },
25658
+ { type: 1 /* Number */, name: "DATA OUT A2" },
25659
+ { type: 1 /* Number */, name: "DATA OUT A3" },
25660
+ { type: 1 /* Number */, name: "DATA OUT A4" },
25661
+ { type: 1 /* Number */, name: "DATA OUT A5" },
25662
+ { type: 1 /* Number */, name: "DATA OUT A6" }
25661
25663
  ];
25662
25664
  var TwoInGate = class extends BenchNode {
25663
25665
  a;
@@ -26013,10 +26015,10 @@ var Button = class extends BenchNode {
26013
26015
  }
26014
26016
  };
26015
26017
  var Joystick = class extends BenchNode {
26016
- constructor(id, twoAxis) {
26018
+ constructor(id, axis) {
26017
26019
  super(id);
26018
- this.twoAxis = twoAxis;
26019
- if (twoAxis) {
26020
+ this.axis = axis;
26021
+ if (axis === "xy" || axis === "cross") {
26020
26022
  this.addPort(1 /* Number */, false, "X+");
26021
26023
  this.addPort(1 /* Number */, false, "X-");
26022
26024
  this.addPort(1 /* Number */, false, "Y+");
@@ -26028,6 +26030,9 @@ var Joystick = class extends BenchNode {
26028
26030
  }
26029
26031
  x = 0;
26030
26032
  y = 0;
26033
+ get twoAxis() {
26034
+ return this.axis === "xy" || this.axis === "cross";
26035
+ }
26031
26036
  evaluate() {
26032
26037
  const DZ = 0.08;
26033
26038
  const dz = (t) => {
@@ -26036,11 +26041,14 @@ var Joystick = class extends BenchNode {
26036
26041
  };
26037
26042
  const pos = (t) => Math.round(Math.max(0, dz(t)) * 65535);
26038
26043
  const neg = (t) => Math.round(Math.max(0, -dz(t)) * 65535);
26039
- if (this.twoAxis) {
26044
+ if (this.axis === "xy" || this.axis === "cross") {
26040
26045
  this.outputs[0].value = pos(this.x);
26041
26046
  this.outputs[1].value = neg(this.x);
26042
26047
  this.outputs[2].value = pos(this.y);
26043
26048
  this.outputs[3].value = neg(this.y);
26049
+ } else if (this.axis === "x") {
26050
+ this.outputs[0].value = pos(this.x);
26051
+ this.outputs[1].value = neg(this.x);
26044
26052
  } else {
26045
26053
  this.outputs[0].value = pos(this.y);
26046
26054
  this.outputs[1].value = neg(this.y);
@@ -26155,6 +26163,13 @@ var LightDisplay = class extends BenchNode {
26155
26163
  evaluate() {
26156
26164
  }
26157
26165
  };
26166
+ var Notes = class extends BenchNode {
26167
+ text = "# Notes\n\nDouble-click to edit. Supports **markdown** \u2014 headings, lists, `code`, [links](https://example.com).";
26168
+ width = 280;
26169
+ height = 200;
26170
+ evaluate() {
26171
+ }
26172
+ };
26158
26173
  var Oscilloscope = class extends BenchNode {
26159
26174
  input;
26160
26175
  samples = [];
@@ -26551,11 +26566,49 @@ var VectorRotate = class extends BenchNode {
26551
26566
  }
26552
26567
  };
26553
26568
  var SHIP_THRUST_ACCEL = 6;
26554
- var SHIP_ANG_ACCEL = 2.5;
26569
+ var SHIP_ANG_ACCEL = 0.625;
26555
26570
  var SHIP_DRAG = 0;
26556
26571
  var SHIP_DAMPER_MAX = 3;
26557
26572
  var SHIP_ANG_DAMPER_MAX = 3;
26558
- var ASTEROID_LOCK_RADIUS = 4.5;
26573
+ var COMMON_ELEMENTS = [26, 14, 29, 22, 28];
26574
+ var RARE_ELEMENTS = [79, 78, 48, 82, 47, 92, 74];
26575
+ var MAGNETOSPHERE_M = 2500;
26576
+ var STATION_SAFE_M = 500;
26577
+ function pickComposition(pool) {
26578
+ const n = Math.random() < 0.5 ? 2 : 3;
26579
+ const picked = [];
26580
+ while (picked.length < n) {
26581
+ const e = pool[Math.floor(Math.random() * pool.length)];
26582
+ if (!picked.includes(e)) picked.push(e);
26583
+ }
26584
+ const weights = picked.map(() => 0.2 + Math.random());
26585
+ const sum = weights.reduce((a, b) => a + b, 0);
26586
+ const comp = {};
26587
+ picked.forEach((e, i) => comp[e] = Math.round(weights[i] / sum * 100) / 100);
26588
+ const total = Object.values(comp).reduce((a, b) => a + b, 0);
26589
+ const delta = Math.round((1 - total) * 100) / 100;
26590
+ if (delta !== 0) comp[picked[0]] = Math.round((comp[picked[0]] + delta) * 100) / 100;
26591
+ return comp;
26592
+ }
26593
+ function makeAsteroidField() {
26594
+ const out = [];
26595
+ const TOTAL = 400;
26596
+ for (let i = 0; i < TOTAL; i++) {
26597
+ const inner = i < 48;
26598
+ const rMin = inner ? 100 : STATION_SAFE_M;
26599
+ const rMax = inner ? STATION_SAFE_M : MAGNETOSPHERE_M - 100;
26600
+ const r = Math.cbrt(rMin ** 3 + Math.random() * (rMax ** 3 - rMin ** 3));
26601
+ const u = Math.random() * 2 - 1, phi = Math.random() * Math.PI * 2;
26602
+ const s = Math.sqrt(1 - u * u);
26603
+ const pos = vec(r * s * Math.cos(phi), r * u, r * s * Math.sin(phi), 0);
26604
+ const t = Math.random();
26605
+ const radius = 13 + t * 7;
26606
+ const mass = Math.round(30 + t * 70);
26607
+ const comp = pickComposition(inner ? COMMON_ELEMENTS : RARE_ELEMENTS);
26608
+ out.push({ id: `a${i}`, pos, vel: vec(), radius, mass, comp });
26609
+ }
26610
+ return out;
26611
+ }
26559
26612
  var STATION_LOCK_RADIUS = 14;
26560
26613
  var BEAM_SLEW = 5 * Math.PI / 180;
26561
26614
  var BEAM_GIMBAL_LIMIT = 80 * Math.PI / 180;
@@ -26578,9 +26631,13 @@ var SHIP_STATE = {
26578
26631
  orient: vec(0, 0, 0, 1),
26579
26632
  dir: vec(0, 0, 1, 0),
26580
26633
  asteroid: vec(),
26634
+ asteroidField: [],
26581
26635
  station: vec(),
26582
26636
  lockedTarget: null,
26583
- beam: { aim: vec(0, 0, 1, 0), massRange: false, spectro: false }
26637
+ beam: { aim: vec(0, 0, 1, 0), massRange: false, spectro: false },
26638
+ respawnAt: 0,
26639
+ magnetosphere: MAGNETOSPHERE_M,
26640
+ stationSafe: STATION_SAFE_M
26584
26641
  };
26585
26642
  var ShipSim = class {
26586
26643
  pos = vec(100, 0, 0, 0);
@@ -26591,10 +26648,11 @@ var ShipSim = class {
26591
26648
  // quaternion
26592
26649
  angVel = vec();
26593
26650
  // body-frame angular velocity (rad/s) — persists, so the ship coasts its spin
26594
- // ── world, station = origin (0,0,0). Ship starts at (100,0,0), so the station sits 100 m to port and the
26595
- // asteroid (86,25,30) is ~30 m ahead / 14 m to port / 25 m up of the ship's start. ──
26596
- asteroid = { pos: vec(86, 25, 30, 0), vel: vec(), mass: 30, comp: { 26: 0.9, 14: 0.1 } };
26597
- // 30 T · 90% Fe · 10% Si
26651
+ // ── world, station = origin (0,0,0). Ship starts at (100,0,0). 100 procedurally placed asteroids
26652
+ // (asteroidField) live around the station, inner shell (60–500 m) = common ores, outer shell
26653
+ // (500–2500 m) = rare. `asteroid` is the currently-active scan target (mutated by LOCK BEAM). ──
26654
+ asteroidField = makeAsteroidField();
26655
+ asteroid = this.asteroidField[0];
26598
26656
  station = { pos: vec(0, 0, 0, 0) };
26599
26657
  lockMode = 0;
26600
26658
  // 1 CLEAR · 2 LASER(mass/rock) · 3 PROCESSING PLANT(station) · 4 SHOP · 5 MAILING GUN · 6 CUSTOM (0 = none)
@@ -26628,20 +26686,22 @@ var ShipSim = class {
26628
26686
  const a = this.beam.aim;
26629
26687
  const d = qrotate(this.orient, qnormalize({ x: a.x, y: a.y, z: a.z, w: 0 }));
26630
26688
  let bestT = Infinity;
26631
- let best = null;
26632
- const ta = raySphereHit(this.pos, d, this.asteroid.pos, ASTEROID_LOCK_RADIUS);
26633
- if (ta !== null && ta < bestT) {
26634
- bestT = ta;
26635
- best = { pos: this.asteroid.pos, vel: this.asteroid.vel, kind: "asteroid" };
26689
+ let bestAst = null;
26690
+ for (const ast of this.asteroidField) {
26691
+ const t = raySphereHit(this.pos, d, ast.pos, ast.radius);
26692
+ if (t !== null && t < bestT) {
26693
+ bestT = t;
26694
+ bestAst = ast;
26695
+ }
26636
26696
  }
26637
26697
  const ts = raySphereHit(this.pos, d, this.station.pos, STATION_LOCK_RADIUS);
26638
26698
  if (ts !== null && ts < bestT) {
26639
- bestT = ts;
26640
- best = { pos: this.station.pos, vel: vec(), kind: "station" };
26641
- }
26642
- if (best) {
26643
- this.lockedTarget = { pos: { ...best.pos }, vel: { ...best.vel } };
26644
- this.lockKind = best.kind;
26699
+ this.lockedTarget = { pos: { ...this.station.pos }, vel: vec() };
26700
+ this.lockKind = "station";
26701
+ } else if (bestAst) {
26702
+ this.asteroid = bestAst;
26703
+ this.lockedTarget = { pos: { ...bestAst.pos }, vel: vec() };
26704
+ this.lockKind = "asteroid";
26645
26705
  } else {
26646
26706
  this.lockedTarget = null;
26647
26707
  this.lockKind = null;
@@ -26663,7 +26723,7 @@ var ShipSim = class {
26663
26723
  this.lockKind = "station";
26664
26724
  }
26665
26725
  /**
26666
- * LOCK MODE selects the target type. 6 CUSTOM tracks the CUSTOM TARGET vector live; the others apply on
26726
+ * LOCK MODE selects the target type. 6 CUSTOM tracks the CUSTOM TGT vector live; the others apply on
26667
26727
  * *change* (1 CLEAR wipes, 2 LASER → asteroid/rock, 3–5 → station), so a manual LOCK BEAM/STATION press can
26668
26728
  * override in between. 0 = no command (unwired) → leaves the current lock alone.
26669
26729
  */
@@ -26711,6 +26771,11 @@ var ShipSim = class {
26711
26771
  this.pos.x += this.vel.x * dt;
26712
26772
  this.pos.y += this.vel.y * dt;
26713
26773
  this.pos.z += this.vel.z * dt;
26774
+ const r = Math.hypot(this.pos.x, this.pos.y, this.pos.z);
26775
+ if (r > MAGNETOSPHERE_M) {
26776
+ this.reset();
26777
+ SHIP_STATE.respawnAt = Date.now();
26778
+ }
26714
26779
  const ta = SHIP_ANG_ACCEL;
26715
26780
  this.angVel.x += torqueBody.x * ta * dt;
26716
26781
  this.angVel.y += torqueBody.y * ta * dt;
@@ -26737,6 +26802,7 @@ var ShipSim = class {
26737
26802
  SHIP_STATE.orient = this.orient;
26738
26803
  SHIP_STATE.dir = this.direction();
26739
26804
  SHIP_STATE.asteroid = this.asteroid.pos;
26805
+ SHIP_STATE.asteroidField = this.asteroidField;
26740
26806
  SHIP_STATE.station = this.station.pos;
26741
26807
  SHIP_STATE.lockedTarget = this.lockedTarget ? this.lockedTarget.pos : null;
26742
26808
  SHIP_STATE.beam = this.beam;
@@ -26771,24 +26837,32 @@ var ProximitySensor = class extends BenchNode {
26771
26837
  this.out = this.addPort(1 /* Number */, false, "PROXIMITY");
26772
26838
  }
26773
26839
  evaluate() {
26774
- const p = SHIP_SIM.pos, a = SHIP_SIM.asteroid.pos, s = SHIP_SIM.station.pos;
26775
- const da = Math.hypot(p.x - a.x, p.y - a.y, p.z - a.z) - ASTEROID_LOCK_RADIUS;
26776
- const ds = Math.hypot(p.x - s.x, p.y - s.y, p.z - s.z) - STATION_LOCK_RADIUS;
26777
- const d = Math.max(0, Math.min(da, ds));
26840
+ const p = SHIP_SIM.pos, s = SHIP_SIM.station.pos;
26841
+ let best = Math.hypot(p.x - s.x, p.y - s.y, p.z - s.z) - STATION_LOCK_RADIUS;
26842
+ for (const ast of SHIP_SIM.asteroidField) {
26843
+ const d2 = Math.hypot(p.x - ast.pos.x, p.y - ast.pos.y, p.z - ast.pos.z) - ast.radius;
26844
+ if (d2 < best) best = d2;
26845
+ }
26846
+ const d = Math.max(0, best);
26778
26847
  this.out.value = d >= PROXIMITY_RANGE ? 0 : clampData((1 - d / PROXIMITY_RANGE) * DATA_MAX);
26779
26848
  }
26780
26849
  };
26781
26850
  var SHIP_IN_CONTROLS = [
26782
26851
  "THRUST",
26783
26852
  "REVERSE",
26853
+ // Order: X+, X-, Y+, Y- so a 2-axis joystick wires straight through (RIGHT→X+, LEFT→X-, UP→Y+, DOWN→Y-).
26854
+ "TRANSLATE RIGHT",
26855
+ "TRANSLATE LEFT",
26784
26856
  "TRANSLATE UP",
26785
26857
  "TRANSLATE DOWN",
26858
+ // YAW first then PITCH so X+/X- / Y+/Y- on a 2-axis joystick map straight onto YAW LEFT / YAW
26859
+ // RIGHT / PITCH UP / PITCH DOWN without crossed cables.
26860
+ "YAW RIGHT",
26861
+ "YAW LEFT",
26786
26862
  "PITCH UP",
26787
26863
  "PITCH DOWN",
26788
- "YAW LEFT",
26789
- "YAW RIGHT",
26790
- "ROLL LEFT",
26791
26864
  "ROLL RIGHT",
26865
+ "ROLL LEFT",
26792
26866
  "LINEAR DAMPER",
26793
26867
  "ANGULAR DAMPER"
26794
26868
  ];
@@ -26803,17 +26877,19 @@ var ShipIn = class extends BenchNode {
26803
26877
  evaluate() {
26804
26878
  }
26805
26879
  refresh() {
26880
+ const FWD = 1, REV = 0.5, STRAFE = 0.5;
26806
26881
  const thrustBody = vec(
26807
- 0,
26808
- this.ctl("TRANSLATE UP") - this.ctl("TRANSLATE DOWN"),
26809
- this.ctl("THRUST") - this.ctl("REVERSE"),
26882
+ (this.ctl("TRANSLATE LEFT") - this.ctl("TRANSLATE RIGHT")) * STRAFE,
26883
+ // camera looks +Z; +X is camera-left, so RIGHT must push body −X
26884
+ (this.ctl("TRANSLATE UP") - this.ctl("TRANSLATE DOWN")) * STRAFE,
26885
+ this.ctl("THRUST") * FWD - this.ctl("REVERSE") * REV,
26810
26886
  0
26811
26887
  );
26812
26888
  const torqueBody = vec(
26813
26889
  this.ctl("PITCH DOWN") - this.ctl("PITCH UP"),
26814
26890
  // pitch about X (DOWN − UP: nose-up is −X)
26815
- this.ctl("YAW RIGHT") - this.ctl("YAW LEFT"),
26816
- // yaw about Y
26891
+ this.ctl("YAW LEFT") - this.ctl("YAW RIGHT"),
26892
+ // yaw about Y (LEFT yaws nose left)
26817
26893
  this.ctl("ROLL RIGHT") - this.ctl("ROLL LEFT"),
26818
26894
  // roll about Z
26819
26895
  0
@@ -26824,7 +26900,7 @@ var ShipIn = class extends BenchNode {
26824
26900
  var SENSORS_IN_PORTS = [
26825
26901
  { type: 1 /* Number */, name: "LOCK MODE" },
26826
26902
  // 1 CLEAR … 6 CUSTOM
26827
- { type: 3 /* Vector */, name: "CUSTOM TARGET" },
26903
+ { type: 3 /* Vector */, name: "CUSTOM TGT" },
26828
26904
  // used when LOCK MODE = 6
26829
26905
  { type: 1 /* Number */, name: "BEAM X+" },
26830
26906
  { type: 1 /* Number */, name: "BEAM X-" },
@@ -26865,7 +26941,7 @@ var SensorsIn = class extends BenchNode {
26865
26941
  evaluate() {
26866
26942
  }
26867
26943
  refresh() {
26868
- SHIP_SIM.applyLockMode(Math.round(this.val("LOCK MODE")), this.vecIn("CUSTOM TARGET"));
26944
+ SHIP_SIM.applyLockMode(Math.round(this.val("LOCK MODE")), this.vecIn("CUSTOM TGT"));
26869
26945
  const yawRate = (this.val("BEAM X-") - this.val("BEAM X+")) / DATA_MAX;
26870
26946
  const pitchRate = (this.val("BEAM Y+") - this.val("BEAM Y-")) / DATA_MAX;
26871
26947
  SHIP_SIM.aimBeam(yawRate, pitchRate, Math.round(this.val("ELEMENT")), 0.02);
@@ -26898,13 +26974,22 @@ var SensorsOut = class extends BenchNode {
26898
26974
  this.out("LOCKED").value = 0;
26899
26975
  }
26900
26976
  const b = SHIP_SIM.beam;
26901
- const a = SHIP_SIM.asteroid;
26902
26977
  const bd = qrotate(SHIP_SIM.orient, qnormalize({ x: b.aim.x, y: b.aim.y, z: b.aim.z, w: 0 }));
26903
- const hit = raySphereHit(SHIP_SIM.pos, bd, a.pos, ASTEROID_LOCK_RADIUS);
26904
- this.out("MASS").value = b.massRange && hit !== null ? clampData(a.mass) : 0;
26905
- this.out("SCAN RANGE").value = b.massRange && hit !== null ? clampData(hit) : 0;
26906
- this.out("COMP %").value = b.spectro && hit !== null ? clampData((a.comp[b.element] ?? 0) * DATA_MAX) : 0;
26907
- this.out("SPECTRO POS").vec = { ...b.aim };
26978
+ let bestT = Infinity;
26979
+ let bestAst = null;
26980
+ for (const ast of SHIP_SIM.asteroidField) {
26981
+ const t = raySphereHit(SHIP_SIM.pos, bd, ast.pos, ast.radius);
26982
+ if (t !== null && t < bestT) {
26983
+ bestT = t;
26984
+ bestAst = ast;
26985
+ }
26986
+ }
26987
+ const aHit = bestAst;
26988
+ const hit = bestAst ? bestT : null;
26989
+ this.out("MASS").value = b.massRange && aHit !== null ? clampData(aHit.mass) : 0;
26990
+ this.out("SCAN RANGE").value = b.massRange && aHit !== null && hit !== null ? clampData(hit) : 0;
26991
+ this.out("COMP %").value = b.spectro && aHit !== null ? clampData((aHit.comp[b.element] ?? 0) * DATA_MAX) : 0;
26992
+ this.out("SPECTRO POS").vec = vec(b.aim.x * DATA_MAX, b.aim.y * DATA_MAX, b.aim.z * DATA_MAX, 0);
26908
26993
  }
26909
26994
  };
26910
26995
  var NODE_LIBRARY = [
@@ -26962,9 +27047,11 @@ var NODE_LIBRARY = [
26962
27047
  { kind: "slider", label: "SLIDER", group: "Controls", nodeType: "analog", make: (id) => new AnalogControl(id, false) },
26963
27048
  { kind: "button1", label: "BUTTON", group: "Controls", nodeType: "button", make: (id) => new Button(id, 1) },
26964
27049
  { kind: "button4", label: "4-BUTTON", group: "Controls", nodeType: "button", make: (id) => new Button(id, 4) },
26965
- { kind: "joy1", label: "1-AXIS JOYSTICK", group: "Controls", nodeType: "joystick", make: (id) => new Joystick(id, false) },
26966
- { kind: "joy2", label: "2-AXIS JOYSTICK", group: "Controls", nodeType: "joystick", make: (id) => new Joystick(id, true) },
26967
- { kind: "throttle", label: "THROTTLE", group: "Controls", nodeType: "throttle", make: (id) => new Joystick(id, false) },
27050
+ { kind: "joy1", label: "1-AXIS JOYSTICK Y", group: "Controls", nodeType: "joystick", make: (id) => new Joystick(id, "y") },
27051
+ { kind: "joy1x", label: "1-AXIS JOYSTICK X", group: "Controls", nodeType: "joystick", make: (id) => new Joystick(id, "x") },
27052
+ { kind: "joy2", label: "2-AXIS JOYSTICK", group: "Controls", nodeType: "joystick", make: (id) => new Joystick(id, "xy") },
27053
+ { kind: "joy2cross", label: "2-AXIS JOYSTICK +", group: "Controls", nodeType: "joystick", make: (id) => new Joystick(id, "cross") },
27054
+ { kind: "throttle", label: "THROTTLE", group: "Controls", nodeType: "throttle", make: (id) => new Joystick(id, "y") },
26968
27055
  { kind: "valuedisp", label: "VALUE DISPLAY", group: "Displays", nodeType: "readout", make: (id) => new ValueDisplay(id) },
26969
27056
  { kind: "elementdisp", label: "ELEMENT DISPLAY", group: "Displays", nodeType: "elementdisp", make: (id) => new ElementDisplay(id) },
26970
27057
  { kind: "counter", label: "COUNTER", group: "Displays", nodeType: "counter", make: (id) => new Counter(id) },
@@ -26973,6 +27060,7 @@ var NODE_LIBRARY = [
26973
27060
  { kind: "linearlight", label: "LINEAR LIGHT", group: "Displays", nodeType: "light", make: (id) => new LightDisplay(id, "linear") },
26974
27061
  { kind: "scope", label: "OSCILLOSCOPE", group: "Displays", nodeType: "scope", make: (id) => new Oscilloscope(id) },
26975
27062
  { kind: "speaker", label: "SPEAKER", group: "Displays", nodeType: "speaker", make: (id) => new Speaker(id) },
27063
+ { kind: "notes", label: "NOTE", group: "Helpers", nodeType: "notes", make: (id) => new Notes(id) },
26976
27064
  { kind: "sine", label: "SINE WAVE", group: "Signal", nodeType: "sine", make: (id) => new SineWave(id) },
26977
27065
  { kind: "triggered", label: "TRIGGERED", group: "Signal", make: (id) => new Triggered(id) },
26978
27066
  { kind: "proximity", label: "PROXIMITY SENSOR", group: "Sensors", make: (id) => new ProximitySensor(id) }
@@ -27207,6 +27295,18 @@ live(
27207
27295
  { nodeId: external_exports.string(), port: external_exports.string(), value: external_exports.number() },
27208
27296
  (a) => ({ cmd: "set_input", nodeId: a.nodeId, port: a.port, value: a.value })
27209
27297
  );
27298
+ live(
27299
+ "move_node",
27300
+ "Reposition a node on the canvas in flow-space coordinates (the same units that get_graph reports under position.x/y). Works on every node, fixtures included. Wires re-route automatically.",
27301
+ { id: external_exports.string(), x: external_exports.number(), y: external_exports.number() },
27302
+ (a) => ({ cmd: "move_node", id: a.id, x: a.x, y: a.y })
27303
+ );
27304
+ live(
27305
+ "set_collapsed",
27306
+ "Collapse a node down to just its header strip (collapsed=true) or restore the full card (collapsed=false). Purely a UI state \u2014 wiring, values, and engine behavior are unchanged.",
27307
+ { id: external_exports.string(), collapsed: external_exports.boolean() },
27308
+ (a) => ({ cmd: "set_collapsed", id: a.id, collapsed: a.collapsed })
27309
+ );
27210
27310
  live(
27211
27311
  "clear_graph",
27212
27312
  "Wipe all player-added nodes and cables on the live canvas back to the bare fixed walls.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-lab-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "MCP server to drive the IFR Node Lab graph from any MCP client (Claude / Codex / Gemini …) — locally or, via a PartyKit relay, against the deployed app.",
5
5
  "type": "module",
6
6
  "bin": {