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.
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
Suppresses the SolidCraft features named (holes filled, fillets and chamfers back to
their sharp edges, bosses removed) and rebuilds the design; a feature that cannot be
suppressed alone is merged into its neighbour for meshing only. Kept in the model,
replayed on open, undoable; geometry.defeature.delete turns them back on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
SolidCraft lists a solid's faces in a different order from run to run
(the rounded box, for one), which reordered the triangle soup and the
face tags with every coordinate the same; welding keeps the first copy
it meets and every later stage visits ids in order, so the tet mesh
changed between runs. CanonicalSoup sorts the triangles by their
coordinates, numbers vertices by first use and face tags by first
appearance, and maps the tags back; mesh.tet.generate and the corpus
use it.
Tests: the washer's tet10 mesh has a fixed fingerprint (identical on
arm64 and x86-64 builds); a reordered, renumbered input gives the same
mesh; heavy: rounded-box and filleted-bracket mesh bit for bit the same
on repeated runs and with 1 and 8 threads.
The bracket oracle cases stay unfrozen: SolidCraft's tessellation
itself differs in the last bits between arm64 and x86-64. The coarse
case moves to 203.6 MPa with the canonical order; its band is 5%.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
mesh.size.set adds a size control on faces, bodies, a box or a sphere;
mesh.size.growth sets the growth rate; mesh.size.clear and
mesh.size.list. The tet mesher bakes the controls into its graded field,
the hex meshers, cells and the octree take the graded RegionSize, and
the block projection and mesh plans divide their edges by it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
The thinnest wall (measured to a facing wall) and narrowest patch give the cell size the grid
needs; the requested size refined that deep comes first, a root shrunk so one level fewer is
exact second; beyond the levels or cells allowed meshing fails at once naming the wall, levels and
cells. max_depth is a cap (default 4); rebuilds stay within the planned levels; lattice positions
no longer depend on the lattice depth; the report gives root size, levels, wall, patch and cells
per level before and after closure.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
geometry.face.split {name, face, datum} records a face split; it is
applied after the merges whenever the design is opened or cut again,
follows its datum (which it holds), undoes, and is saved with the
model. geometry.face.split.delete removes it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
For a body with features the sweep cannot mesh, mesh.plan replies a
decomposition: the cuts that box those features into one cell, the region,
and the commands that make the cuts and mesh the cells with pyramids. A plate
with a joined dome: 5 cuts, 17 hex cells swept together, the dome cell tet,
valid and conforming, end displacement in tension within 0.4% of all-tet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
The owner's direction: bad nodes move to better places, the nodes within a reach follow by
the graph Laplacian's harmonic extension, edges held, nothing outside the reach moves.
mesh.node.relax strong mode takes below and reach and reports each round.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
A five-node pyramid, the conforming transition between a hexahedron's face
and tetrahedra: Bedrosian's rational shape functions with a collapsed (Duffy)
Gauss rule, a certified Jacobian on the base square with exact volume, a
scaled Jacobian, C3D5 / CPYRAM / LS-DYNA degenerate solid, VTK_PYRAMID and
the binary mesh code 13. A distorted hex8-pyramid5-tet4 transition mesh
passes the patch test and uniaxial tension exactly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
geometry.stitch {name, surface, tolerance} records a stitch; the
surfaces keep what was read (TaggedSurface::original) and are stitched
again from it whenever a surface is imported, the model opened, or a
stitch added or deleted (geometry.stitch.delete). Opening a model also
warns about merges that no longer apply.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
A face merge may keep one of its faces' names (Merge::into, model
FaceMerge::into): absorbing a tiny face leaves the neighbour by its own
name, so sets on it still select it. Shape::absorbing_neighbour finds
the face a tiny one shares the longest edges with. geometry.face.absorb
absorbs one face (into a named or the best neighbour) or, from_check,
every small and sliver face the last geometry.check marked; check marks
now carry the small face they found.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
geometry.faces.merge {name, faces} records a face merge in the model;
it is applied after the partitions whenever the design is opened or cut
again, undoes through the journal and is saved with the model. Meshers,
mesh.plan (features and rings see merge:<name>; a merged face is
prismatic when its B-rep faces are), checks and the viewport see one
face, drawn in the warm colour. geometry.faces.merge.delete removes it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
The updater's release endpoint, docs, README, release notes and the Pages
URL point at the tangency-labs org. App IDs (io.github.jyviko.fieldcraft)
stay unchanged so installed copies keep their identity and settings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
A nodal field at a node per frame (nonlinear static displacements from
load factor 0, explicit frames, heat temperatures) or per mode, the
explicit energies and histories, history nodes, and the frequencies or
buckling load factors; capped at 100000 points with `every`; stale
results refused; `path` writes a two-column CSV. XY Plot is ready.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
egui-wgpu's startup hook and the app's own steps (window, app setup, viewport
pipelines, first frame, running) go into the journal. On Windows,
native_crash.rs (adapted from SolidCraft, the only module allowed unsafe code;
the app crate now denies instead of forbids it) writes the exception code,
faulting module and offset and a minidump. A nightly/on-demand CI job runs the
app's tests on Windows, including a child that faults on purpose.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
#445 also closed the window on SIGHUP, which overrides the SIG_IGN that
`nohup fieldcraft &` sets so the app outlives its terminal. A safe API cannot
ask whether a signal was ignored at launch, so SIGHUP keeps its default.
SIGTERM and SIGINT still close the window the normal way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
SIGTERM, SIGINT and SIGHUP ended the process on the spot: no last autosave,
and the crash journal left behind became an unclean_exit report at the next
start. The report on #248's machine ("ended without a clean exit between
commands (last: control ui.capture)") has that exact signature and is
reproduced by kill -TERM after a capture; 500 captures of the solved
crank-mirror (50 app runs), 30 CLI snapshots and 250 MCP headless
screenshots exited cleanly. The first signal now asks the window to close;
a second ends the process with the signal's default action.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
Static, frequency (per mode), nonlinear and explicit (per frame, with a
.pvd collection) and heat results; fluid steps are refused. Wedges keep
the model's node order: VTK 9's wedge has the same orientation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
--section x|y|z[=VALUE] hides the elements on the camera's side of a plane
(at the mesh's middle by default) so the element faces inside show;
--field contours a nodal field of the solved step (results.contour). The
MCP server's headless screenshot draws with the viewport's pipeline on the
shared device and its reply, like snapshot's, names the renderer.
Refs #422
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
fieldcraft-cli snapshot script.json --out shot.png [--view] [--size WxH]
draws the engine's framed scene (engine::view::frame, shared with the CPU
render_png) with the viewport's GPU pipelines offscreen: hardware GPU, then
a software one, then the CPU rasterizer (FIELDCRAFT_GPU=auto|hardware|
software|cpu). CI installs lavapipe so the GPU path runs there.
Refs #422
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
The section comes from a set (a 2D mesh or element faces), the mesh already
on the source faces (tagged, or a neighbouring body's boundary lying on
them, whose nodes are shared so the meshes conform), or a fresh quad mesh
of one planar face. Target and side faces project through the tet mesher's
B-rep projection. Replaces the body's mesh like the other meshers, tags the
body's own source, target and side faces, and is on the MCP mesh tool as
hex_extrude.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
Deleting, suppressing or editing a partition (or its datum), or cutting a
meshed body, removes the meshes of the bodies that are gone, reported as
replaced, in the same undo step. model.check lists meshes of partition
cells the open design no longer has (a model saved before the cut
changed), and mesh.clear removes them, or the meshes of named bodies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
The file is no longer committed: dependency pull requests from forks failed CI only because
it was out of date. The Dependabot workflow that regenerated it is gone. licences now fails
when a shipped crate names no licence and carries no licence file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
Window captures (light theme, 1400 x 860) of every sample solved on the current UI; the
thumbnails that were viewport crops are now clean ui.render images like the others; the drop
test's figure at 0.1 ms reads 129 MPa.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo