614 Commits
Author SHA1 Message Date
Christian Svensson e9ccf10692 engine: add a small heated flow sample
Demonstrate temperature slices and signed face-average wall heat with cool air passing an isothermal bracket. Register the original CPU-rendered gallery thumbnail and document the coarse transient setup.

Part of #157.
v0.7.0
2026-10-17 14:08:50 -04:00
Christian Svensson 1e5ff08c5f engine: show temperature and wall heat in fluid results
Route saved temperature through slices, obstacle surfaces, streamlines and both result views, with availability and model units shared by commands and the UI. Show signed obstacle heat histories per complete face area at the selected frame, leaving unrecorded times uncoloured and retaining the saved slice settings.

Part of #157.
2026-10-17 14:08:50 -04:00
jyvikoandClaude Opus 5.5 8f8124dfa1 release: version 0.7.0
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 13:50:17 -04:00
jyvikoandClaude Opus 5.5 1f2446935f ci: run develop's Linux jobs on the self-hosted runner when SELF_HOSTED_RUNNER is set
Pushes, the schedule and Run workflow go to the runner labelled by the
repository variable SELF_HOSTED_RUNNER (tangency-labs/ci-runner, label
fieldcraft-ci); pull requests always stay on GitHub's runners, so fork
code never reaches it. Unset the variable and every job is back on
ubuntu-24.04. macOS and Windows jobs are unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 11:32:58 -04:00
jyvikoandClaude Opus 5.5 1daee9d969 docs: ask for pictures in issues and pull requests
The bug template's Evidence asks for a screenshot or recording first,
marked up, and a section for meshes; the feature template gains a
Pictures field; the pull request template and CONTRIBUTING ask for
before/after pictures of any visible change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 10:57:58 -04:00
Christian Svensson 8cd37e0a68 gpu: take wgpu's backend options from its environment variables
The adapter selection built its wgpu instance with default backend
options, so WGPU_DX12_COMPILER had no effect: on DX12 there was no way to
choose between FXC and DXC when measuring them (#561: FXC takes ~8.5 s
to create the GPU solver's pipelines and runs it at half of Vulkan's
speed, DXC 0.5 s and full speed). The options now come from wgpu's own
environment variables. staticdxc is refused with an error: it needs
wgpu's static-dxc feature, which FieldCraft does not build, and wgpu
would panic on it.

Part of #561.
2026-10-17 10:45:13 -04:00
Christian Svensson 60fd0998f5 lbm-gpu: stream in place with the AA pattern
The collide and stream passes each moved the whole population field
through two buffers (304 bytes per cell update). The AA pattern (Bailey
et al. 2009; Wittmann et al. 2013) keeps one buffer: even steps collide
each cell in place with its directions swapped, odd steps read a cell's
incoming populations at its neighbours and write the outgoing ones where
the next step reads them, so each thread writes the addresses it read
and streaming happens inside the collision (152 bytes). A cell's
coordinates are computed once and neighbours wrap with a compare and an
add. The per-link wall rules gather their sources before a scatter
writes them, so no link overwrites another's post-collision value, and
the momentum exchange is taken in the gather of the step whose forces
are read back.

Options::streaming picks the layout; Auto, what a solve uses, streams
in place, which measured faster on every device tried: 1.5x on an RTX
4090, 1.85x on the GB10 and 1.45x on an M4 Max at periodic 128^3.
Options::profile times each kernel with timestamp queries for the
throughput test, which now runs both layouts. The verification tests
run in both layouts, with two new ones comparing fields, mass, forces
and heat with the CPU after odd and even step counts across submission
boundaries.

Part of #562.
2026-10-17 10:45:09 -04:00
Christian Svensson 74dce59c7d lbm-gpu: sum the Zou-He deviations before adding the weights
The open boundaries summed the tangential and outgoing populations as
deviations from their weights plus the weights (2/3 and 1/6), in single
precision; at low speed the weights round away the deviations' last
digits. A low-speed case through an inlet, an outlet and a slip face
came out 2.26e-5 of the largest speed from the CPU under DXC, against
the 2e-5 the tests allow. The deviations are now summed first and the
total weight, exactly 1, added once: the same Zou-He rule, so results
change only at the round-off level.

Part of #562.
2026-10-17 10:45:09 -04:00
jyvikoandClaude Opus 5.5 c1941ad349 ui: Move Nodes prepares once the form settles and skips large regions
A changed form (Alt + scroll on the radius) is prepared 150 ms after it last
changed, the sphere drawn meanwhile; a region of more than 1000 nodes is not
previewed and moves on release.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 10:35:53 -04:00
jyvikoandClaude Opus 5.5 8a178879ee engine: mesh.node.move's live preview, capped before its costly part
prepare_capped counts the nodes that would follow and stops before the design's
faces and the harmonic solve when they are more than a cap; preview_live
reuses the region's baseline and guards more coarsely.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 10:35:53 -04:00
jyvikoandClaude Opus 5.5 1dd2afe525 mesh: a node move measures its region in parallel and can reuse its baseline
move_nodes_with takes the region's quality before the move from an earlier run
and a coarser guard bisection, for a live preview; each pass measures the
elements in parallel (fixed blocks). The relaxation's state sizes its elements
(a certified check each) only for the strong mode, which alone uses the size:
on the crank arm's hex20 mesh that was 114 ms of every 130 ms preview.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 10:35:53 -04:00
jyvikoandClaude Opus 5.5 53a370ed01 ui: Move Nodes icon shows the grid and the node's new position over it
The original 2×2 grid in the line colour, the moved node and its four
edges overlaid in the accent colour.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 10:35:53 -04:00
jyvikoandClaude Opus 5.5 ee09ff2fca ui: Move Nodes tool: drag nodes in the view with a ghost and quality colours
With the form open a drag on the mesh grabs the node under the press and
moves it in the screen plane; Alt + scroll sizes the radius. The view draws
the original edges as a ghost under the moved ones, coloured by how each
element's scaled Jacobian changed, and the sphere; the form shows the region's
numbers live. Release runs mesh.node.move (one undo step), Esc cancels.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 10:35:53 -04:00
jyvikoandClaude Opus 5.5 711e51074c engine: mesh.node.move moves nodes by a vector or to a point
The grabbed nodes (listed, on B-rep faces, or a sphere's) move; the nodes
within the radius follow by a falloff or harmonically; nodes stay on the
geometry as Relax Nodes moves them; the move is scaled back until no element
inverts. The reply gives the region's scaled Jacobian before and after and the
elements that got worse and better. One undo step. Toolbar entry Move Nodes
(Mesh, Improve) with its own icon.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 10:35:53 -04:00
jyvikoandClaude Opus 5.5 e52eda728f mesh: move nodes by hand, with falloff or harmonic followers and an inversion guard
relax::move_weights gives each node its share of a displacement (the grabbed
nodes all of it, the nodes within the radius strength x falloff, or the
harmonic function of the element-edge graph, zero beyond the radius and at
held nodes); relax::move_nodes applies it as the relaxation's constraints let
each node move (faces in UV, edges along, vertices held), scaled back by
bisection until no valid element inverts, and reports each element's scaled
Jacobian before and after.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 10:35:53 -04:00
Christian Svensson 99b0b0cfa4 lbm-gpu: the temperature lattice on the GPU
The D3Q7 scalar lattice steps on the device after the flow's step, in
its own A–B pair of buffers: BGK collision with the velocity the
momentum collision leaves in the moments buffer, pull streaming with
the links' rules read through each group's thermal condition
(anti-bounce-back for a held temperature, bounce-back for an adiabatic
face, a flux added to the bounced population), inlets at their held
temperature and outlets copying their neighbour. The populations are
stored as deviations from w_q T₀ so single precision keeps the digits
of the differences. The heat each link exchanged goes into the fourth
component of the momentum-exchange contributions and the group sums;
the buoyant acceleration −β g θ is read per cell in the collision, the
open boundaries and the moments. A temperature that stops being a
number ends the chunk with Error::Unstable like the flow's checks.

Flow exposes what the backend needs to build the device state (the
thermal settings and populations).

Verification against the CPU on lavapipe: plates held at 20 and 21
(temperatures within 1.4e-5 of the range, the wall heats within 6e-5
and both against k ΔT A / H), a heated channel with a parabolic inlet
carrying its temperature, a flux wall, a held wall and an outlet
(temperature 3e-7, heat 1e-7) and a buoyant cavity at Ra 10³
(velocity 1.3e-6, temperature 6e-7, heat 2.4e-6). The tests now fail
on a shader that does not compile instead of skipping; only a missing
device skips.

Part of #157.
2026-10-17 08:32:30 -04:00
Christian Svensson 190f958b23 engine: heat in a fluid step
A fluid domain takes `thermal` settings (the initial temperature, a
thermal condition per box face and per obstacle face, gravity with the
expansion coefficient for buoyancy) in model.fluid_domain.create and
.edit, and model.fluid_domain.thermal_boundary sets one boundary's
condition as model.fluid_domain.boundary does for the flow. A material
a domain with heat uses keeps needing its conductivity and specific
heat when it is replaced.

solve.fluid's summary adds the thermal relaxation time, the fluid's
temperature range and the heat into every wall (final and the mean
over the last quarter of the history); solve.probe gives the
temperature in the cells at points and, with `heat`, the walls' heat
histories. The form, the catalogue and docs/features.md follow.

Part of #157.
2026-10-17 08:32:30 -04:00
Christian Svensson 939b64e660 lbm: a temperature lattice on the flow
A D3Q7 passive scalar rides on the D3Q19 flow: it is advected by the
cell velocity, diffuses with the fluid's thermal diffusivity (the
relaxation time follows from k / (rho c)), and feeds back into the
momentum through a per-cell Boussinesq force when the domain has an
expansion coefficient and gravity. Walls and obstacle faces hold a
temperature (anti-bounce-back), a heat flux (added to the bounced
population) or are adiabatic (bounce-back); velocity inlets carry the
held temperature in, pressure outlets copy their neighbour.

The heat crossing each face group is accumulated during streaming and
reported in physical units (W) in the result's heat histories, next to
the force histories; frames gain a temperature field and the stats
report the thermal relaxation time.

Closed-form checks: linear conduction between held plates, a flux wall
against q H / k with the wall's heat balance, an inlet temperature
carried through a plug flow, and a periodic blob that conserves its
total and returns after one lap. The de Vahl Davis cavity (Ra 1e3 and
1e4 against the published Nusselt numbers) is there as an ignored test
because it takes minutes.

The GPU backend declines domains with heat for now and asks for the
CPU; its kernels follow.

Part of #157.
2026-10-17 08:32:30 -04:00
Christian Svensson ea284caf80 model: heat in a fluid domain
A fluid domain may carry thermal settings: the initial temperature,
gravity and the fluid's expansion coefficient for buoyancy, and a thermal
condition (adiabatic, held temperature, heat flux) on each box face and on
named obstacle faces, adiabatic unless told otherwise. The material then
needs an isotropic conductivity and a specific heat. Boundaries are set
one at a time like the flow ones.

Part of #157.
2026-10-17 08:32:30 -04:00
jyvikoandClaude Opus 5.5 7d419b73f4 mesh: the tet report carries the scaled Jacobian; both metrics tested on the same tets (#547)
The dihedral range and mesh.quality's scaled Jacobian were both right:
they are computed by the same function on the same final corners (tet4
and tet10 give identical numbers), but a dihedral range does not bound
the corner scaled Jacobian. A needle has dihedral angles near 60 and 90
degrees and a nearly flat apex corner. Before 417c0c88 sphere-on-plate
at size 4 had dihedrals 36.3-141.6 and 106 needles below SJ 0.2 (min
0.095); now 36.0-137.6 and min SJ 0.351.

The mesher's report now carries min_scaled_jacobian so it cannot read
as all-good while needles remain.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 08:12:09 -04:00
jyvikoandClaude Opus 5.5 e315b2bef7 docs: the plan's apply, the cells mesher and the partition are jobs
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 08:12:04 -04:00
jyvikoandClaude Opus 5.5 2729511cb5 ui: mesh.plan.apply, mesh.cells and geometry.partition run as jobs (#545)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 08:12:04 -04:00
jyvikoandClaude Opus 5.5 b5f71612af mesh: mesh.plan.apply, mesh.cells and geometry.partition report their stages and pass a cancel through
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 08:12:04 -04:00
jyvikoandClaude Opus 5.5 b2fcf2c5bc geometry: partition cuts and the projection mesher report progress and stop when cancelled
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 08:12:04 -04:00
Christian Svensson 8dce4b14ce engine: the flow test fixture names its backend
`FluidStats` gained `backend` in #535 and the fixture of the flow drawing
tests arrived with #550; merged one after the other, the fixture lacks the
field and `cargo test -p fieldcraft-engine` no longer compiles on develop.

Part of #157.
2026-10-17 08:12:01 -04:00
Erkin Alp Güney 18be60672b verification: activate the two shell benchmark cases (#106)
The Scordelis-Lo roof and the pinched cylinder had their published
references cited but no recipe, because mesh.block's 2D form is planar
and cannot build a curved patch. Both are now active: the mesh is
written node by node (mesh.node.add / mesh.element.add, the route
docs/verification.md allows), the same cylinder patch the solver's own
benchmarks.rs builds in code, with the quarter/eighth symmetry planes,
the rigid diaphragm and the published load.

- scordelis-lo-roof-shell: 16x16 flat quad4 (MITC4) shells, R = 25,
  t = 0.25, self-weight 90 per area; u_z at the free-edge mid-span
  -0.3024 within 2% (ours -0.29901009815961804, 1.12% low).
- pinched-cylinder: 32x32 flat quad4 shells, R = 300, t = 3, a quarter
  of the unit pinch; u under the load -1.8248e-5 within 2% (ours
  -1.803845342860719e-05, 1.15% low).

Each README carries the generator (so the recipe is reproducible), the
refinement study the tolerance reason cites, and how the reference was
cited before. pinched-hemisphere and mh-twisted-beam now say what they
actually wait for: a doubly curved or helical shell mesh written node
by node, not missing shell elements.
2026-10-17 07:25:44 -04:00
Christian Svensson a0c7d6ce9d ui: show fluid results in the viewport with the part, legend and controls
The window built a deformed copy of the model for every results plot,
which drops the geometry surfaces, so after a fluid solve the bracket
vanished and nothing else was drawn.

A fluid step's plot keeps the part as it is and draws the engine's flow
display through it (the same scene as the headless pictures): the
field on a slice, the part coloured by the flow beside it and
streamlines. The legend shows the field with its bands and the cells
of its extremes (Max/Min mark and turn the view to them); the results
panel swaps the deformation rows for the slice's normal and position,
the obstacles' colours, the streamlines and a frame slider, all
running the results.* commands. The status line describes the new
commands.

Part of #193.
2026-10-17 07:25:04 -04:00
Christian Svensson f7dcd46b2f engine: draw fluid results as slices, coloured obstacles and streamlines
After solve.fluid the results view had nothing to draw: a fluid step's
fields are per cell, the view only knew nodal fields, and the deformed
display it built dropped the geometry surfaces, so even the part
vanished.

A fluid step now has its own display (`engine::flow`, on render's
flow display data), shared by the headless pictures and the window:
the part drawn as it is, its faces coloured by the field in the fluid
beside them, the field on a slice through the domain and streamlines
seeded on the inlet face upstream of the obstacles, coloured by speed
when the legend shows velocity. It is first shown with velocity
magnitude on a slice through the middle, normal to the horizontal axis
across the flow.

New commands: results.slice {step, field: velocity|pressure, axis:
x|y|z|none, position, frame}, results.surface {step, show} and
results.streamlines {step, show, count, max_steps, seeds | line,
frame}, which replies with what it traced. results.contour takes
velocity and pressure for fluid steps (a new `pressure` field) with
the usual bands and legend clamp, and results.frame walks a fluid
step's frames. Streamlines leave the coming-soon list. Hostile
parameters are refused, and the counts are capped.

Part of #193.
2026-10-17 07:25:04 -04:00
Christian Svensson 0d30ddb747 render: slices, surface values and streamlines of cell flow fields
A fluid step's result is a field per cell of a regular grid, not per
node, so the contour machinery had nothing to draw it with and the
viewport showed nothing after a fluid solve.

Add `render::flow`, CPU display data for such fields with no solver
dependency (render stays at L1): an axis-aligned slice through the
cells as triangles carrying cell-centred values (interpolated between
the two layers either side, outer centres moved to the domain faces,
cells inside obstacles left out), the field sampled at a surface
beside the fluid cells for colouring an obstacle, and streamlines
traced through the trilinear velocity field by the midpoint rule,
seeded on a lattice upstream of the obstacles. Seeds, steps and the
total number of points are capped so hostile counts cost a bounded
time. `contour::banded` fans band pieces into scene triangles for the
callers.

Part of #193.
2026-10-17 07:25:04 -04:00
Christian Svensson 1e0c21d088 lbm-gpu: one atomic per workgroup for the chunk's largest speed
Every cell did an atomicMax on the same word; a workgroup reduction first
leaves one per 256 cells. Same result, less contention on a single
address.

Part of #529.
2026-10-17 07:23:40 -04:00
Christian Svensson 48cc80bef5 solve.fluid: backend "cpu" (the default) or "gpu"
The CPU stays the default, so every existing result and fixture is
bit-identical; "gpu" steps on the device FIELDCRAFT_GPU picks through the
registered backend, and is an error when the build has none. The desktop
app and the CLI (hence the MCP server) register fieldcraft-lbm-gpu at
start; the summary names what stepped. Hostile values are errors.

Part of #529.
2026-10-17 07:23:40 -04:00
Christian Svensson b123f31d47 lbm-gpu: the lattice Boltzmann step on the GPU, checked against the CPU
A new L7 crate: the CPU scheme's collision (TRT with Guo forcing), the
per-link streaming rules (bounce-back with Ladd's term, slip reflection,
open placeholders), the Zou–He boundaries and the momentum exchange per
force group as wgpu compute shaders, stepping the flow fieldcraft_lbm's
Flow prepared, through its Backend trait: populations stay on the device,
a chunk of steps is one submission, and only frames, forces and the
stability check come back. Populations are stored as deviations from
their weights, which keeps single precision honest: the verification
cases (Poiseuille with Λ = 3/16, Couette with wall forces, Taylor–Green,
a sphere's drag, a Zou–He channel) agree with the CPU to 2–5e-6 in
velocity, 1e-6 in density and 5e-5 in force, with those tolerances stated
in the tests. The rules are applied one thread per link rather than in a
per-cell loop, which lavapipe's JIT miscompiles (and which diverges on
real GPUs anyway). Native only for now; the browser registers nothing.

Part of #529.
2026-10-17 07:23:40 -04:00
Christian Svensson 944a54c76b lbm: a Backend trait steps the flow, with the CPU scheme as its reference
run() drives a Backend in chunks of steps (to the next frame, history sample
or progress check) instead of the CPU Flow directly, so a device backend
registered by an application can step the same prepared flow: Flow keeps
building the grid, links and open cells and exposes them read-only, and
implements the trait itself. solve_fluid_on picks the CPU or the registered
device backend; solve_fluid stays the CPU and is unchanged in its results.

Part of #529.
2026-10-17 07:23:40 -04:00
jyvikoandClaude Opus 5.5 417c0c8897 mesh: the tet objective sees needles; no tet below scaled Jacobian 0.3 on the skill's tet parts
The improvement objective was the smallest dihedral sine, which misses
needles: every dihedral angle fine while a corner is nearly flat, so
the corpus meshes had scaled Jacobians down to 0.01-0.07. The objective
is now the smaller of the dihedral sine and the scaled Jacobian at the
worst corner (the measure analysts check, Knupp 2001), weighted so a
Jacobian of 0.3 (the skill's limit) counts as a 30 degree dihedral
angle (the schedule's target): the Jacobian decides only near that
limit. The existing edge removal, multi-face removal, smoothing and
insertion then remove the needles.

Skill tet10 min SJ: sphere-on-plate 0.092 -> 0.335, filleted-bracket
0.021 -> 0.318 (both pass, nothing marked). Default-size tet10:
thin-ribs 0.240, shallow-groove 0.321, rounded-box 0.311.

The strong-relaxation test relaxes below 0.45: nothing is left below
0.3. Literature note in docs/meshing/literature.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 00:51:20 -04:00
jyvikoandClaude Opus 5.5 d7444edac7 solver: a tie row nearly repeated on a held secondary node is dropped
Where a tie's secondary surface meets a boundary condition or an
axis-aligned symmetry plane (a crosspoint), the secondary node's motion
is already prescribed and its mortar row can only constrain main
degrees of freedom. Its weights on main nodes off the boundary do not
vanish (the dual basis spreads over the node's faces), so after the
substitutions a small but real constraint is left: 1.3e-6 of the
coefficients that cancelled to give it on the tied hex-tet bar with
the needle-aware tet mesh (round-off would be ~1e-14; the geometry is
exact). It was refused as a near repeat, which made the tied bar
depend on how the tet faces fell. It is now dropped (the tie is then
off by that fraction at the crosspoint), after checking it does not
contradict the prescribed values. A near repeat on a secondary degree
of freedom another constraint eliminated (a node secondary in two
ties) is still refused (#389). Dropping every held secondary node's
row instead fails the tie patch test, so only near repeats go.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 00:51:20 -04:00
jyvikoandClaude Opus 5.5 6b4a966e1a Feature graph: test the panel's words and face selection on a real design
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 00:50:52 -04:00
jyvikoandClaude Opus 5.5 2ffbb9b978 Feature graph panel: features per body in words, click selects their faces, hover highlights them
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 00:50:52 -04:00
jyvikoandClaude Opus 5.5 e955b67bb7 Feature graph: each feature of a timeline in plain words
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 00:50:52 -04:00
jyvikoandClaude Opus 5.5 3977a0b630 deps: zip 9.0.4; ignore the zip 3 extract advisories that come through solidcraft-io
RUSTSEC-2026-0367/0368/0369 are in ZipArchive::extract and
ZipStreamReader::extract. Our zip 9 is updated to the fixed 9.0.4. zip 3
comes only through the pinned solidcraft-io, which never calls extract
(3MF parts are read by name), so the three are ignored with that reason
until SolidCraft moves to a fixed zip.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-17 00:50:21 -04:00
jyvikoandClaude Opus 5.5 ec8e165fbb engine: guards in the Relax Nodes form without a default (strong mode only)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-16 20:41:27 -04:00
jyvikoandClaude Opus 5.5 a2a4f8895e mesh: strong relax keeps a morph unless it inverts, default reach five element lengths
Owner's rule (#438): move the bad nodes, interpolate, keep the morph as long as no
element inverts (guards "valid", the default); the old guards stay as "strict". The
run keeps its best round (highest smallest scaled Jacobian, then fewest elements below
the threshold, then highest mean) and never ends below the start. Default reach five
mean edge lengths of the bad elements (was three); on the Pi 5 base reach 3, 5 and 7
end at the same quality, 5 moving fewer nodes than 7 while leaving none below 0.7.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-16 20:41:27 -04:00
jyvikoandClaude Opus 5.5 426df4a333 mesh: improvement never leaves a tetrahedron floating; the gate refuses one
Deleting a cap tetrahedron (two boundary faces of one B-rep face) gives each of its
two other neighbours a boundary face; a neighbour that had three was left with every
face on the boundary, joined to the mesh by its vertices alone. On Linux the dome on
a plate (cmd::plan::decompose::tests::a_dome_on_a_plate...) got one and the static
solve found the stiffness singular. The kernel's tessellation and sphere parameters
differ in their last bits between macOS and Linux (SolidCraft, outside this repo);
given the same input the tet mesher gives the same mesh on both (checked by replaying
Linux's surface and spheres on macOS, which reproduced the floating tetrahedron).

The deletion now refuses when a neighbour would float, and the linear validity gate
fails a mesh with a tetrahedron that shares no face with the rest, saying so.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-16 20:41:22 -04:00
jyvikoandClaude Opus 5.5 2e639336c3 docs: skill results with the plan sized for its chords
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-16 20:13:29 -04:00
jyvikoandClaude Opus 5.5 8e5072edf0 testkit: why the sphere's fallback misses the volume limit
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-16 20:13:29 -04:00
jyvikoandClaude Opus 5.5 f2ea44b62d testkit: skill volume limit 1 %, causes of the parts now passing dropped
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-16 20:13:29 -04:00
jyvikoandClaude Opus 5.5 8ad0800aa6 mesh: size a plan so its chords keep the volume within 0.5 %
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-16 20:13:29 -04:00
jyvikoandClaude Opus 5.5 729a770d6c mesh: divide a block edge that bends with a curved target by its length
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-16 20:13:29 -04:00
jyvikoandClaude Opus 5.5 5db21aba7b ui: geometry.defeature.propose lists the small features with an on/off switch each (#433)
Holes by diameter, fillets by radius, chamfers by distance and bosses by height below
thresholds, or under a geometry.check finding; the reply panel switches each feature
off and on (geometry.defeature) and defeatures the whole proposal.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-16 18:30:45 -04:00
jyvikoandClaude Opus 5.5 2456912850 mesh: strong relax objective never ranks an inverted element below a valid one
In the max-min phase a valid element's term q^-16 reached 1e96 as q -> 0+, far above
an invalid element's 2e8, so the descent kept elements inverted rather than pass
through flat: the distorted plate stayed tangled (4 invalid) once its mesh was the
same on every platform. The shape term now continues as its tangent line below
q = 0.3 (finite, steep) and an invalid element costs 1e11, above any valid one.

Adds a test that the plate's mesh, its perturbation and the strong relaxation are
the same bits with 1 and 4 threads and match a fingerprint taken on macOS (CI checks
it on Linux).

Part of #438.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-16 18:30:00 -04:00
jyvikoandClaude Opus 5.5 700a7e3d10 mesh: platform-independent maths (libm) so meshes are bit-identical on macOS and Linux
The system maths library's sin, cos, atan2, log … are not correctly rounded and differ
between platforms: mesh.hex.generate gave the plate of the strong-relax test a different
mesh on Linux than on macOS (nodes from 2809 on), so the test passed on a Mac and failed
in CI. Every transcendental call in fieldcraft-mesh now goes through the libm crate (a
pure-Rust port of musl's), which gives the same bits everywhere.

Part of #438.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-16 18:30:00 -04:00