# JETcentral CFD — Lattice-Boltzmann

Main → CFD → Lattice-Boltzmann opens `Pages/Lattice-Boltzman.html` with a detached
geometry snapshot. Reopen after changing the aircraft in Main. No CFD action writes
aircraft JSON or cloud team projects. The existing saved-layout migration adds the
new pane and widget while preserving existing widget state and dragged positions.

## Workflow

1. Generate the STL preview without saving a file. The separate **Save .stl** action
   opens Save As, then offers to save the same file in Team Files / CFD. The CFD
   folder is created/reused only after cloud save is confirmed. Cancelling Save As
   saves nothing and does not open the cloud prompt. Edge/Chrome destination
   selection is required for local saving.
2. Review the CAD manifold STL and blue inlet/orange exhaust disks in 3D. STL numeric
   coordinates in saved STL copies are **feet**, X aft, Y right, Z up. The solver mesh retains SI coordinates with explicit units. The very same indexed triangles
   supply the solver; existing CAD union, topology repair and engine/duct cutouts are reused.
3. Review/edit paired disks using the boundary tables (advanced JSON is optional),
   total mass flow (kg/s), exit speed (m/s), freestream,
   density, viscosity, resolution and duration. Generated inlet boundaries preserve
   the shared renderer's actual inner lip outline (including noncircular openings),
   projected onto an X-normal plane. The plane is snapped to the first grid plane
   at least one cell behind the aft-most scarfed lip point; preview, case and solver
   share this placement and update it when resolution changes. The equivalent-area
   radius is informational; inlet X and radius are read-only. Y/Z translate the outline.
   Exhausts and manually added boundaries remain X-normal circles. Suggested exhaust centers
   use engine position plus length and need review. Zero mass flow disables a pair;
   a physically justified operating-point demand must be entered to enable bypass.
4. Select Local or Cloud and check capabilities. Select CPU or a native OpenCL GPU,
   then run. Run is available once geometry is ready; there is no review acknowledgement checkbox. Input changes hide old results, and active generation/submission prevents a conflicting run.
5. Inspect side/top velocity or Cp slices, density extrema, iteration history,
   physical/numerical viscosity, simulated time and requested/achieved bypass flow.
   Save the case/results through Save As. Case JSON is also runnable with
   `python lbm_engine.py case.json result.json`.

## Ideal bypass and NASA reference

[NASA Cart3D: Boundary Conditions for Simulating Propulsive Effects](https://www.nas.nasa.gov/publications/software/docs/cart3d/pages/howto/samples_power/README.html)
documents intake outflow and nozzle inflow boundary conditions. This is the NASA
reference for the propulsion-boundary concept; it does **not** validate this solver
or prescribe our paired source/sink implementation.

Each enabled pair removes distributions proportionally at the inlet, then adds
the same mass in an equilibrium distribution at the specified exit velocity.
Several mouths/exits can belong to one pair, avoiding duplicate demand for shared
inlets. A one-cell plug behind each sink and ahead of each source isolates the
omitted engine/duct path. The effective surfaces are voxel approximations of the
reviewed profiles/disks. Internal losses and engine restrictions are omitted. External lip
spillage remains possible because finite inlet demand still changes the captured
streamtube. This does not assume every approaching parcel is ingested.

The numerical model limits per-step removal to 1.5% of local inlet populations.
Requested and achieved flow and equal removed/added mass are reported; unmet
demand is not silently called successful. The net source/sink momentum is reported
separately. Do not add it to a thrust model without a consistent control-volume
accounting convention. Fuel mass addition and hot/compressible exhaust are absent.

## Solver scope and validation

This is an original JETcentral research implementation, **not OpenLB or NASA
Cart3D**. `scripts/lbm_engine.py` implements D3Q19 BGK with Smagorinsky Cs=0.16,
halfway bounce-back voxel walls, equilibrium upstream/lateral boundaries and a
zero-gradient aft boundary. NumPy CPU and PyOpenCL GPU use the same equations.
The lattice speed scale is 0.045, referenced to the larger freestream/exit speed.
Freestream is limited to 90 m/s; this does not model shocks, real compressibility,
heat transfer, combustion, engine maps, or boundary-layer-resolved aircraft drag.

Minimum base tau=0.501 introduces a **reported numerical viscosity floor**.
Smagorinsky adds further eddy viscosity. The returned effective Reynolds upper
bound can be much lower than the physical aircraft Reynolds number. This first
version is for qualitative experiments; it cannot certify aerodynamic performance.
No completion is automatically labeled converged. Wall-force momentum exchange is
currently returned for CPU execution only and is an instantaneous research value.

Default padding is 0.5 longest-aircraft extents upstream and on all transverse
sides, 1.5 downstream. There is no angle-of-attack input or adjustable domain yet.
Closed manifold geometry is required. Ray-parity voxelization fills the union STL;
sampled surface cells preserve thin walls but can enlarge them by a cell. Ports
under three cells across are rejected. The current case memory cap is 1 GiB and
run time cap is 30 minutes (worker watchdog 35 minutes). Finer real-aircraft
meshes may exceed these limits; do not interpret the coarse default as adequate.

Numerical regression checks are in `tests/test_lbm.py`: equilibrium moments,
collision conservation, uniform flow, periodic shear-wave analytical decay,
closed/open mesh handling, exact paired mass accounting and CPU/GPU agreement.
These verify implementation properties, **not aircraft CFD accuracy**. Before any
quantitative use, extend verification to published cylinder/sphere and propulsion
benchmarks and perform domain, grid and run-duration studies.

## Local execution

Save the helper ZIP from the page, extract it and install Python 3.10+ and the GPU
vendor's OpenCL driver. The start scripts create a private venv and install NumPy
and PyOpenCL. Run `start-lbm.cmd https://YOUR-WEBSITE` on Windows or
`sh start-lbm.sh https://YOUR-WEBSITE` on Linux/macOS. Use the precise origin shown
on the CFD page, including the localhost port during development. Several trusted
origins can be supplied directly through repeated `lbm-server.py --origin` flags.

The service binds only `127.0.0.1:8772`; explicit origin, Host, custom request header
and random browser-tab job owner checks prevent arbitrary websites from controlling
the helper. The browser may ask for local-network permission. GPU detection uses
the native OpenCL driver, not browser WebGPU. Total GPU memory and max allocation
are reported; free memory can be smaller. Conservative estimates and actual buffer
allocation gate GPU jobs. Unsupported GPUs do not silently run on CPU.

Jobs use a shared durable SQLite job store, isolated subprocess workers, per-owner
queue limits, idempotent submission IDs, cancellation, a bounded queue and six-hour
retention. The helper ZIP includes the existing FEA store module solely for reuse;
it does not start or modify the separate CalculiX services.
Submitted cases, submission UUIDs and job IDs survive a page reload in IndexedDB;
local job ownership survives in tab session storage. Check submitted job reconnects
without creating a second run. Completed cases and results are restored as well.
Temporary browser snapshots expire after 24 hours; server results expire after six.

## Cloud deployment

The cloud implementation is ready to configure; **an unconfigured deployment
cannot run cloud jobs**. Local browser tests do not establish VPS availability.
Install a separate service rather than changing the existing FEA ports/services.

On this Windows checkout, run `JETcentral/scripts/deploy-cfd-cloud.ps1` in
PowerShell. Enter the VPS password at the SSH prompts; it is never an argument,
stored credential or browser token. The script stages the source, runs the dedicated
installer, retrieves private settings, verifies authenticated HTTPS health and merges
only the CFD endpoint/token into local website configuration, with a private backup.
`-Resume` retries private settings retrieval and configuration after successful VPS setup.

For manual installation, copy `lbm_engine.py`, `lbm-server.py`, `lbm-requirements.txt`,
`fea-cloud-server.py` and `install-cfd-vps.sh` to a staging folder on the Ubuntu VPS.
Review existing RAM
reservations; the installer requires at least 2 GiB currently available RAM. Ensure
`python3-venv` exists, then run the installer as root. It creates:

- `/opt/jetcentral-cfd/{scripts,venv}`
- `/var/lib/jetcentral-cfd/runs` (service-owned job storage)
- `/etc/jetcentral-cfd/token` (private root/CFD-group token; never browser-visible)
- `jetcentral-cfd.service` bound to VPS loopback 8772, with a 1500 MiB systemd limit.

The installer adds `handle_path /cfd/*` to the existing `fea.jetdesign.online` Caddy
site, forwarding to `127.0.0.1:8772`; existing FEA routes remain unchanged. It validates
the complete candidate configuration before replacing the snippet, keeps a backup,
and restores it if reload fails. Unknown/custom CFD routes require manual review.
The website `CLOUD/config.php` needs `cfd_endpoint` set to
`https://fea.jetdesign.online/cfd` and `cfd_token` matching the private VPS token.
Keep TLS verification enabled and preserve all existing website configuration and FEA settings. Upload
`cfd-api.php` and `cfd-policy.php` together with the application release.

The same-origin PHP gateway checks the existing website access cookie, Same-Origin,
custom header and request size; it creates a distinct HttpOnly CFD owner session.
It sends Bearer credentials and HMAC owner identity privately to the VPS. Browsers
never receive the private token or choose arbitrary upstream URLs. The service
rejects jobs from a different owner. CPU is supported without a cloud GPU; native
OpenCL GPU use additionally requires a VPS GPU and driver access under systemd.

Verify cloud health through the authenticated website, then submit, poll, cancel,
retry the same UUID, test two-owner isolation and restart recovery. Do not consider
the service deployed until these pass. Check `journalctl -u jetcentral-cfd` for
operational failures; tokens and aircraft payloads should not be printed in logs.

## Boundary case compatibility

Cases with opening profiles use `jetcentral-lbm-2`: each profile is a simple polygon
of relative [Y,Z] metre offsets from its center; `aftLipX` records the aft-most
inner lip X in metres. The server recomputes the inlet plane from geometry bounds,
resolution and `aftLipX`, preventing forward snapping. Concave outlines use polygon
triangulation for preview and point-in-polygon masks for CPU/GPU. A transverse
extent under three cells is rejected for enabled pairs; refine for detailed features.
Regenerating an old single-pair circular suggestion upgrades its inlet geometry while
retaining flow demand and exhaust settings. Restored submitted cases keep their
original shape. Legacy circle cases (`jetcentral-lbm-1`) remain supported.
Update the native helper and VPS solver/gateway before submitting profile cases;
older helpers reject version 2 instead of silently substituting a circle.

## 3D solved-flow display

New results include `visualization` (`jetcentral-lbm-visualization-1`), a surface Cp
sample per submitted triangle and up to 2000 upstream-seeded streamlines with SI
positions and speeds. Both CPU and GPU use the same final-field postprocessor.
The results viewer provides independent Surface pressure map and Show particles
toggles, Cp or pressure relative to freestream in lbf/ft², and a 1 to 2000 flow-path slider. Controls
change presentation only; they do not submit jobs or invalidate results. Separate
pressure and speed color legends sit outside the rotatable, zoomable 3D canvas.
Particle colors use speed divided by the saved case freestream speed: green at 1.00,
blue at or below 0.80, and red at or above 1.20. The fixed range is shared by
full-domain and focused paths, so path count, focus changes and outliers do not
rescale the colors. Existing results use this scale without rerunning CFD.
Surface Cp uses fluid-only trilinear samples 0.75 to 3 cells along each triangle's
outward normal. Unsampled faces stay gray. This is a voxel near-wall approximation,
not a resolved wall-pressure calculation. Pressure difference is Cp times the
submitted freestream dynamic pressure; absolute pressure is not supplied.
Streamlines use midpoint integration of the final instantaneous velocity field,
stop at voxel walls/domain limits, and do not represent unsteady particle paths.
Particle size is decorative, not a flux-calibrated streamtube area. Seeds use a
distributed Halton sequence on an upstream plane around the aircraft bounds;
prefixes remain spread across that plane. The symmetric pressure color range
spans the sampled Cp magnitude; particle speed uses the fixed freestream ratio.
These products survive job/result recovery and are included in saved result JSON.
Older saved results remain viewable as slices and explicitly request a new run
with an updated solver to populate 3D data. Update the VPS solver or local helper
before creating these new products; old services cannot reconstruct them from slices.

### Path count and movable cuts

The Number of flow paths slider selects 1 to 2000 genuinely traced paths from the
solved field. Older results are limited to their existing path pool and explicitly
request a new run for 2000. Every selected usable path carries moving particles;
the saved paths remain intact and static tube geometry is not drawn.
X (transverse), Y (vertical longitudinal) and Z (horizontal) cutting planes support
keep-below, keep-above and a centered slab. Position is movable with a slider or an
exact feet input; slab width is a percentage of the scene's span along that axis.
The aircraft surface, particle heads and tails are clipped at exact plane intersections;
cut surfaces are open, not artificial pressure-bearing caps. Optional translucent
purple planes mark the boundaries. Invisible bounds keep framing steady while
moving cuts. These controls only change display; no flow is recomputed. Restored
results retain their flow data and start with the full display.

## Flight condition and cloud result files

Airspeed is the freestream magnitude in m/s (0.1 to 90). Alpha and beta are degrees,
limited to -45 to 45, defaulting to zero for legacy cases. With X aft/Y right/Z up,
the velocity is V [cos(alpha) cos(beta), -sin(beta), sin(alpha) cos(beta)]. Positive
alpha tilts flow toward +Z; positive beta is wind from the right. Both CPU and GPU
use all three components for equilibrium and farfield boundaries. The downstream
outflow remains X-normal; large-angle cases need domain/boundary sensitivity checks.
Streamline seeds are traced backwards from a distributed aircraft cross-section
to the inflow side of the padded domain, supporting oblique freestream directions.
Nonzero angles require `jetcentral-lbm-3`; older schema cases with nonzero angles
are rejected. Version 3 also supports inlet profiles. Update the helper/VPS solver
and PHP CFD gateway before running nonzero-angle cases; older services reject them.

**Save results to cloud** uses the current Main Login identity and the shared Team
Files save dialog with a chosen filename and explicit replacement confirmation.
The destination is Team Files / CFD / Results; folders are created only on save.
The versioned `jetcentral-cfd-result-file-1` JSON includes the submitted case and
geometry, original case checksum/job reference, completed pressure/velocity products,
and current display settings/camera. It contains no team PIN, cloud token or helper
owner credential. Local Save results JSON uses the same portable bundle.

**Load CFD results from Team Files** is available before generating an STL. It
lists only JSON files in that team's CFD / Results folder and validates their case,
checksum, geometry, slices and 3D data before replacing the page's current snapshot.
Loading restores airspeed/angles, ports, geometry, pressure maps, flow paths and display
controls without contacting the CFD job service or submitting a job. Main aircraft
JSON remains untouched. Submitted jobs must finish or be cancelled first. Identity
and revision checks prevent a delayed read overwriting a newly edited case. Loaded
results remain in the normal page checkpoint and can be exported or run again.
Legacy result JSON with an embedded case is also readable. Results without an
embedded case cannot reconstruct the aircraft and are rejected.

Hosted pages use the existing Team Files cloud backend. A localhost preview uses
the existing browser-only Team Files test store and identifies that storage mode
in both save and load dialogs; it does not claim those files are in the cloud.

## Particle size and focused paths

Particle size uses the existing 0.5 to 8 times slider, defaulting to 3. It scales
particle heads and tails and is a display dimension, not a flux-calibrated area.

New results also store `visualization.velocityField` (`jetcentral-lbm-velocity-1`):
the full solved grid, SI origin/spacing, component-major little-endian int16 velocity
values with a reported m/s scale and maximum component quantization error, and packed solid
bits. Both arrays use zlib/base64, preserving solver resolution without a large
uncompressed JSON vector field. The browser bounds decoded size to the declared
shape (maximum 3 million cells), rejects invalid payloads, and skips solid values.

Expand **Isolate paths from an upstream window** and enable **Filter by upstream
origin**. Wind Y/Z center and window width/height select existing solved paths by
their upstream starting positions. Increase **Number of flow paths** to make more
saved paths available. Filtering preserves the complete downstream paths, camera,
particle size, playback time and selected cut; it does not retrace or run CFD.
The saved velocity grid remains available for result processing, but the current
upstream filter needs only the existing path samples. Particle size, focus
dimensions, selected cuts and camera retain their existing saved-setting keys.

## Particle animation and export

In **Results**, **Show Stream Tubes** pauses animation and displays the full saved
paths as static tubes. **Play particles** returns to moving heads with fading
tails along the solved streamlines. **Particle Tail Length** ranges from zero to
aircraft length in feet, defaults to 1 ft, and measures distance along each solved
path independently of playback speed. Zero draws heads only. **Pause** freezes
time; rotating, zooming, selecting a camera pose, changing the number of flow paths
or moving a cut continues to reproject that paused frame. **Playback speed** scales
physical time (0.25× by default). Segment travel
times use the saved SI positions and `speedMS` samples, with interpolation between
samples. Motion describes advection through the final instantaneous field, not a
recording of transient solver history. Missing or unusable speeds never fall back
to geometry-derived flow. Older results without paths/speeds report unavailable.

The upstream filter and **Number of flow paths** select particle paths. Every
selected usable path carries particles, up to all 2000 saved paths. Smaller counts
use three staggered particles per path; higher counts distribute at least one per
path within a 2000-head limit and use fewer tail samples. Increasing the path count
never reduces the population. **Particle size** scales heads and tails. Tails stop at path restarts, curve along the
same samples, and obey the current cut. A separate transparent canvas uses the
shared aircraft camera and cached surface occlusion; animation does not rebuild
aircraft meshes, rerun CFD, or change aircraft JSON. Invisible full-field bounds
retain the same framing without tube geometry. Hidden tabs, offscreen
plots and the STL view suspend motion. Invalidated results clear it, and page
teardown releases animation frames, observers and listeners. Motion starts off;
saved result presentation retains playback speed, tail length and static-tube mode without starting animation.
Playback speeds are 1×, 0.5×, 0.25× (default), 0.1×, 0.05×, 0.01× and 0.0025×.
Existing saved `stream-tubes`, `tube-count` and `tube-width` settings remain readable:
they now mean particle visibility, selected flow-path count and particle size.

**Save particle animation** exports the current camera, aircraft, selected solved
paths and cut as real Motion JPEG AVI (20 fps), MPEG-1 video (24 fps) or animated
GIF (5 fps). Clips last 1–10 seconds. Video dimensions are bounded to 960 pixels;
GIF uses 640 pixels and fewer frames. Export uses deterministic frame sampling
at the selected playback speed and includes particles even if live motion is off.
AVI and GIF encode directly in the browser. MPEG lazily loads a pinned, same-origin
single-thread FFmpeg WebAssembly worker (about 31 MB); frames remain on the user's
computer. Cancel releases the encoder and writes no completed export. Save As
opens before preparation; browsers using the shared compatibility flow require
the final explicit **Save file** click. No new CFD job is submitted. **Save plot**
also includes the current particle frame.

## Feet-based page measurements

The page uses feet for boundary positions, radii, inlet profiles, cutting planes,
focus-box centers/dimensions and slice coordinates. True airspeed entry uses knots TAS with a Mach toggle; exhaust and displayed
flow speeds use ft/s; viscosity is ft2/s, density lbm/ft3, mass demand lbm/s,
pressure difference lbf/ft2, and forces lbf. Angles remain degrees and time seconds.
Summary fields carry explicit converted names (gridSpacingFt, airspeedFtS, etc.).
Advanced boundary JSON uses the page's ft/ft-s/lbm-s units and is converted before
validation or submission. Save STL writes feet and tags the binary header; the
review worker's internal SI mesh remains unchanged. No existing aircraft geometry
is rescaled in storage. Solver case/result JSON and compressed velocity grids retain
their existing SI schemas, preserving native/cloud jobs and older saved files.
All UI conversions occur at input/display boundaries. Saved views now declare
lengthUnits=ft; older saved view positions/focus dimensions are migrated from metres
while camera pan pixels, angles, counts and percentage widths stay unchanged.
The unchanged 90 m/s solver limit is approximately 174.95 knots TAS / Mach 0.2645.
Mach entry uses a fixed standard-air 59°F reference, a=sqrt(1.4*287.05*288.15) m/s; this is a display conversion, not a compressible model. Unit toggles preserve physical speed and completed results. Saved case speeds remain SI and restore into the selected entry units. Mach 0.7 is rejected; a compressible solver with appropriate boundaries and validation is required to support it.

## Flow controls and interactive cuts

The shared 3D display has STL and Results buttons. Generating geometry selects STL; completed or loaded results select Results. Switching views keeps the geometry and result data without running CFD. The XYZ cutting gizmo belongs to the Results view. On wide screens the display stays beside the controls while scrolling; on narrow screens the controls stack below it.

Aircraft and STL review, Conditions, Compute and Flow results start closed in the right-hand controls. The Aircraft header exposes Generate STL and its busy indicator. Conditions exposes true airspeed (knots/Mach), alpha and beta, with Atmosphere and Ideal bypass subpanels. Compute exposes Flow solver, Run location and the blue Run CFD button in its header, including when collapsed, and contains Solver, grid and run duration plus Compute and job controls, including Save solver case. Changing header input values still invalidates stale results; expanding a panel or switching display views is presentation only.

Flow results groups surface/particle display, animation export, clipping, upstream path filtering, 2D slices and run details into subpanels. Select a cut mode under Cut plane and clipping to enable the XYZ handles drawn over the 3D view. Drag the red X, green Y or blue Z arrow to select that plane normal and slide the cut through the corresponding coordinate. The exact location and slider remain synchronized in feet. Arrow keys move a focused handle by 1% of its axis range (Shift: 0.1%); Home/End reach the bounds. Escape cancels a drag. End-on axes use a screen-space fallback handle.

The cut control uses the shared renderer's public projection helpers with an SVG overlay; it does not rotate the camera, change aircraft geometry or submit CFD jobs. It works with WebGL and software rendering. Run `check-instance-startup.cjs check-lbm-cut-gizmo.cjs` for the focused interaction check.

## Compressible Euler backend (Mach 0.7)

Select **Compressible Euler** under **Solver, grid and run duration**, then use the knots/Mach entry in the header. Static temperature in **Atmosphere and airspeed reference** defaults to 59°F; Mach conversion uses a=sqrt(1.4*287.05*T), with T in kelvin. Density and temperature set the ideal-gas freestream pressure. A version-4 SI case (`jetcentral-cfd-4`, solver=euler) records temperatureK and physical speed. Older cases keep their LBM behavior and default 59°F reference; the low-Mach 90 m/s cap remains unchanged.

This is an original **CPU NumPy compressible Euler research solver**, not SU2, OpenFOAM or compressible LBM. It advances all five conserved variables (mass, three momentum components, total energy) with a first-order local Lax–Friedrichs/Rusanov finite-volume flux and a CFL-limited adaptive time step. Gamma=1.4 and R=287.05 J/(kg K) are fixed for ideal air. The maximum permitted freestream Mach is 0.7 at 200–500 K. Locally supersonic flow/shocks may arise; they are handled by the compressible flux, not clipped to a low-Mach lattice velocity. Slip reflective voxel walls enforce zero wall mass and energy flux. Incoming farfield characteristics/entropy use freestream values and outgoing characteristics use the fluid state. The domain matches the existing reviewed voxel grid, so inlet-profile planes remain aligned with the preview. Boundary/domain sensitivity must be assessed.

**Limits:** no viscosity, turbulence, skin friction, viscous separation, wall heating or engine cycle. The viscosity control is disabled for Euler. Voxel stair steps and first-order numerical diffusion limit aerodynamic accuracy. Finite-time results are always marked unconverged; neither method has aircraft lift/drag validation. This upgrade enables compressible research flow at Mach 0.7; it does not establish design-grade transonic aircraft predictions.

### Ideal bypass conservation

The existing inlet profiles/exhaust source planes and isolated engine plugs are retained. Each step removes a capped fraction of the inlet conserved state and adds exactly the same mass at the outlet. Mass-weighted removed internal energy is retained. Outlet momentum uses the requested body-X exhaust speed; the associated changes in momentum and kinetic energy are reported as momentumSourceN and energySourceW. Thus redirection work is explicit, rather than silently creating/destroying energy. Over-demand is capped at 2% of inlet-cell mass per step and reported by achievedKgS/demandLimited. There is no claim of a real compressor/nozzle thermodynamic cycle.

### Verification and references

`python JETcentral/scripts/euler_engine.py --verify` runs the production flux against exact Sod shock-tube star states, NASA/NACA-1135 normal-shock relations, a Mach-0.7 reflecting-wall moving-shock solution, Mach-0.7 uniform characteristic farfield, and an exact isentropic vortex at two resolutions. The vortex check requires error reduction under refinement; the shock and wall checks bound pressure/density/velocity error, and the Sod check bounds mass/energy conservation. This is numerical verification, not experimental aircraft validation. References:

- [Clawpack: Euler equations and exact Riemann solutions](https://www.clawpack.org/riemann_book/html/Euler.html).
- [NASA: Normal shock wave equations / NACA-1135](https://www.grc.nasa.gov/www/k-12/airplane/normal.html).
- [NASA: A Survey of the Isentropic Euler Vortex Problem Using High-Order Methods](https://ntrs.nasa.gov/citations/20150018403). The benchmark is shared; this implementation uses first-order finite volumes, not that report's high-order method.

The helper advertises solvers.euler only after these checks pass, and refuses compressible submissions if verification fails. An old helper cannot accept version 4; the page reports that it must be updated instead of falling back to LBM. The worker verifies again before solving. Result files retain pressure/flow paths, velocity grids, case temperature, verification metrics and display state, so existing CFD/Results cloud save/load and focused inspection need no rerun.

### Installation

No new dependency beyond NumPy. The helper ZIP and VPS deployment include euler_engine.py. Restart an existing local helper after updating its files. Run `JETcentral/scripts/deploy-cfd-cloud.ps1` from Windows PowerShell to upload/restart the VPS worker; it asks for the VPS password in the terminal. The installer runs numerical verification before restarting the service. Publish the updated website files including CLOUD/cfd-policy.php to allow version-4 cloud submissions. Existing FEA routes and authentication/owner isolation are retained. Euler is CPU-only on both local and cloud; OpenCL GPU execution remains available for LBM.

Panel 2 formats the airspeed entry as one-decimal knots or three-decimal Mach on blur, unit changes and case reload. Precise SI speed is retained beneath the rounded display, so presentation changes do not alter submitted or restored conditions. Typing in the field explicitly updates its physical value.

STL preparation starts automatically after the Main aircraft handoff or a JSON load.
The Aircraft panel shows preparation progress and Run CFD remains disabled until
the mesh is ready. Loading a newer aircraft cancels an obsolete preparation; a
failed preparation can be retried by loading the JSON again. Restoring a submitted
or completed case uses its saved mesh. Preparation does not open Save As, save an
STL, or submit a CFD job. Save .stl remains a separate action.

The Conditions load-factor estimate uses the same effective Official lift curve as
the Aero widget. Geometry-linked CLa and zero-lift alpha follow current wing/airfoil
edits, including camber; explicit external sources and JSON locks are preserved.
The detail shows CLa, zero-lift alpha and their sources, plus lift and the component
weight at 50% fuel and 50% expendable payload. It uses CL = CLa × (alpha − alpha0)
and n = q S CL / W, with signed lift below zero-lift alpha. It remains a linear
estimate, not a CFD result or a trim/stall prediction. Old unversioned references
are refreshed only from a matching full aircraft; otherwise reopen from updated
Main or load the full JSON instead of using a potentially stale lift curve.
Submitted-job recovery also retains a lift/weight reference bound to that case's
source hash. If neither the saved reference nor the current aircraft matches the
restored case, the estimate is unavailable rather than borrowing another aircraft's curve.


Streamline seeds use the complete aircraft projection onto a plane normal to the
freestream, padded by one grid cell on each side. The 2,000 low-discrepancy rays
are intersected with the interior flow-grid boundary before tracing. Both LBM and
Euler use this visualization routine. Previously saved streamlines are unchanged;
new coverage requires an updated execution helper and a new run.


## Aero Analysis wall loads
The Aero Analysis panel reports final instantaneous wall forces in lbf and moments
in ft·lbf, in body and wind axes. Moment references use the body origin, geometry
bounding-box center, or custom body coordinates in feet. Reference shifts are
M_ref = M_origin - (ref - origin) cross F. Force coefficients use qS; moments about
X/Z use qSb and about Y use qSc. Reference dimensions default from incoming JET JSON WINGS.Wing.S (ft²),
WINGS.Wing.b (ft), and WINGS.Wing.MAC (ft), and remain editable. Missing or invalid
values stay blank; none are guessed from a bounding box. They are retained in
saved result view settings and completed-job checkpoints. Body X is aft, Y right, Z up; wind X follows freestream.
All moment signs follow the right-hand rule about these axes.

Both LBM execution backends use a CPU reduction of the final populations' collision
and halfway wall-link momentum exchange, with physical wall-link positions for
moments. GPU memory estimates include that final reduction; GPU load parity still
requires an installed OpenCL GPU to exercise. Euler integrates its final reflected
wall numerical pressure flux at voxel-face centers. Solver outputs `wallForceN`,
`wallMomentNm`, `wallMomentReferenceM` and `wallLoadMethod` are retained in saved
results. Older results can display force without manufacturing missing moments.
Changing reference coordinates or dimensions needs no CFD rerun. New moment outputs
require the updated helper/VPS and a new solve.

Loads include virtual bypass plugs, and exclude external engine thrust attribution;
bypass fluid momentum sources are reported separately. Euler has no skin-friction
force. No converged, validated aircraft performance claim is made from these loads.

## Double-click local helpers

The page offers a Windows x64 EXE bundling the pinned FEA portable Python runtime, NumPy, PyOpenCL and transitive dependencies. `scripts/package-cfd-native.mjs` verifies the runtime and `CFDHelper/wheels.json` SHA-256 hashes before building. Wheel licenses are retained in the extracted site-packages. Builds do not download dependencies. Update the checked-in wheels and manifest together when updating the runtime. The original source ZIP remains available.

The Mac ZIP contains a Finder `.app` that opens an executable `.command` launcher in Terminal for Intel/Apple Silicon. Python 3.10+ is a one-time prerequisite; NumPy is installed with confirmation into a private user environment. This is a CPU launcher, not a self-contained or notarized Mac binary. Verify on macOS before claiming Mac runtime validation. Both launchers trust only the two production website origins by default; additional explicit `--origin` flags are supported. Existing browser local-network permission and Save As downloads remain required.

## Cancelling a CFD attempt

Cancel CFD is available next to Run CFD and in the error popup while connecting, submitting, or retaining a job. It aborts browser requests, stops polling, clears pending recovery state, closes the error popup, and immediately restores controls while keeping geometry and settings. Known service jobs receive a bounded cancellation request. A disconnected service or an unknown submission ID cannot confirm server-side cancellation; the page reports that limitation without locking its controls. Late replies cannot reactivate a cancelled attempt. Verify with `node scripts/check-cfd-cancel.cjs` and `node --test tests/lbm-cancellation.test.mjs`.

## Website gateway failures

A non-JSON HTTP 500 from the cloud gateway is a website/PHP failure, not a
solver result or proof that a login expired. Check the hosting PHP error log
for `JETcentral/CLOUD/cfd-api.php`. The gateway installs its JSON error boundary
before loading dependencies and logs the exception class, file and line without
returning private server details to the browser. Missing server files, invalid
private configuration or web-server failures still require a host-side repair.


### Cloud memory budgets and serial execution

The website PHP `memory_limit` only controls the gateway; it does not allocate
memory to the solver. The dedicated worker uses `JETCENTRAL_CFD_MEMORY_MB` as
its administrator-owned per-case estimate ceiling. Without that environment
variable, local helpers retain the 1024 MiB default. The maximum supported
administrator budget is 20480 MiB (20 GiB). Submitted cases can lower this limit
but cannot raise it; both Euler and LBM check before allocating their grids.
This is an estimate ceiling, not a physical RAM reservation or guaranteed peak.

Local/legacy deployments retain one sequential worker. InMotion can run two CFD
jobs when their combined grid peak estimates plus safety headroom fit within its
21 GiB aggregate cap. Other native tools without verified peak-memory estimators
continue to run alone. Extra jobs remain queued, and a POSIX
file lock prevents another service process from opening the same queue and
recovering its running jobs. The HTTP request-thread count is independent of
solver concurrency. Health responses expose `limits.memoryLimitMB` and
`limits.maxConcurrentJobs`.

The existing iFastNet worker (185.27.135.107) has about 7 GiB RAM; use a 4096 MiB
budget with a 5 GiB systemd service cap and only one active CFD job. The InMotion
website server (173.231.196.211) has 24 GiB RAM. A 20480 MiB budget belongs on
that larger host, after deploying a worker there and switching the website's
private endpoint. Changing the smaller worker's limit does not give it the
larger server's RAM. Other solvers and services can also consume memory.

For an existing worker, stage the updated `lbm_engine.py`, `euler_engine.py`,
`lbm-server.py`, `fea-cloud-server.py` and `upgrade-cfd-memory.sh` together and run, as root:

```sh
sh upgrade-cfd-memory.sh 4096
```

On a worker with enough capacity, pass `20480` instead. This updater checks
available RAM, refuses queued/running jobs, verifies Euler numerics, backs up
installed sources and its systemd drop-in, then restarts the worker. It checks
private authenticated health without printing the token and restores the prior
worker if that check fails. It does not move a worker to another server or
change website configuration, DNS, authentication or existing FEA routes.
Run during an idle maintenance window; a service restart interrupts requests.


For the approved 20 GiB deployment on the 24 GiB InMotion server, use
[the complete cloud migration](cloud-inmotion-migration.md). It colocates all
seven services, shares a memory-aware admission scheduler across solvers, and verifies the
aggregate 21 GiB memory cap before switching private website endpoints.
