@vibecook/ice 0.8.1 → 0.10.0

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.
@@ -1623,6 +1623,10 @@ function defaultsOf(schema) {
1623
1623
  for (const [name, spec] of Object.entries(schema)) out[name] = defaultValueOf(spec);
1624
1624
  return out;
1625
1625
  }
1626
+ var utf8 = new TextEncoder();
1627
+ function facetBytesOf(cell) {
1628
+ return utf8.encode(JSON.stringify(cell)).length;
1629
+ }
1626
1630
  function signatureOf(spec, phase2) {
1627
1631
  const schema = spec.schema ?? {};
1628
1632
  return JSON.stringify({
@@ -1632,6 +1636,11 @@ function signatureOf(spec, phase2) {
1632
1636
  version: spec.version ?? 1,
1633
1637
  phase: phase2,
1634
1638
  tickWhile: spec.tick?.while ?? "all",
1639
+ // The I19 byte claim is IDENTITY-BEARING: a redefinition that changes only
1640
+ // the claim is a different declaration (hosts compare descriptors; the
1641
+ // ensure-cache must agree). `?? null` because JSON.stringify drops
1642
+ // undefined-valued keys, which would alias "no claim" with "absent field".
1643
+ maxFacetBytes: spec.maxFacetBytes ?? null,
1635
1644
  // Field ORDER is part of the shape: it is the generated component's column
1636
1645
  // order, which the durable cell layout depends on.
1637
1646
  schema: Object.entries(schema).map(([k, s]) => [k, s.kind, defaultValueOf(s)]),
@@ -1679,6 +1688,17 @@ function validate(name, spec, phase2) {
1679
1688
  if (spec.budgetMs !== void 0 && (!Number.isFinite(spec.budgetMs) || spec.budgetMs <= 0)) {
1680
1689
  fail2(name, `budgetMs must be a positive number (got ${String(spec.budgetMs)}).`);
1681
1690
  }
1691
+ if (spec.maxFacetBytes !== void 0) {
1692
+ if (spec.store !== "ephemeral") {
1693
+ fail2(
1694
+ name,
1695
+ `"maxFacetBytes" is ephemeral-only \u2014 it bounds the published presence facet, and only ephemeral behaviors publish one (petition I19).`
1696
+ );
1697
+ }
1698
+ if (!Number.isSafeInteger(spec.maxFacetBytes) || spec.maxFacetBytes <= 0) {
1699
+ fail2(name, `maxFacetBytes must be a positive integer byte count (got ${String(spec.maxFacetBytes)}).`);
1700
+ }
1701
+ }
1682
1702
  for (const [fieldName, s] of Object.entries(spec.schema ?? {})) {
1683
1703
  if (fieldName.length === 0) fail2(name, "schema field names must be non-empty.");
1684
1704
  if (typeof s !== "object" || s === null || typeof s.kind !== "string") {
@@ -1817,6 +1837,7 @@ function describeBehavior(behavior) {
1817
1837
  version: behavior.version,
1818
1838
  phase: behavior.phase,
1819
1839
  ...behavior.budgetMs === void 0 ? {} : { budgetMs: behavior.budgetMs },
1840
+ ...behavior.maxFacetBytes === void 0 ? {} : { maxFacetBytes: behavior.maxFacetBytes },
1820
1841
  tickWhile: behavior.tickWhile,
1821
1842
  schema: Object.entries(behavior.schema).map(([name, spec]) => ({
1822
1843
  name,
@@ -1845,6 +1866,15 @@ function defineBehavior(name, spec) {
1845
1866
  return existing;
1846
1867
  }
1847
1868
  const schema = spec.schema ?? {};
1869
+ const defaults = defaultsOf(schema);
1870
+ if (spec.maxFacetBytes !== void 0 && spec.store === "ephemeral") {
1871
+ const bytes = facetBytesOf(defaults);
1872
+ if (bytes > spec.maxFacetBytes) {
1873
+ throw new Error(
1874
+ `ice: defineBehavior("${name}") \u2014 the default facet is ${bytes} bytes, over the behavior's own maxFacetBytes=${spec.maxFacetBytes} claim. Nothing was registered.`
1875
+ );
1876
+ }
1877
+ }
1848
1878
  const raw = {};
1849
1879
  for (const [fieldName, s] of Object.entries(schema)) raw[fieldName] = strataFieldOf(name, fieldName, s);
1850
1880
  const component = ensureComponent(behaviorComponentName(name), raw);
@@ -1857,9 +1887,10 @@ function defineBehavior(name, spec) {
1857
1887
  version: spec.version ?? 1,
1858
1888
  phase: phase2,
1859
1889
  budgetMs: spec.budgetMs,
1890
+ maxFacetBytes: spec.maxFacetBytes,
1860
1891
  schema,
1861
1892
  component,
1862
- defaults: defaultsOf(schema),
1893
+ defaults,
1863
1894
  reads: spec.reads ?? [],
1864
1895
  writes: spec.writes ?? [],
1865
1896
  migrate: spec.migrate ?? {},
@@ -8486,6 +8517,15 @@ function createBehaviorRuntime(opts) {
8486
8517
  if (!world.isAlive(peer)) return;
8487
8518
  if (this.hookEntity !== void 0 && this.hookEntity !== peer) return;
8488
8519
  const cell = cellForBehavior(world, this.b, peer, patch);
8520
+ const bound = this.b.maxFacetBytes;
8521
+ if (bound !== void 0) {
8522
+ const bytes = facetBytesOf(cell);
8523
+ if (bytes > bound) {
8524
+ throw new Error(
8525
+ `ice: behavior "${this.b.name}" ctx.write produced a ${bytes}-byte facet, over its declared maxFacetBytes=${bound} \u2014 the write was refused and the prior facet left intact.`
8526
+ );
8527
+ }
8528
+ }
8489
8529
  if (world.has(peer, this.own)) presence.eph.edit(peer).set(this.own, cell);
8490
8530
  else presence.eph.addComponent(peer, this.own, cell);
8491
8531
  }
@@ -8627,6 +8667,11 @@ function createBehaviorRuntime(opts) {
8627
8667
  },
8628
8668
  attach(e, behavior, data) {
8629
8669
  const other = behavior;
8670
+ if (other.store === "ephemeral") {
8671
+ throw new Error(
8672
+ `ice: behavior "${node.b.name}" called tx.attach("${other.name}") \u2014 an ephemeral behavior's facet is presence data and cannot be attached into the document.`
8673
+ );
8674
+ }
8630
8675
  const cell = cellForBehavior(world, other, e, data ?? {});
8631
8676
  if (world.has(e, other.component)) {
8632
8677
  tx.edit(e).set(other.component, cell);
@@ -8635,6 +8680,11 @@ function createBehaviorRuntime(opts) {
8635
8680
  }
8636
8681
  },
8637
8682
  detach(e, behavior) {
8683
+ if (behavior.store === "ephemeral") {
8684
+ throw new Error(
8685
+ `ice: behavior "${node.b.name}" called tx.detach("${behavior.name}") \u2014 ephemeral facets are withdrawn by the runtime (I17) or the presence layer's tombstones, never through the document.`
8686
+ );
8687
+ }
8638
8688
  tx.removeComponent(e, behavior.component);
8639
8689
  }
8640
8690
  };
@@ -8710,6 +8760,11 @@ function createBehaviorRuntime(opts) {
8710
8760
  `ice: engine.behaviors.attach("${b.name}") \u2014 durable attachment is a DOCUMENT op: use tx.attach inside a transaction so it syncs and undoes.`
8711
8761
  );
8712
8762
  }
8763
+ if (b.store === "ephemeral") {
8764
+ throw new Error(
8765
+ `ice: engine.behaviors.attach("${b.name}") \u2014 an ephemeral behavior's facet is minted by the runtime on the local peer; it cannot be attached to an entity.`
8766
+ );
8767
+ }
8713
8768
  const cell = cellForBehavior(world, b, e, data ?? {});
8714
8769
  if (world.has(e, b.component)) world.edit(e).set(b.component, cell);
8715
8770
  else world.addComponent(e, b.component, cell);
@@ -8720,6 +8775,11 @@ function createBehaviorRuntime(opts) {
8720
8775
  `ice: engine.behaviors.detach("${b.name}") \u2014 durable detachment is a DOCUMENT op: use tx.detach inside a transaction.`
8721
8776
  );
8722
8777
  }
8778
+ if (b.store === "ephemeral") {
8779
+ throw new Error(
8780
+ `ice: engine.behaviors.detach("${b.name}") \u2014 ephemeral facets are withdrawn by the runtime (I17) or the presence layer's tombstones, never detached through the world.`
8781
+ );
8782
+ }
8723
8783
  if (world.has(e, b.component)) world.removeComponent(e, b.component);
8724
8784
  },
8725
8785
  has: (e, b) => world.isAlive(e) && world.has(e, b.component),
@@ -9430,6 +9490,20 @@ function installWidgetRuntime(engine, opts = {}) {
9430
9490
  }
9431
9491
 
9432
9492
  // ../core/src/settings/ground-config.ts
9493
+ var DEFAULT_GRID_MAGNET_CONFIG = {
9494
+ enabled: false,
9495
+ glyph: "needle",
9496
+ reach: 60,
9497
+ polarity: 1,
9498
+ widgets: true,
9499
+ widgetStrength: 1,
9500
+ widgetRadius: 12,
9501
+ alwaysAlign: false,
9502
+ needleLength: 5,
9503
+ needleWidth: 0.55,
9504
+ maxSources: 256,
9505
+ fadeZoom: 0
9506
+ };
9433
9507
  var DEFAULT_GRID_CONFIG = {
9434
9508
  spacings: [20, 100, 500],
9435
9509
  dotColor: [0.75, 0.77, 0.8],
@@ -9986,7 +10060,7 @@ var K_SNAPSHOT_OFFER = 2;
9986
10060
  var K_SNAPSHOT = 3;
9987
10061
  var K_UPDATE = 4;
9988
10062
  var K_PRESENCE = 5;
9989
- var utf8 = new TextEncoder();
10063
+ var utf82 = new TextEncoder();
9990
10064
  var fromUtf8 = new TextDecoder();
9991
10065
  var defaultClock = {
9992
10066
  setTimeout: (fn, ms) => setTimeout(fn, ms),
@@ -10000,7 +10074,7 @@ function frame(kind, payload) {
10000
10074
  return out;
10001
10075
  }
10002
10076
  function frameSnapshot(targetId, snapshot) {
10003
- const id = utf8.encode(targetId);
10077
+ const id = utf82.encode(targetId);
10004
10078
  const out = new Uint8Array(1 + 2 + id.length + snapshot.length);
10005
10079
  out[0] = K_SNAPSHOT;
10006
10080
  out[1] = id.length & 255;
@@ -10174,7 +10248,7 @@ function joinDoc(world, channel, opts = {}) {
10174
10248
  active = false;
10175
10249
  buffered.length = 0;
10176
10250
  opts.onSession?.(void 0);
10177
- send(frame(K_HELLO, utf8.encode(joinerId)));
10251
+ send(frame(K_HELLO, utf82.encode(joinerId)));
10178
10252
  armTimeout();
10179
10253
  };
10180
10254
  const onFrame = (bytes) => {
@@ -10222,7 +10296,7 @@ function joinDoc(world, channel, opts = {}) {
10222
10296
  if (unsubPresence !== void 0) roomSubs.push(unsubPresence);
10223
10297
  const begin = () => {
10224
10298
  if (left) return;
10225
- send(frame(K_HELLO, utf8.encode(joinerId)));
10299
+ send(frame(K_HELLO, utf82.encode(joinerId)));
10226
10300
  if (left) return;
10227
10301
  armTimeout();
10228
10302
  };
@@ -10844,7 +10918,7 @@ import {
10844
10918
  Local as Local3
10845
10919
  } from "@vibecook/strata-ecs";
10846
10920
  import { DurableUndoStatus } from "@vibecook/strata-ecs/durable";
10847
- var CORE_VERSION = "0.8.1";
10921
+ var CORE_VERSION = "0.10.0";
10848
10922
 
10849
10923
  export {
10850
10924
  defineSchemaOnce,
@@ -11098,6 +11172,7 @@ export {
11098
11172
  createMeasureIngest,
11099
11173
  createWidgetRuntime,
11100
11174
  installWidgetRuntime,
11175
+ DEFAULT_GRID_MAGNET_CONFIG,
11101
11176
  DEFAULT_GRID_CONFIG,
11102
11177
  DEFAULT_WIRES_CONFIG,
11103
11178
  DEFAULT_SNAP_GUIDES_CONFIG,
@@ -11129,4 +11204,4 @@ export {
11129
11204
  Local3 as Local,
11130
11205
  DurableUndoStatus
11131
11206
  };
11132
- //# sourceMappingURL=chunk-VITC7U3J.js.map
11207
+ //# sourceMappingURL=chunk-5FQS7V2C.js.map