Seed handling and input hardening (deferred from the B-list fixes) ===================================================================== DONE - Validate ranSeed in the GUI and startSequence: it must be an integer in a safe range (|v| <= 2^31-1, matching ISEED = IDINT(RI) in trimspNL.F). Enforced in runSimulationSequence() (before any job is generated) AND in CreateInpFile() (defense-in-depth), so a crafted share link or direct deck builder is blocked with a "please change it" message. Non-numeric / NaN / non-integer / oversized seeds no longer reach the engine. DONE - Derive per-scan-point seeds deterministically from the base seed and the scan index, so a multi-point run is reproducible in any finish order (currently: initialSeed + iScan, only when the user set a valid seed). DONE - Cap valEnergy magnitude at 60000 eV (positive incidence mode and the Maxwellian transmission mode |E0| = temperature). Stopping fits (KDEE1=4) are documented valid to ~50 keV for helium (meaning-of-params.txt:177-185), so 60 keV is the model's fitted ceiling. Applied to scanned energies and the base value in runSimulationSequence() and CreateInpFile(). - Warn when a shared configuration fixed the RanSeed: every scan point then uses the same generator, which can produce statistically misleading results. DONE (advisory, not a hard cap) - Warn on the web when numberProj exceeds 25000 per point. Measured cost model on a 3.2 GHz core, single-threaded engine: runtime is linear in numberProj and linear in energy, and cost per projectile is dominated by the number of nuclear collisions along the track, so the expensive axis is the LIGHT projectiles, not the heavy ones. Projectile -> Si, Maxwellian 60 keV, ZBL potential, ms/projectile: muon 3.04, H 3.30, He 4.06 (worst), Li-8 3.91, Be-11 3.64, B-12 3.19, Mg-31 1.34, Ar 0.83. Checked and found NOT to affect runtime: layer count (1..60 layers is flat, the species lookup is a binary search), depth increment dz (0.1 A == 20 A), and grazing incidence (ALPHA 89 deg is cheaper - the ion skims out). NH=25000 at the worst case measured 98.6/99.5/100.1 s on the 1.3.2 binary and 101.5/100.7/101.7 s on a fresh 1.4.0 build, i.e. ~3x margin under the 300 s TRIMSP_CMD_TIMEOUT. Deliberately warning-only: the value is advisory, the run still starts, and the user keeps the final say. Web-only, because the standalone app has no watchdog. TRIMSP_WEB_PROJ_ADVISORY in TrimSPlib.js; uses the max of the sweep when scanning numberProj. The dead GUI ProjNumberLimit() (TrimSPlib.js:3516) was left alone - it clamps at 5000, which is below the 50000-100000 used by the publication test configs, so wiring it up as-is would have clamped the regression suite. - Persisting the effective per-point seed: the seed already lives in each archived *.inp (all three deck slots), so reproducibility does not require the *.seq header change; extending *.seq would shift the 999(A20,1x) layer slots and break getSequenceColumnInfo(). Surfacing it in run details instead is deferred - not sure it is needed. Out-of-bounds recoil write in the sputtering path (parIRL >= 1) ============================================================= FIXED - the engine now refuses the divergent input instead of corrupting memory. Symptom, as originally reported: setting parIRL (the recoil/sputter output level) to 1 crashed the engine with SIGSEGV, reproducibly 5/5 seeds, and end to end through singleTrimSP.cgi as exitCode 139 / ok:false. parIRL=0 was never affected. Root cause was NOT the KO dimension. The first diagnosis blamed KO(600,MAXNL5,2) at trimspNL.F:114 (debug: "Index '601' of dimension 1 of array 'ko' above upper bound of 600"), on the grounds that the other recoil-list arrays are 2000 wide. Enlarging KO to 2000 does not help: with KO(2000) the run still dies, because the cascade overruns all of them. The actual cause is a non-converging cascade driven by parERC = 0. The primary knock-on test is `IF(DEN(IV).LE.ERC) GO TO 6`, so with a zero recoil cutoff every atom that receives any energy at all is respawned as a recoil on every cascade generation. The branching ratio exceeds one, so NREC1 and NREC2 grow geometrically instead of draining: NREC2 = 81, 153, 269, 455, 773, 1317, ... (parERC=0, parIRL=1) and the list-2/list-1 loop at label 83 never terminates. Instrumented runs show the block is never even entered when parIRL=0. parERC defaults to 0.0 in the GUI (TrimSP.html:293) and tests/Si_1layer/Si_1layer_E19000.inp carries 0.0, so the failing combination is the default one - no unusual user action is required to hit it. Evidence that a real cutoff converges: forcing parERC to 0.02, 0.05 or 0.2 makes parIRL=1 finish normally (exit 0), and the peak cascade depth is only 103, well inside KO's 600. KO was therefore left at 600, and the resize was reverted (it cost 7.7 MiB of .lbss and fixed nothing). Changes in fortran/trimspNL.F: - After readGuiInput, reject parIRL>=1 with parERC<=0 using a clear message and STOP 1, before any transport work. - The `IF(NREC2.GT.2000)` guard now stops with a non-zero status instead of falling through into the out-of-bounds write loop, so any future non-convergent cascade fails cleanly rather than corrupting memory. The GUI now catches this before the engine is ever invoked: - handleRecoilModeChange (wired to the parIRL widget) fills parERC with TRIMSP_MIN_RECOIL_CUTOFF = 0.02 when recoil calculation is switched on with a cutoff of 0, and says so in a transient warning. - A pre-run check in runSimulationSequence refuses the run if the user then insists on parIRL=1 with parERC=0, reports it in the status bar plus a transient warning, and focuses the parERC field. The handler is deliberately not wired to parERC, so retyping the cutoff is what "insisting" means and is not silently overridden. This also covers shared-configuration links, which assign widget values directly and so bypass the change handler. - The parIRL tooltip documents the requirement. Verified: parERC=0/parIRL=1 -> exit 1 with the new message; parERC=0.02 with parIRL=0 or 1 -> exit 0; parERC=0 with parIRL=0 -> exit 0 and byte-identical .rge (and an identical .out apart from timestamps/elapsed/filenames) versus the pre-fix binary. tests/cgi-tests.sh 18/18, plus tests/recoil-cutoff-test.js (12 cases over the shipped cutoff code) for the GUI behaviour above. Minimal repro: tests/Si_1layer/Si_1layer_E19000.inp with the last field of line 2 changed from 0 to 1 (parERC is field 10 of line 1, still 0.0). Deferred follow-ups from the C-list CGI hardening ================================================= - The perl "Statement unlikely to be reached" warning at the exec/_exit(127) child branch of run_bounded_command is intentional (matching muSRFlow) and does not affect `perl -c`; tests run with -X to keep output clean. - No global concurrency cap is implemented (by design, mirroring muSRFlow): concurrent per-point engine runs are allowed; archive is serialized per run with flock. Revisit if a runaway share link floods scan points. - Auth (MUCH-need, muSRFlow parity): none is implemented, keeping the read-only /tmp contract. If this CGI is ever exposed beyond a trusted LAN, gate it with httpd authentication or a shared-token header. - Restart integration (retch): the watchdog returns exit code 124 and the JSON "log" carries the description; a deployment that wants automated retries can key off exitCode === 124 and re-post the same .inp. - A future client change could surface TRIMSP_TMPROOT behind a shared configuration; the browser path regex is derived from the same env var, so both sides stay consistent. Follow-ups from the parIRL / parERC fix ======================================= - startSim() switches to the Results tab and purges the plots BEFORE runSimulationSequence() validates anything, so every refused run drops the user on an empty Results tab with no explanation of why. This predates the parIRL work and affects all the abort paths equally (energy cap, seed validation, recoil cutoff, and the re-entrancy guard in startSequence). Fix by validating in startSim() before the tab switch, or by moving the tab switch after the first successful job is queued. - The web deployment runs TRIMSP_BIN, which defaults to /usr/local/bin/trimspNL, so the engine-side fix in trimspNL.F does not reach the live site until the binary is rebuilt and installed. scripts/deploy-web.sh only rewrites the path in the CGI; it does not build. The GUI-side fix works immediately, so a deployment can briefly have the friendly GUI refusal in front of an engine that still segfaults on a hand-written deck. - Untracked scratch left in the tree, decide .gitignore vs delete: SrTiO3_E2000.inp (2 keV / 1000-projectile benchmark deck at the repo root), fortran/Makefile_org (Makefile backup), fortran/profile.txt and fortran/profile_annotated.txt (gprof reports; .gitignore covers gmon.out and fortran/trimspNL-profile but not these), and tests/pcg32-test/, tests/ranlux-test/, tests/xoshiro-test/ (per-backend engine run output trees from the RNG comparison, i.e. generated results rather than fixtures).