Skip to content

Rework node layout and bit width resolution, and add the Const/AndN/OrN intrinsics - #60

Merged
chelproc merged 8 commits into
mainfrom
20260606
Sep 19, 2026
Merged

chelproc merged 8 commits into
mainfrom
20260606

Conversation

@chelproc

@chelproc chelproc commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Two refactors and the features built on top of them: the node renderer is split into a layout and a geometry step, bit widths are resolved per node instead of per component pin, and on that base the Const, AndN and OrN intrinsics, display config editing and a play-mode guard are added.

Node renderer: layout → geometry split

  • A layout (size + relative nodePinOffsetById) is separated from a geometry (absolute rect + nodePinPositionById). ccComponentEditorRendererLayoutToGeometry turns one into the other with the position of the node, and getCCComponentEditorRendererNodeGeometry composes the two steps.
  • A layout calculator now receives the config of the node instead of its position, so a node can size itself from its config.
  • The node wrapper is an <svg> instead of a <g>, so a renderer draws in local coordinates and can use percentage sizes (width="100%" in the Default renderer). Display and Const compose the Default renderer for their frame instead of drawing their own.
  • box-sizing: border-box is applied globally, which the HTML embedded in a foreignObject relies on.

Bit width resolution

  • getComponentPinBitWidthStatus only knew the component pin definition, so a fixed width was evaluated against definition.initialConfig and changing the resolution of a display never changed the width of its Pixels pin. Aggregate and decompose could not be evaluated there at all and were special cased into a manual fix mode, with their widths recomputed by hand in CCNodePinStore.
  • The two concerns are now split: getComponentPinBitWidthStatus reports only how a width is determined (automatic or nodeDependent), and getNodePinBitWidthStatus resolves it from the config of the node and the bit widths manually specified for its pins. The fixed policy is renamed to calculated to match.
  • isBitWidthConfigurable and isSplittable are folded into the configurable policy, so the policy is the only source of truth. The hard-coded cases for aggregate, decompose and broadcast are gone: manual widths are collected per pin key (In, Out, …) and passed to calculateBitWidth, for which intrinsic pin attributes now carry their key.
  • Results are cached in CCNodePinStore and dropped on node pin and connection changes, and on the new didUpdateConfig event of CCNodeStore, which is kept separate from didUpdate so that frequent position updates do not drop the cache.
  • Because a width can now change after a connection was made, hasCompatibleBitWidths backs isConnectable, connections whose ends no longer agree after a config change are dropped, and existing connections are re-validated when a new one is registered.

New intrinsics

  • Constconfig: { mode, data }, an output whose width is calculated from data.length, and an evaluate that writes the configured data to the output pin. The node renders a text editor whose contents are parsed into data on every keystroke, in one of three modes (binary, hex, UTF-8) chosen by a toggle group and kept in the config. Text that is not valid in the current mode only marks the editor and leaves the last valid data in place.
  • AndN / OrN — reduce every bit of their configurable, splittable input to a single output bit.
  • Intrinsic evaluate functions receive the config of the node as a fourth argument.

Display

  • A settings popover on the node edits the resolution and persists it through store.nodes.update.
  • The layout scales with config.resolution and shares its constants with the renderer.
  • Pixel fill indexes inputValue in row-major order, fixing the previously reversed pixel order.

Play mode and simulation

  • isEachInputPinConnected and the useCanSimulate hook disable the play button of the ViewModeSwitcher while any input pin is unconnected.
  • executeSimulation no longer subscribes to raw node and connection store events.
  • Input values are keyed by { componentPinId, timeStep } with an explicit serializeInputValueKey instead of JSON.stringify of a tuple, and an initial value takes its width from the node pin implementing the component pin.

Tests

nodePin.test.ts covers bit width resolution (from a display config, propagated over a connection, summed over the manual widths of aggregate and decompose pins, broadcast, and a configurable pin missing its manual width) and the consistency of connections across a config change.

Open points

  • Dropping a connection on a config change can invalidate another one; resolving that needs a repeated sweep (TODO in connection.ts).
  • The re-validation on didRegister in CCConnectionStore looks up nodes.getManyByComponentId(connection.parentComponentId), which returns the instances of the parent component rather than the nodes inside it, so it is currently a no-op for the component being edited. Making it match, without guarding against re-registering from inside the handler, would recurse.
  • The Const layout calculator still carries the grid constants copied from Display, and the width of its foreignObject subtracts a hard-coded 30 for the output pin.

Separate node layout (relative pin offsets) from geometry (absolute
positions), replacing the single geometry calculator with a
layout → geometry two-step. Switch node SVG wrapper from <g> to <svg>
so child elements can use percentage dimensions. Update Display node
to compute its size from config resolution and add a config settings
button stub. Disable the ViewModeSwitcher play button when any input
pin is unconnected. Remove redundant node/connection event listeners
from the simulation trigger.

Key changes covered:
- Layout / LayoutSource types split out from Geometry, with nodePinOffsetById replacing absolute positions
- <g> → <svg> in Node/index.tsx so Default can use width="100%"
- Display/geometry.ts now scales with config.resolution and exports layout constants
- New ConfigSettingButton component for Display nodes
- isEachInputPinConnected utility in component.ts gating the play button
- executeSimulation decoupled from raw store events in the core slice
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 6, 2026

Copy link
Copy Markdown

Deploying create-cpu-c8345196-9409-4cce-afc4-389f413ad8ee with  Cloudflare Pages  Cloudflare Pages

Latest commit: e293840
Status: ✅  Deploy successful!
Preview URL: https://c4f9f4d5.create-cpu-c8345196-9409-4cce-afc4-389f413ad8ee.pages.dev
Branch Preview URL: https://20260606.create-cpu-c8345196-9409-4cce-afc4-389f413ad8ee.pages.dev

View logs

chelproc and others added 7 commits June 14, 2026 18:21
  - Make the display ConfigSettingButton functional: open a Popover form
    to edit resolution (x/y) and persist via store.nodes.update
  - Compute display node size from config inside the layout calculator
  - Cache getNodePinBitWidthStatus results, invalidating on node/pin/
    connection register/unregister/update events
  - Switch input value bit-width init to getNodePinBitWidthStatus and
    deprecate getComponentPinBitWidthStatus
  - Convert InputValueKey from a tuple to a { componentPinId, timeStep }
    object with a serializeInputValueKey helper
  - Add a reactive useCanSimulate selector and use it to drive the
    ViewModeSwitcher disabled state
- Add CONST intrinsic component with fixed-bit-width output driven by
  configured data, plus a dedicated renderer and layout calculator
- Pass node config to intrinsic evaluate functions
- Fix Display renderer pixel indexing to use row-major order directly
- Remove FlipFlop debug log; log pin bit widths on fixed-width mismatch
The bit width of a pin was resolved by getComponentPinBitWidthStatus,
which only knows the component pin definition. A `fixed` policy was
therefore evaluated against `definition.initialConfig`, so changing the
resolution of a display never changed the bit width of its Pixels pin.
For the same reason aggregate and decompose could not be evaluated
there at all and were special cased into a `manual` fix mode, with
their widths recomputed by hand in CCNodePinStore.

Split the two concerns: getComponentPinBitWidthStatus now only reports
how a width is determined (`automatic` or `nodeDependent`), and
getNodePinBitWidthStatus resolves it from the config of the node and
the manual bit widths of its pins. The `fixed` policy is renamed to
`calculated` to match, and the duplicated per-node calculations for
aggregate and decompose are gone.

Bit widths are cached, so CCNodeStore now emits `didUpdateConfig`, kept
separate from `didUpdate` so that frequent position updates do not drop
the cache. Because a width can now change after a connection was made,
connections whose ends no longer agree are dropped when the config
changes.

Also derive whether a pin is configurable and splittable from its bit
width policy instead of duplicating it on the pin attributes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Const intrinsic was merged in with the `fixed` bit width policy,
which is now named `calculated`. Its output width comes from the config
of the node, so it is resolved per node like the display resolution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Const node used to render a grid of randomly generated bits, so its
value could neither be read nor set. It now renders a text editor whose
contents are parsed into the data of the node config on every keystroke.
The text is interpreted in one of three modes, binary, hex or UTF-8,
which is chosen by a toggle group and kept in the config so it survives
a reload. Text that is not valid in the current mode only marks the
editor as invalid and leaves the last valid data in place. Pointer
events on the editor no longer reach the node, so selecting text does
not drag the node, and the node is resized to fit the editor.

Add the AndN and OrN intrinsics, which reduce every bit of their
configurable input to a single output bit.

Also apply `box-sizing: border-box` globally, which the editor relies on
to fill the foreign object it is rendered into.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chelproc chelproc changed the title Refactor node layout to use relative offsets and add play-mode guards Rework node layout and bit width resolution, and add the Const/AndN/OrN intrinsics Sep 19, 2026
@chelproc
chelproc marked this pull request as ready for review September 19, 2026 05:40
@chelproc
chelproc merged commit 2262b21 into main Sep 19, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants