Commit Graph
46 Commits
Author SHA1 Message Date
matthewtrahan 5caa288b45 plugins: nozzle review fixes -- an out-of-range or null nozzle is clamped/defaulted instead of keeping the last part's widths; Fusion's hand-built dialog gets the Nozzle field it was documented with 2026-10-13 13:32:31 -05:00
matthewtrahan 041448a71f plugins: nozzle size -- Cura and Orca read it from the part's extruder, CAD dialogs show it; the entry resets to 0.4 when a call leaves it out
options.json gains nozzle (hostSupplied: slicer, 0.2-0.8, default 0.4). The
entry sends it in tunables on every call, so a host's long-lived engine context
can't keep the last part's widths. Cura: machine_nozzle_size on the part's
extruder. Orca: nozzle_diameter, the wall extruder's entry. FreeCAD, Blender,
Fusion, SolidWorks and the CLI pick it up from the schema.
2026-10-13 13:28:16 -05:00
matthewtrahan 172d810eab nozzle: a Nozzle size field that scales the support widths -- every width was a 0.4 nozzle's, so a 0.6 or 0.8 dropped the tines or laid the wall as one line
Tines and the contact tip scale by nozzle/0.4 both ways; the wall, wedge and
sway brace rib only grow (a finer nozzle doesn't stiffen a free-standing wall).
Heights stay on the layer height. 0.4 is unchanged (sweep vs main: identical).
The Worker gets it in opts.tunables.nozzle; the plugins stay at 0.4 for now.
2026-10-13 13:14:20 -05:00
Per Hjort a5170233d9 Allow optional Sway depth beyond the existing 60 mm cap (#241) 2026-10-13 07:38:43 -05:00
Matthew Trahanandmatthewtrahan a59fb2ba6d Interface material: wall crests + tines as their own 3MF object for a toolchanger (#21) (#205)
* engine: interface crest (GitHub #21) -- with PROP.iface on, every wall (prop, part-attached, squat, Draw/fill, wedge blade) splits 0.6 mm under its top into body + crest, the crest and the tine comb tagged (v[3] = 1) so triRanges/removal/Worker are untouched; the 3MF writer's separate form writes the tagged triangles as a third object, '<name> interface', in register; off (the default) is byte-identical

* site: Interface material switch (Walls) -- the crest and tines show purple and export as their own 3MF object, for a toolchanger's second filament (PETG under PLA); reaches the Worker via tunables and Draw on the page; re-import still pre-selects the part; German strings; plugins leave it out (one body to the host)

* engine: interface crest review fixes -- sway brace tines are interface too (Auto and Draw); the crest is one trapezoid on the taper line, so no section has a collinear corner (zero-area cap triangles); Interface material is in the undo history; FIN-SPEC lists what is not split yet (sway rib edge, a part-standing wall's foot, a wedge whose contact steps straight up) (review)

* site: the interface is a part of the supports object, not its own -- an object starting in mid-air is refused by Orca ('empty first layer'); as the supports' second component it stands on the plate with them and takes its own filament per part

* engine: interface crest modes -- 'flat' (the default when on) crests a wall station only under an underside within 10deg of level, a full-width (th) 0.6 mm block with no tip taper, tines left in the body; 'all' crests every wall top and the tines as before. A 35deg cube's print with the crest everywhere couldn't lay PETG on the narrow PLA tip through every layer; PETG interfaces are used on flat undersides. Off stays byte-identical (sweep vs main)

* site: Interface material is a dropdown (Off / Flat contacts / Everywhere); the readout says when it is on and no wall got a crest; German strings

* engine: interface modes review fixes -- cutouts under a flat crest keep their old ceiling (holeTop: the taper start, not the crest's cut), so the split loses no plastic (lbracket Y8 lattice lost 4.6%); the crest's part reference is set only while the crest is on and dropped after each build; the readout's 'no flat contacts' note counts walls only (not the pad) and only in Flat contacts; tests: flat mode with lattice/diamond cutouts, mixed flat/tilted walls, squat walls (review)

* engine: the interface crest is one layer of the print (PROP.ifaceLayers 1, the Layer height field, as the tines), not a fixed 0.6 mm -- one tool change each way per flat contact instead of three; tooltip and FIN-SPEC say so; tests: the crest is one 0.2 / 0.3 mm layer

---------

Co-authored-by: matthewtrahan <mathyout@pm.me>
2026-10-09 22:00:37 -05:00
Matthew Trahanandmatthewtrahan f2147bd19f Plate only: every support on the build plate, none on the part (#218) (#221)
* engine: plateOnly -- every support on the build plate, none on the part (#218). Auto and the short walls drop a line the part-attached path would take (built or floored) and count it as skipped.onPart, never stilting it to the plate through the part; drawnWall refuses a line with part under it, naming the setting, and its squat fallback skips the part; the fill pass never retries a wall on the part. Off by default: sweep vs main identical. tests: plate_only -- a drawn line over the part refused, over open plate byte-identical; Auto and Full on the portal at Y90 build nothing on the part and count it; off, the lbracket is unchanged

* ui + plugins: a Plate only checkbox under Walls (#218) -- read by the auto build and the drawn walls; the readout says how many supports it left off and why, and names it when it emptied a part; undoable; German strings. options.json gains plateOnly, so every plugin dialog shows it, and both entries pass it on. tests: the dead-control check runs it on the portal at Y90 (the lbracket has no wall on the part); FreeCAD and Blender unit fixtures list it

* plate only: Full refuses a line over the part before building it, and a mixed line keeps its plate stretch (review) -- the local pass turned the on-part retry back on, and every try reached drawnWall to be refused: the portal at Y90 spent all 120 tries and blamed the wall limit. stationClear's `plate` now refuses a station with part under it on every try. Auto hands a line the part-attached path claims to the plate path (its column probes trim the stations over the part) instead of dropping it whole. The readout names Plate only when it emptied a part in any mode, not only Prop; German help line. tests: every built support reaches the plate, Full isn't capped

---------

Co-authored-by: matthewtrahan <mathyout@pm.me>
2026-10-08 22:32:15 -05:00
matthewtrahan 2dda91573a nav: a Mouse menu in the rotate panel picks which button orbits, pans and zooms (#53) -- Default (unchanged), Fusion 360, Onshape, SolidWorks, Blender. A preset is just OrbitControls.mouseButtons: a held Shift/Ctrl swaps orbit and pan on a button, so the CAD chords (Shift+middle orbit, Ctrl+middle pan) come free. The CAD presets leave left-drag to the picks, as those packages do; touch and wheel zoom unchanged; a middle press no longer starts the browser's autoscroll. Remembered in localStorage (optional), undoable. tests: navpresets -- Default is the old mapping, every preset orbits + pans, CAD presets leave left unbound; options -- nav-preset is site-only 2026-10-08 17:15:13 -05:00
matthewtrahan ae1ded3e43 pose: "Show rotate rings" checkbox hides the rotate gizmo (#53) -- the rings sit over the part and some users never use them; off hides and disables them, while Rotate 90deg, Reset and Lay a face flat still turn the part. Lives next to "Show layers" in the rotate panel (the Display section only shows once fins are on, and the rings matter before that). Remembered in localStorage (optional), undoable like every setting; the "Drag the rings" hint hides with them. tests: options -- show-rings is site display, not a plugin option 2026-10-08 17:04:04 -05:00
matthewtrahan 1001b26a22 ci: build the SolidWorks add-in (dotnet on Linux) and publish support-fins-solidworks.zip to plugins-latest, labelled with its .csproj version 2026-10-05 20:51:22 -05:00
matthewtrahan 7b13a58601 plugins/shared: the bridge exports reportLine, for a host whose dialog is a web page (SolidWorks) to show the shared result line; every other export unchanged 2026-10-05 20:51:22 -05:00
matthewtrahan 54e02ee141 draw: lines down to 2 mm, and squat walls when no full wall fits -- on the part with 0.6 mm of room, on the plate (only over clear plate) as a brimmed stem; tried after both full walls fail, so every line that built before builds the same (local issue 011) 2026-10-04 14:09:50 -05:00
matthewtrahan d89f72f0df engine: sway brace tines kiss the part's face (kissEnds), and the material's tine bite is gone -- one SWAY.tineReach 0.3 gates placement; Material tooltip no longer claims PETG shrinks a bite; same braces and tines on 16 cases x PLA/PETG, slices identical on 30 of 32 (lighthouse Y90 notch: +-0.13 mm2) 2026-10-03 15:14:03 -05:00
matthewtrahan 4d2fa682a2 site: drop the Tine bite field -- bite doesn't change the weld (the part's next layer prints on the tine) and a small bite only removed tines on shallow faces; CALIBRATION.md loses the bite test 2026-10-02 18:19:49 -05:00
matthewtrahan 1904c8f4fb site: Plugins menu in the topbar -- every plugin from plugins-latest with its install note, one Your-computer picker for the per-platform files, version line from the release labels 2026-10-02 15:28:54 -05:00
matthewtrahan 1b7334c2df site: Tine bite field (Tines section) drives the walls' PROP.tineBite through tunables.wallBite; default 0.5 unchanged, plugins keep the engine default 2026-10-02 13:52:52 -05:00
matthewtrahan f1fd249dd0 ci: publish FreeCAD zips + the CLI to plugins-latest, label every asset with its plugin version and engine commit (the site's Plugins menu reads the labels) 2026-10-02 11:18:53 -05:00
matthewtrahanandRemusTL 08c0a2b1ce blender: Draw wall + Lay face flat -- click the part for one hand-placed wall (the site's Draw), or the face to put on the bed
Draw wall: two clicks under an overhang make one wall through drawWall, with an orange
preview line between the first click and the mouse; a line the engine refuses says why.
Each drawn wall keeps its ends in the part's frame, so Generate re-stands it for the
part's current pose (the site's rebuildDrawn) or says why it no longer fits; a drawn
wall alone marks the fins out of date when the part moves. Lay face flat turns the part
so the clicked face is down, keeping its lowest point, and drops the old pose's fins.

The tools start from the sidebar but cast from the 3D View's window region, and any
error ends them cleanly (no preview handler or header text left behind).

Co-authored-by: RemusTL <336249490+RemusTL@users.noreply.github.com>
2026-10-01 14:24:13 -05:00
matthewtrahan 0cddbe2460 blender: Support Fins extension core -- Generate puts the site's fins under the part as objects, on the shared engine path
Rebuild of #145 on the plugins' model: the panel is generated from options.json and a
setting shows only when the engine uses it; the fins come from computeFins through the
shared host (mini-racer as a manifest wheel, JIT on), one object per wall / sway brace
/ pad (pieces); the result line is host_report, so overhangs that weren't reached and
floating pieces are always said. Moving the part, editing its mesh (caught across a
save + reopen by a mesh fingerprint), changing a setting or the unit scale marks the
fins out of date. Units: Unit Scale x 1000 = mm; the panel offers Use millimetres when
the part's size looks wrong.

build.py makes one zip per platform and flattens mini-racer's wheel: it publishes the
package under .data/purelib/, which Blender 4.2 / 4.3's installer leaves unimportable.

GPL-3.0-or-later (Blender's rule for add-ons); the bundled engine stays MIT. Draw wall,
Lay face flat, 3MF export and the overhang overlay follow on their own branches.
2026-10-01 13:51:02 -05:00
matthewtrahan fdd4d3307b shared: host_engine(jit=True) keeps V8's JIT on macOS for a host that allows it -- 80 s -> 3 s on a 75k-triangle part
The default stays --jitless on macOS (Orca's hardened app refuses JIT memory). Blender's
macOS app is signed with com.apple.security.cs.allow-jit, and jitless ran issue121 (75k
triangles, 81 walls) in 80 s against 3 s with the JIT -- same walls and tines. Pinned:
jit=True gives lbracket's 4 walls / 20 tines in a fresh process.
2026-10-01 13:51:02 -05:00
matthewtrahan 56e23fe43a shared: vendor.py hands out the mini-racer wheel itself -- for Blender, which installs an extension's wheels
mini_racer_wheel(plat, cache) downloads (once) and returns the wheel; vendor_mini_racer
unpacks it as before for Cura and FreeCAD.
2026-10-01 13:51:02 -05:00
matthewtrahan 902f0f48f3 shared: review fixes -- overFaces is the site's red (kept), smallFaces its amber; draw tests compare coordinates
overFaces was every face past the threshold, but the site paints only the faces in
regions it supports red (res.kept) and the too-small ones amber. A host painting
overFaces red would have called slivers overhangs that no fin targets. Now
overFaces = red, smallFaces = amber, both pinned to paintOverhangs' rule (a shelf +
2 mm nub part gives an amber face, so that half of the check runs).

drawWall: the site comparison checks where the wall is, not just its counts, on an
off-centre part; layer height 0.1 / 0.3 reach the wall. host_draw_wall refuses a
NaN end in Python instead of a raw JS parse error; stats say partAttached.
2026-10-01 08:32:48 -05:00
matthewtrahan 33bce1252e shared: Draw mode for the plugins -- drawWall builds one hand-drawn wall the way the site's Draw does
engine/draw_entry.js drawWall(soup, a, b, options): the part as computeFins takes it
and the two clicked points in the same frame -> web/draw.js drawnWall, called as
web/ui/walls.js calls it (print-frame part, the material's tunables set). Same seat
as computeFins, so the wall is bit-identical wherever the part sits; a wall it can't
build comes back with the site's reason ("wall too short ..."). Bridge: drawWallB64;
Python: host_draw_wall.

Tests pin it to the site's wall (PLA and PETG), plate-position independent, no
clearance leak into the next wall or the next computeFins.
2026-10-01 08:32:48 -05:00
matthewtrahan 2e7503aa43 shared: computeFins says which triangles are which fin (pieces) and which faces overhang -- for a host that shows one object per fin
A host that wants each wall as its own object (Blender next) had to call buildFins
itself to get the fin ranges, skipping the entry's seat and material clearances.
computeFins now returns pieces ({id, kind, ranges} in triangles of its soup; every
triangle in exactly one) and overFaces (input faces analyze() marks red). Python:
host_compute_pieces; host_compute is unchanged for Orca, Cura and FreeCAD.

The seat (centre + 1 nm snap) and the material clearances move to engine/seat.js
so the next entry (Draw mode) seats a part exactly the same way.
2026-10-01 08:32:48 -05:00
matthewtrahan f59eeb9610 plugins: mini-racer vendoring moves to plugins/shared/vendor.py -- FreeCAD vendors it the same way as Cura
No behaviour change: Cura's build imports PLATFORMS, wheel_platform and
vendor_mini_racer from there (same wheel cache under plugins/cura/build/wheels).
2026-10-01 06:51:06 -05:00
matthewtrahan 597aaa33db cli: review fixes -- --rot reads the site's X·Y·Z readout, --fins-only lines up with the file's part, own 3MF refused
- --rot is three.js Euler XYZ (Rx*Ry*Rz), the order printfins.com shows under
  the part, so a pose found on the site carries over
- --fins-only maps the fins back onto the part as it is in the file; it can't
  take --rot, and writes nothing when there are no fins
- a Support Fins 3MF (part + fins) is refused instead of finned again
- settings the site would hide warn on stderr; -o can't overwrite the input;
  -- ends the options; Node 20.10+
- the summary rounds ties to even, word for word with Python's host_report
2026-09-30 18:30:03 -05:00
matthewtrahan d0c8f7f098 plugins: the one-line result in JS (engine/report.js) -- word for word with Python's host_report, for the command line 2026-09-30 18:30:03 -05:00
matthewtrahan ff52ae9792 fusion: ticking sway braces enables its fields; review fixes for 018
The dialog re-synced only on the Tines toggle, so ticking Add sway braces left
Grip from / spacing / depth greyed out (already so on main). The earlier-run note
says support bodies (braces are counted there too); README names Sway brace N.
The split test now runs on a part with fins, braces AND a pad and checks the fin
and pad blocks are byte-identical to the braces-off run; host_report's brace
wording is pinned.
2026-09-30 13:18:37 -05:00
matthewtrahan 30a402b742 plugins: sway braces come back as their own block -- Fusion names them Sway brace N, not fins (local issue 018)
The entry's soup was fins+braces | pad, split only by finTriangles, so Fusion built
a braced 150 mm post as 4 'Support fin' bodies and headlined 4 fins. Now
finTriangles is fins only and swayTriangles follows it (fins | braces | pad); Fusion
splits three ways and counts fins from fins alone. Readouts (host_report) name sway
braces apart from walls. Fusion's Grip from / Brace tine spacing now need Tines too,
as on the site. Fusion add-in 0.6.1.
2026-09-30 13:18:37 -05:00
matthewtrahan 073d168c02 plugins: readouts count plain walls; engine errors reach the user as one line
With Tines off every wall is a plain prop, which the entry's stats didn't carry, so
host_report said "0 walls" under a part with four. stats.props is new and walls =
braces + props, as the site counts them. host_options now raises a ValueError with
just the engine's message; V8's text carried the whole bundle source after it.
2026-09-30 12:55:56 -05:00
matthewtrahan b59b9099bc plugins: optionsFromDialog is strict -- refuses bad values instead of guessing; Python gets host_options
"false" was true, '' and None became the minimum, and a percent sent without the /100
was clamped to 1 (coverage 50 -> 1): all silent. Now bools take true/false or their
strings, null/'' mean the default, and anything out of range throws, naming the /100.
The slicer's own layer height is used as is (the site doesn't clamp it either).
Python hosts get host_schema / host_options / host_visible through the bridge. The
defaults test compared a cube with no fins (0 == 0); it now uses lbracket and the
sway-braced bar.
2026-09-30 12:25:40 -05:00
matthewtrahan 909de5ba8c plugins: options review fixes -- showIf grammar, optionsFromDialog/optionVisible, layer height + overhang angle
showIf is now a list (all must hold) of bool keys, {key, in} and {any}, enough for the
site's rules: brace grip/spacing need sway AND tines; layer height shows with tines or
a one-layer pad. Hosts pass dialog values through optionsFromDialog (nest, clamp,
check, drop sway when off) and ask optionVisible, so no host re-implements either.
Matthew's calls: layerHeight in (hostSupplied: slicer, CAD hosts show it), the
overhang angle in, placement mode stays Auto only. Tests: every site control is in
the schema or listed as excluded with a reason; pad styles are the entry's own list.
2026-09-30 12:25:40 -05:00
matthewtrahan 4da4c93adc plugins: list options.json in the shared README 2026-09-30 12:25:40 -05:00
matthewtrahan f12cb8cca4 plugins: options.json -- one settings schema every plugin dialog is built from
Material, tines, tine grip, bed pad (Off/Auto/Light/Sure hold), sway braces, cutouts
and coverage, with the site's labels and tooltips. Tests pin every default, range and
choice to web/index.html, the choices to the engine's own lists, and check each option
changes the fins (no dead control). The entry takes its defaults from the file, and
padStyle 'off' is the site's Off.
2026-09-30 12:25:40 -05:00
matthewtrahan e3a8f40be5 plugins: entry review fixes -- tests compare PETG geometry to the site's, pin PLA to the engine's built-ins
The PETG check compared counts only, which PLA passes too; it now compares every
vertex (site copy snapped to 1 nm like the entry, else tine noise). New: PLA profile
must equal FIN/PAD/PROP/PERP/SWAY defaults; sway braces carry the material and a
null gap/bite from a host counts as unset. Each option test resets in finally.
2026-09-30 11:19:03 -05:00
matthewtrahan 5cc1da01ac plugins: entry takes material, padStyle and cutout -- plugins can print PETG, the site's pad styles and wall cutouts
The entry sends the full clearance set on every call, as the site's worker does:
buildFins keeps it as module state and a plugin's V8 context lives across calls,
so a partial set would leak PETG into the next PLA run. Unknown values throw
instead of silently building PLA. Sway braces take the material's gap and bite.
2026-09-30 11:19:03 -05:00
matthewtrahan 2d33435644 plugins: review nits -- CI step name, slicer docstring, Orca build size 2026-09-30 10:10:58 -05:00
matthewtrahan 5c307f26f3 plugins: document the shared slicer and what goes in shared vs an adapter 2026-09-30 10:10:58 -05:00
matthewtrahan 6b2b0ee779 plugins: shared mesh slicer -- Orca's soup -> layer polygons code moves to plugins/shared/py
split_shells / slice_soup / group_loops are geometry, not Orca glue. Moved unchanged
to supportfins_slice.py with their tests; Orca's build now inlines a list of shared
modules at single-line markers. Built plugin is byte-identical code apart from the
inlined-module wrapper.
2026-09-30 10:10:58 -05:00
matthewtrahan fc81d2c92e cura: Add Support Fins -- the site's engine puts fins on Cura's plate as an object on the part
Extensions > Support Fins > Add Support Fins reads the selected part as it sits on
the plate (Cura is Y-up; frames.py maps it to the engine's Z-up), runs the bundled
engine in a background Job through the shared Python host, and adds the fins + pad
as a "Support Fins" node parented to the part: it moves with the part, Ctrl+Z takes
it off, a re-run replaces it. The readout says what wasn't reached.

mini-racer is vendored by build.py (Cura's Python can't pip-install). Cura's global
Remove Mesh Intersection trims the tines back to the part's wall, so they touch
rather than bite -- kept on purpose, for a clean snap-off.

Website defaults only; the settings dialog and per-platform packages come next.
Checked in Cura 5.13 (macOS arm64) with devrun.py: lbracket @35 X gets 4 walls /
20 tines, same as the site.
2026-09-30 09:03:45 -05:00
matthewtrahan c99b50e41f plugins: host_report -- the one-line result, with overhangs not reached and floating pieces 2026-09-30 09:03:45 -05:00
matthewtrahan df466baca0 plugins: shared host tests read the committed lbracket (web/dev-models isn't in git) 2026-09-30 08:26:54 -05:00
matthewtrahan 041dadc4c8 plugins: shared Python host -- V8 start and soup round-trip leave Orca for plugins/shared/py
Cura and FreeCAD run the engine in mini-racer the way Orca does, so the code that
starts V8 and moves triangles across now lives once in
plugins/shared/py/supportfins_host.py: host_engine() (Orca's flags, plus a
vendor_dir for hosts that can't pip-install -- under a PyInstaller app like Cura,
mini-racer looks for its library in sys._MEIPASS) and host_compute() (float64 in,
fins back in the caller's frame). Orca's build inlines it, so the plugin is still
one file; its behaviour and tests are unchanged.
2026-09-30 08:26:54 -05:00
Mitch MilamandClaude Opus 5 b40a2ce54c engine: let a plugin ask for sway braces
The braces (web/sway.js) shipped on the site in #29, but no plugin could
reach them: computeFins forwards an explicit list of options to buildFins,
and `sway` was not on it, so every host silently got fins only.

It now forwards `sway` whole -- a host passes the same object the website's
options panel builds -- and reports the result apart from the fins:
swayBraces / swayTines / swaySkipped, plus swayReason for a host that asked
and got none. Off by default, so nothing changes for a caller that says
nothing: ENGINE_DEFAULTS.sway is null and the geometry is byte-identical.

Braces land in the same soup as the fins, so stats.finTriangles counts them
too; splitting them per body would need per-brace ranges this entry point
does not expose yet. Noted where it matters.

Tests: off by default is unchanged, { on: true } braces a tall post and says
so, the counts match the website brace for brace, tine spacing and "grip
from" reach the braces, and a part too short to brace reports why while its
fins still build. 26 pass in this suite, 200 across the JS suites.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-30 08:11:47 -05:00
MorbidJ-hub c274b73d6c engine: flag pieces that start in mid-air
floatingPieces() (overhangs.js) finds any closed piece of the mesh that
neither reaches the plate nor rests on another piece (within 0.3 mm).
buildFins() returns them as built.floating and the readout says so; the
plugins' stats carry floating and floatingDrop.

Split out of #84 and ported onto the fins.js split.
2026-09-27 13:48:23 -05:00
matthewtrahan 54b5fbe715 ci: rebuild and test the plugins when web/ or plugins/ change (step 2)
Orca bundles the site's engine at build time, so a site change only reaches it
through a rebuild. New plugins.yml workflow:

- PRs and pushes touching web/**, plugins/** or the workflow: build Orca
  (plugins/orca/build.py) and run its pytest suite; run the Prusa Lua checks and
  zip the bundle folder
- push to main: publish both builds to the rolling plugins-latest pre-release
  (tag moved to the commit, assets replaced)

The shared engine's Deno tests stay in test.yml.
2026-09-25 18:17:43 -05:00
matthewtrahan 8518e0451d plugins: move the engine bridge and bundle step into plugins/shared (step 1 of one engine for every plugin)
Orca's engine glue wasn't Orca-specific. Move it where the Fusion add-in (and any
later JS-running plugin) can use it too:

- plugins/orca/panel/fins_entry.js     -> plugins/shared/engine/fins_entry.js
- plugins/orca/panel/engine_bridge.js  -> plugins/shared/engine/bridge.js
- the esbuild step in orca/build.py    -> plugins/shared/bundle.py (bundle_engine)
- bridge/entry tests, sensitivity repro and ENGINE-SENSITIVITY.md move with them

No behaviour change: the built Orca plugin and its engine bundle are
byte-identical to main's. The Python V8 loader stays in Orca for now; it moves
to shared when Fusion needs it.
2026-09-25 16:18:40 -05:00