Repository navigation
Releases: LoveDaisy/Lumice
Releases · LoveDaisy/Lumice
Release list
v4.7.4
⚠️ Breaking Changes
LUMICE_API_VERSIONnow lives in a new leaf headerlumice_api_version.h, notlumice_base.h(#509). The macro moved out of the wide-inclusion base header — a value bump used to invalidate the compile cache of every TU inlumice_base.h's includer closure, and now only the TUs that textually use the macro recompile — and its per-version change history moved with it. Code that readLUMICE_API_VERSIONafter including onlylumice_base.hno longer compiles: add#include "lumice_api_version.h". The macro's meaning is unchanged, and the 453 → 454 value bump is not part of this move — it is #508's surface addition below, recorded in the new header's history.
CLI
- Added:
Lumice raypath survey(#508, #511, #512). A target-free census face of the raypath analysis: given one crystal entry and one path (--crystal 1 --path 1-2), it routes the path's class at the algebra layer and emits one JSON document (schemalumice.path-survey,schema_version4) — the configurational analysis of that class, without choosing a sky point, sampling, or warm state. A class whose every member carries the identity certificate collapses to a single source image at the sun and the analysis stops there; any other member verdict ships the structural facts with named reasons and leaves the deeper object kinds unevaluated.--literalanalyses the requested face sequence as written instead of the physical P|B|D class;--lambdapins the wavelength;--budgetbounds the request. Internal analysis deepening since the first cut (the full-dimensional delta machine, the chromatic tint leg) is part of the same output. - Changed:
-vand-dare now observable (#502, #503, #506). The engine's only VERBOSE log site had become unreachable on both production consumption paths, so-vran silently no matter what it was given; the dead site is gone and both flags now print a parse-time level line (-varms VERBOSE,-darms DEBUG), and--helpdescribes-vas the verbose level instead of implying a deeper trace.
GUI
- Added: an entry bar in the Edit Entry modal (#497). The crystal's participation toggle and Weight slider now sit at the top of the Edit Entry modal, buffered like the modal's other fields (nothing writes through until OK), so re-enabling a disabled crystal or retuning its weight no longer requires closing the modal to reach the card. The card keeps both controls.
- Changed: the Axis tab is a one-gesture editor (#495, #496, #513). The preset name is a label the user chooses, never re-derived from the parameters: clicking one of the six label chips sets the label and loads that preset's parameters (remembered per crystal for the session); the old Apply-preset combo is gone — a chip click does that job, and a Reset row reloads the current label's definition without touching the label, the memory, or the view. Applying parameters no longer resets the preview view, and parameters drifting outside a preset's usual range light a mismatch badge, with an exact "currently reads as" tooltip, instead of silently renaming the axis.
- Fixed: the analysis list's crystal numbers now match the crystal cards (#494). The list showed scene-insertion-order ids (
C0,C1, …) where the cards show pool ids (#0,#1, …); the two diverged after dragging a card within its layer or deleting one, and the analysis list then described the wrong crystal. Both now speak the card numbering. - Fixed: the lens border colour is gated under print mode (#504).
tone=printlays annotation ink by density and never reads hue, but the lens border — an annotation by construction — still took its colour from the screen setting and could print in colour. It now joins the same gate as the horizon, grid, sun circles, distance rings and markers, and stays editable under screen.
Engine C API
- Added:
LUMICE_API_VERSION453 → 454 — the path survey surface (#508).LUMICE_AnalyzePathSurvey,LUMICE_PathSurveyToJsonandLUMICE_PathSurveyDestroy, theLUMICE_PathSurveyRequeststruct, the opaqueLUMICE_PathSurveyhandle and the two budget constants (LUMICE_PATH_SURVEY_MAX_BUDGET_MS,LUMICE_PATH_SURVEY_DEFAULT_BUDGET_MS) publish the same survey the CLI subcommand prints: one schema-4 document per request, serialized engine-side once. A pure append — nothing existing changed. The library is still not part of the download packages.
What's Changed
- docs(raypath): align analysis docs with the current product state by @LoveDaisy in #492
- test(gui): restore shared window basis at case entry with a leak sentinel by @LoveDaisy in #493
- fix(gui): align analysis list crystal numbers with card IDs by @LoveDaisy in #494
- feat(gui): sticky user-chosen axis label, decoupled from preset apply by @LoveDaisy in #495
- feat(gui): Apply preset is a pure parameter shortcut, label and view untouched by @LoveDaisy in #496
- feat(gui): entry bar in Edit Entry — weight and participation toggle in the modal by @LoveDaisy in #497
- docs: organize documentation into topic directories by @LoveDaisy in #498
- docs: reconcile guides and architecture with current implementation by @LoveDaisy in #500
- Fix dump_xyz_stats.py: consume current C API contract via CLI npy export by @LoveDaisy in #501
- fix(cli): describe -v as verbose level in help text by @LoveDaisy in #502
- fix(cli): describe -v as verbose level in help text, pin -v/-d level mapping by @LoveDaisy in #503
- fix(gui): gate lens border colour under print mode (join NotUnderPrintMode family) by @LoveDaisy in #504
- chore(source-comment-consistency): rewrite five drift groups of src/ comments by @LoveDaisy in #505
- feat(cli): make -v/-d observable — drop dead VERBOSE site, add parse-time level lines by @LoveDaisy in #506
- test/e2e cost trims + shadow bench probe (697 PR1: restore work headroom, instrument the budget) by @LoveDaisy in #507
- refactor(capi): move LUMICE_API_VERSION to a leaf header by @LoveDaisy in #509
- feat(raypath): survey CLI skeleton + C14 smoke slice (schema4) by @LoveDaisy in #508
- chore(test): restore the endpoint-bisection registry entry dropped in #507 by @LoveDaisy in #510
- feat(raypath): survey C01 full-dimensional delta machine, radial probe and chromatic layer v1 by @LoveDaisy in #511
- feat(raypath): survey C15 slice — weight_tint chromatic leg (smoke trilogy finale) by @LoveDaisy in #512
- GUI: axis chips switch label and parameters, Reset row replaces apply combo by @LoveDaisy in #513
- ci: per-miss build budget — adaptive limit + absolute backstop (registry v3) by @LoveDaisy in #514
- docs: slim root AGENTS.md to constitution + route map (983→396 lines) by @LoveDaisy in #515
- chore(test): track li-parity fixtures in Git LFS by @LoveDaisy in #517
- ci: per-miss budget on all four legs + envelope-derived outer bounds by @LoveDaisy in #518
- refactor: single authority for the pi/vec3 families (census ledger item 2) by @LoveDaisy in #516
- feat(ci): test-TU cost declaration gate — fifth diff-scoped checker by @LoveDaisy in #519
- chore(release): cut 4.7.4 by @LoveDaisy in #520
Full Changelog: v4.7.3...v4.7.4
v4.7.3
⚠️ Breaking Changes
Lumice raypath --reportnow emits schema 3 (#475, #477, #478;LUMICE_API_VERSION
452 → 453). The target-free feature report serializes asschema_version: 3where 4.7.2
emitted 2, and a reader written for the schema-2 document must reject a v3 document (the
engine's own reader does) — update any script that parses--reportJSON to accept schema 3.
The change is the report's shape, not just its number: where schema 2 was a per-feature list
with Monte-Carlo evidence, schema 3 emits structural objects — ascopeblock, asupport
block carrying the delta-axis partition with its completeness certificate, the per-wavelength
onset table and the restricted family curve, per-feature blocks,unattributed_structures,
andmc_evidencedemoted to a corroboration field. The request surface (target schema 1, the
fiber walk,--warm) is unchanged.
CLI
- Changed: config fields that silently did nothing now warn at startup (#476). A config that
declares a filter nothing binds, places afilterkey where nothing reads it (on the scene or
a scattering layer, instead of per entry), or commits an emptyrenderlist used to be
accepted without a trace; the engine now logs a warning naming the no-op. Every such config
still renders byte-identically — the change is the warning, not new behavior.
GUI
- Added: Look At target locking (#483). The camera row is now
Reset→Look At→Lock.
TheLook Atcombo points the camera at a named direction in one click; withLockchecked,
the camera keeps following that direction as scene values such as sun altitude change, without
a Run. EditingElevationorAzimuth, or dragging the preview to rotate it, turnsLock
off and keeps that manual view; changingRoll,FOV, or the preview zoom does not. The
selected target is retained, so re-checkingLockresumes following it.Reset, a lens
change, or a document New/Open/Revert turnsLockoff (document changes also reset the target
to Sun); full-sky lenses disable both controls. - Changed: the Sun diameter field accepts 0 — a point source (#486). The GUI's sun-diameter
editor clamped the value at a 0.1° floor, so a point-source sun (diameter 0, which the
engine's default scene uses and the config file has always accepted) could not be entered
from the panel. The floor is gone; the tooltip names what 0 means. - Fixed: the Look At row aligns with the panel's control grid (#487, #489). The
Reset/
Look At/Lockrow drew its three controls at mismatched heights and off the grid the
right panel's other rows follow; all three now share one full-height row that ends on the
Lock checkbox, its right edge on the same line the FOV input ends on.
Rendered output (GUI + CLI)
- Fixed: dual-fisheye fold-boundary energy loss (#480). The dual-fisheye projection passed
the fold-boundary direction through a float32 rounding that pushed rays out of the projection
and let the bounds check silently drop them — roughly 18.9% of a dual-fisheye frame's energy.
The boundary is now clamped after the floor, so the dropped rays render; the projection's
shared path is fixed once, for both the CLI and the GUI.
liblumice_analytic
- Added:
LUMICE_ANALYTIC_API_VERSION7 → 8 — the u-S² field layer (#477). Eight new calls
(TraceBoundaryLoop,TraceWeightKinks,ClassifyCriticalStructure,PartitionDeviationAxis,
TraceWavelengthCriticalTable,TraceRestrictedFamilyCurve,DiagnoseChromatic,
DiagnoseClassTint) with their result structs,Releasefunctions and twelve enumerations
publish the deviation-field geometry of one fixed face sequence: kind-1/2/3 critical structure,
the delta-axis partition with its escape regimes, per-wavelength onset tables, the restricted
family curve, and the two-index chromatic verdicts. Nothing existing changed. The kernels are
the C++ port of Lumice Integral's field layer, pinned against it by parity fixtures; the
library is still not part of the download packages.
What's Changed
- feat(raypath): schema3 measure layer (task-661) by @LoveDaisy in #475
- fix(config): warn on the two silent no-op config shapes by @LoveDaisy in #476
- feat(analytic): schema3 geometry layer port (u-S2 field, partition, walks, focusing/chromatic, contour, API v8) by @LoveDaisy in #477
- feat(raypath): schema3 report - state machines, support block, unattributed, mc_evidence, schema_version=3 by @LoveDaisy in #478
- Share-domain calibration: lens landing semantics + dual fold-boundary defect + cone cross-check tool (task-669) by @LoveDaisy in #479
- fix(raypath): clamp dual-fisheye fold-boundary pixels into canvas (task-670) by @LoveDaisy in #480
- Clarify mixed-corner tolerance consumer comment by @LoveDaisy in #481
- ci: reduce repeated raypath test work and govern cumulative duration by @LoveDaisy in #482
- feat(gui): add Look At target locking with manual takeover by @LoveDaisy in #483
- chore(release): cut 4.7.3 by @LoveDaisy in #484
- docs: migrate repo name ice_halo_sim to Lumice by @LoveDaisy in #485
- fix(gui): allow sun diameter 0 (point source) in the GUI domain by @LoveDaisy in #486
- fix(gui): align the View Look At row to the panel's control grid by @LoveDaisy in #487
- fix(ci): fix ccache seed audit false positive (one write per miss) by @LoveDaisy in #488
- Fix: revert right panel to 300px and end the Look At row on the Lock square by @LoveDaisy in #489
- chore(release): recut 4.7.3 by @LoveDaisy in #491
Full Changelog: v4.7.2...v4.7.3
v4.7.2
CLI
- Added: the
Lumice raypathsubcommand (#445) — the physics behind one raypath, without
the UI.Lumice analyzelists a scene's raypaths;Lumice raypath --crystal <n> --path <p>
takes one of them and reports what the light does through that one crystal: fiber components
at a sky point (seed search, then the fiber walk — the level set on the sun-direction sphere),
per-point detail (crystal pose, the sun as seen inside the crystal, segment directions,
per-interface energy), a grid over the sun-direction sphere, and areachfield that splits
an empty result three ways — a degenerate point-mass path, a geometrically unreachable
target, and a target not excluded but not found (most often because no finite crystal admits
the passage).--warmre-reads a previous run's JSON and densifies from its seeds. The result
is serialized once in the engine (target schema 1,doc/raypath-cli-output.md; user-manual
section in en/zh). This is the analysis half of the raypath feature: the GUI workspace it
feeds is still being designed, and the JSON grows by appended fields only. - Added:
Lumice raypath --report— target-free feature diagnostics (#464, #468). Instead
of following one chosen path,--reportasks which halo features the document's actual
spectrum, crystal shape and orientation fold can support, and answers per candidate with a
three-tier verdict —actual(confirmed by sampled evidence),candidate(plausible but
under-evidenced), orunfinishedwith its reason — alongside per-wavelength finite-crystal
A·T estimates, convergence evidence, positions and explicit coverage limits. A combined
numerical budget bounds the work (--budget-ms, default 15 s, max 120 s;
--max-evaluations;--max-field-evaluations); a run that exhausts it still emits its
partial report markedpartial, and a report that fails to parse leaves stdout empty.
Serialization is report schema 2 (doc/raypath-cli-output.md§7); the target/fiber/--warm
contracts above are unchanged.
GUI
- Fixed: window geometry at fractional Windows display scales (#465). Match Background
computed its window size from an unscaled minimum, so at a fractional scale the system
imposed its own scaled minimum instead — changing the preview's aspect ratio and leaving
blank bands above and below the image. Startup, aspect presets and scale changes now share
one geometry model (current monitor work area, actual decoration sizes, scaled minimums);
programmatic resizes settle as bounded transactions that expire unconfirmed results rather
than act on them; and a preview area that cannot represent the requested aspect is clamped
and reported from the actual area. - Changed: import diagnostics live in one Load Notice (#466). The warnings raised while
loading a document are consolidated into a single load-time notice, and a preview restored
from cache states what its texture mode can and cannot show (recommending Run then Save)
instead of leaving the limitation implicit. Cached previews are kept; nothing reruns behind
the user's back.
Rendered output (GUI + CLI)
- Fixed: sporadic light-leak points on Parry-oriented scenes (#472). Single bright pixels
near azimuth 160–162°, altitude ≈0° (beta-reported, no multi-scattering). CPU and Metal
judged whether a spawned child ray had left the crystal implicitly: the face it was born on
could win the min-t race at t ≈ 0⁺ and slip past a guard whose epsilon has the wrong
dimension for grazing children (t = δ/denom, so denom → 0 blows a ~1 ulp plane residual past
any fixed ε) — the child then re-hit its own birth face, TIR'd back inside and exited
unphysically. Each event carries ~1e-5 of the ray's energy but is visible because
grazing-incidence Fresnel reflection is near-total. Children are now classified at birth by
the pure sign of the source denominator, in CPU and Metal (CUDA already had the semantics;
the three backends now share one shape), which also removes the edge-birth phantom family. - Fixed: the
channel_brdisplay mode no longer subtracts the sky into the diagnostic
(#466). The B−R difference used to include the configured sky background — an empty blue
sky rendered white, which is background, not signal. Both the CLI's and the GUI's B−R output
now diagnose halos only; the post-gamma formula, gain, exposure, masking and annotations are
unchanged, so a scene whose sky contributes no channel difference renders as before.
Engine C API
- Added:
LUMICE_API_VERSION449 → 452 (#445, #464, #468) — new functions only; nothing
existing changed shape or meaning. v450 addsLUMICE_AnalyzeSinglePath/
LUMICE_SinglePathResultToJson/LUMICE_SinglePathResultDestroy— the single-path result
is serialized once in the engine, and the CLI and the future GUI read the same JSON — plus
LUMICE_SetLogLevel(NULL)to set the engine-wide log level. v451 adds the
path-feature-report entry points with their
LUMICE_PATH_FEATURE_REPORT_MAX_SAMPLE_COUNT/..._MAX_WAVELENGTH_COUNTbounds. v452 adds
a report-request suffix carrying budgets, observation, layer and symmetry bits; requests in
the old prefix form are accepted with the new defaults. - Changed: the single
lumice.his gone; the C API is six capability headers (#453) —
lumice_{base,scene,render,editor,engine,raypath}.h, one bridge file per header, same
functions and values: the split left the export tables byte-identical and moved neither API
version. The engine and testapi shared libraries additionally export the analytic capability
(lumice_analytic_core.h), and each packaged library's export surface is declared once in
cmake/export_surfaces.cmakeand checked in CI and at packaging time. The headers still do
not ship in the download packages, so this reaches source-level consumers only.
liblumice_analytic
- Added:
LUMICE_ANALYTIC_API_VERSION1 → 7 (#444, #446, #468) — the analytic library's
surface grew to two modules plus diagnostic numerics: module A
(LUMICE_ANALYTIC_EvaluatePath,TraceFiber/TraceFiberBatchwith per-pose diagnostics,
DiscoverComponents), module B (LUMICE_ANALYTIC_BandSum— bounded sums over sampled
poses, rank-0 point-mass branch included), and the v7 diagnostics (batch diagnostic
evaluation, source-event brackets, weighted-field values, fixed-observation colour bands).
The kernels are pinned against Lumice Integral by replayed parity fixtures. Consumers build
against the install tree withfind_package(LumiceAnalytic 7); the library is not yet part
of the download packages.
What's Changed
- ci: add top-level permissions: contents: read to workflows by @LoveDaisy in #443
- liblumice_analytic module A v0: EvaluatePath, TraceFiber, DiscoverComponents + LI parity by @LoveDaisy in #444
- Lumice raypath: single-path analysis module, lumice.h v4.50 entry, D4 verification by @LoveDaisy in #445
- liblumice_analytic wave 2: per-pose diagnostics (API 5), band sum module B (API 6), 93 LI parity fixtures by @LoveDaisy in #446
- docs(analytic): three kinds of critical lines; LI 31c682e parity fixtures (94) by @LoveDaisy in #452
- Organize the C API and engine by capability by @LoveDaisy in #453
- Split c_api_scene.cpp into bridge, encoder and decoder by @LoveDaisy in #455
- scrum ci-time-governance (WIP): build-time landing by @LoveDaisy in #451
- ci: continue time-governance calibration by @LoveDaisy in #462
- docs(ci): close time-governance acceptance by @LoveDaisy in #463
- Add target-free raypath feature diagnostics and scientific acceptance by @LoveDaisy in #464
- fix(gui): keep background aspect ratios consistent across UI scales by @LoveDaisy in #465
- fix(render): exclude sky background from B-R diagnostic by @LoveDaisy in #466
- fix(testing): isolate local slow pytest performance pool by @LoveDaisy in #467
- feat(raypath): phase-1 raypath feature diagnostics (scrum-649) by @LoveDaisy in #468
- refactor(raypath): module design cleanup — role-based naming, detail/ demote, JSON single-owner by @LoveDaisy in #469
- fix(sky-direction): resolve convention-mismatch investigation — call sites proven correct, defense lines landed by @LoveDaisy in #470
- docs(cases): add raypath analysis scenario-case corpus index by @LoveDaisy in #471
- fix(core): classify outward children at birth in CPU/Metal slab traversal by @LoveDaisy in #472
- docs(corpus): C12 tint multi-caliber + C06 LI closed-form anchor by @LoveDaisy in #473
- chore(release): cut 4.7.2 by @LoveDaisy in #474
Full Changelog: v4.7.1...v4.7.2
v4.7.1
Added
- C API:
LUMICE_API_VERSION446 → 449 (#429, #430, #436) — new functions only, nothing
existing changed.LUMICE_GetCrystalSymmetry/LUMICE_CrystalSymmetry: which of the P/B/D
symmetry elements a crystal's shape actually has (a prism with unequal alternating face
distances has a three-fold axis, not a six-fold one).LUMICE_IsPApplicable/
LUMICE_IsBApplicable: the orientation-ensemble counterparts of the existing
LUMICE_IsDApplicable.LUMICE_CouldCrystalHaveFace/LUMICE_CouldFilterMatchFace: whether a
filter naming a face can ever match through it on this crystal (the latter with the filter's own
P/B/D taken into account).LUMICE_ExpandRaypathClasswith itsLUMICE_SYMMETRY_SEMANTICS_*/
LUMICE_MAX_RAYPATH_CLASS_MEMBERSconstants: the members of a raypath's label or physical
class, which Exclude this raypath uses to write a filter that removes exactly one analysis
row's members.
Changed
- The raypath-analysis list now groups rows by physical symmetry class; a filter's P/B/D
keeps its previous meaning (#429, #430, #431, #436). The analysis list used to assume every
prism and pyramid has full six-fold symmetry regardless of the configured shape or orientation
distribution — on a prism withface_distance: [1, d, 1, d, 1, d](d != 1), which really has
only a three-fold axis, P merged a near-face path with a far-face path that differ in energy by
2.4×. The list now intersects P/B/D with what the crystal's shape (LUMICE_GetCrystalSymmetry)
and its orientation ensemble (LUMICE_IsPApplicable/LUMICE_IsBApplicable) actually admit,
so a row that used to merge such paths can now split into several; the filter editor shows a
hint beside the P/B/D checkboxes when a tick would merge physically inequivalent paths on the
bound crystal (e.g. a Parry arc's lit 3-5 with its dark 4-6). A filter's own P/B/D matching
behavior is unchanged from v4.7.0 — it stays a label equivalence (P: any 60° face relabeling;
B: 1↔2 and upper/lower cone swap) whatever the crystal's shape or orientation, exactly as
before this release; only the analysis list's grouping and the editor's hints are new. See
doc/raypath-symmetry.md§1.1/§2a for the two meanings of the same P/B/D bits. sim.ray_allocation's Settings editor is a two-option combo instead of a bare checkbox
(#434). The row's label was only the key path, so nothing said whether checked meant adaptive
or proportional; it is now an explicitadaptive/proportionalcombo. The underlying field
and the.lmc/ JSON / C API wire formats are unchanged.- Channel B−R display mode's contrast is 4× stronger (#439).
gray = clamp(0.5 + 2·(B−R), 0, 1)on the displayed sRGB channels, up from0.5 + 0.5·(B−R), matching a researcher's
Photoshop channel-mixer workflow (measured slope 1.998 on their reference image; this project's
formula now fits at 1.941, was 0.484). Purely a diagnostic display transform — no ABI or struct
change, no config key change — but a savedchannel_brscreenshot or a config with
display_mode: channel_brnow renders visibly steeper:|B−R| >= 0.25clips to pure
black/white where it did not before.
Fixed
- The
adaptiveray-allocation mode no longer starves a filtered crystal whose output is small
in total but concentrated in one visible arc (#435). Example: a layer with a randomly oriented
column and a Parry-oriented column filtered to3-5, equal proportions. Adaptive (Neyman)
allocation gave the filtered entry so few rays that the Parry arc came out 2.1–2.6× noisier than
underproportional, visibly grainier than the rest of the image. Each entry's allocation now
floors at half its proportional share (q_i >= p_norm_i / 2), which brings the arc back to
1.1–1.2× theproportionalnoise; the price on that scene is about 11% more noise on the random
column's halos, which the floor takes rays from. Scenes where no entry hits the floor allocate
exactly as before. The starvation dates from whenadaptivewas introduced (v4.6.0), not from
this release. - Duplicating a crystal card that carries a filter no longer blanks the whole preview after a
finished run (#440). Any edit that structurally changes the committed scene (Duplicate on a
filtered entry is one way to trigger it) used to zero the displayed frame's intensity
immediately, on the assumption a new render would follow within its live-edit debounce window —
which does not hold once a run has already finished, so the preview stayed black until the next
Run. The old frame now stays on screen until the new one actually replaces it; the
"unsaved changes" cue is the existing top-bar Modified indicator and Revert, not a
blanked preview.
What's Changed
- docs(raypath-analysis): expand §5.1 into the post-selection three-feature plan by @LoveDaisy in #426
- docs(raypath-analysis): compute location moves per module maturity by @LoveDaisy in #427
- docs(raypath-analysis): Analyze as the second product core (§5.1.8) by @LoveDaisy in #428
- fix(core): reduce raypaths only under the symmetry the crystal shape admits by @LoveDaisy in #429
- fix(core): apply P/B raypath symmetry only where the orientation ensemble admits it by @LoveDaisy in #430
- fix(core): device filter reduction follows the crystal's shape and ensemble symmetry by @LoveDaisy in #431
- docs: finalize the Analyze workspace layout (raypath-analysis §5.1.8) by @LoveDaisy in #432
- docs: repository roles for Lumice / Lumice Integral, and the shared library's first consumer by @LoveDaisy in #433
- gui: sim.ray_allocation Settings editor becomes an adaptive/proportional combo by @LoveDaisy in #434
- core: relative floor in adaptive ray allocation stops starving a filtered arc by @LoveDaisy in #435
- core: a filter's P/B/D is a label equivalence again; the analysis list keeps physical symmetry by @LoveDaisy in #436
- docs: state which symmetry meaning each repo's computation uses by @LoveDaisy in #437
- Channel B-R display mode: raise gain from 0.5 to 2 by @LoveDaisy in #439
- GUI: a struct-hard edit no longer blanks the preview by @LoveDaisy in #440
- Publish-ready shared library foundation: liblumice_analytic, per-library export lists, packaging by @LoveDaisy in #441
- chore(release): cut 4.7.1 by @LoveDaisy in #442
Full Changelog: v4.7.0...v4.7.1
v4.7.0
Added
- The GUI is DPI-aware, with a user scale preference (#398). Until now the whole UI was drawn
at physical pixels regardless of monitor scaling — on a 150% Windows display, at 13 px instead
of the intended ~19 px (matched to the OS title bar: the same window went from 1600×980 to
2400×1470 physical pixels). A singleUiPx()/UiPxI()outlet now converts every layout
constant (~90 literals) through a monitor tier (read at startup, tracked across screens) and a
new user tier: Settings › app gets a discrete scale combo (75 / 100 / 125 / 150 / 200%),
stored asapp.ui_scale_multiplierinuser_defaults.jsonand applied immediately.
ui_scale == 1renders byte-identical to 4.6.1. Changing the scale re-centres the Settings /
Analysis / Colors windows at their design size, so a larger window cannot end up off-screen. - A search box over the raypath analysis list (#399). Filters the list by the raw raypath
text — the same text the CSV and the log print, not the on-screen arrow glyph; energy shares,
the cumulative column and the selection keep their meaning, it only hides rows. - Right-click Copy (and Copy all) on read-only text (#399), across the analysis list, the Log
panel, the filter editor's expansion preview, and a new "Copy as text" button on the Summary
page. - The analysis list remembers what you have excluded (#400). Pressing Exclude this
raypath used to make the row vanish on the next Analyze, with no way to see what had already
been taken out. An excluded raypath now stays in the list as a greyed row (was X%, the share
it last had), with Include again to undo it. A hand-written filter the greyed-row detector
cannot read as a plain raypath token is not shown this way, by design. - Cards can be reordered by dragging, and Duplicate lands next to the original (#405). The
card list previously supported only append and delete; a single new primitive
(MoveEntryWithinLayer) now backs both a drag handle on the card thumbnail (click-to-edit now
fires on release, so a drag no longer opens the editor) and Duplicate, which inserts
directly below the source card instead of at the end of the layer. Every position-based
reference — the open Edit Entry popup, the analysis panel's pick-link mode — is remapped
through the same compensation the delete path already used. - Continue: add more rays to a finished render without starting over (#406). A new
Continue button next to Run traces anotherray_numworth of rays into the
accumulation a completed run already has — the XYZ planes, ray/energy totals and both exposure
anchors (relativeandabsolute) carry over unchanged, so the picture only gets less noisy,
not different. The new rays are a fresh random stream, never a replay, on all three backends.
Continue is disabled, with a tooltip, when there is no finished render to continue, an analysis
session is active, or the document has changed in a way that needs a full re-simulation. - Globe lens: the far side of the sphere shows through, fading like fog (#407, #416, #424). A
new Back fade slider (Settings, under the Globe lens; JSONglobe_back_fade, default 0 —
today's camera-facing-hemisphere-only image) adds the far hemisphere's light on top of the near
side, weighted by exponential fog with no hard cutoff (exp(-depth/fade)); the slider runs
0–2.0. The four line families (grid, horizon, angular distance, view distance) fade the same
way and are drawn under the near-side lines; markers and text labels stay near-side only.
Every direction — light and lines alike — is clipped by its ownvisible/front, not by
the near point sharing its pixel, so an "upper" view correctly shows a faded far side even
where the near point itself is excluded.fade <= 0, or a full-spherevisible, renders
byte-identical to before this feature. - A "Channel B−R" display mode: is this spot bluer or redder? (#410, #419). A small segmented
control in the preview's top-right corner (moved there from a Settings row after users did not
find it) switches between the normal image and a grey-level map of
clamp(0.5 + 0.5·(B−R), 0, 1)on the displayed sRGB — the same subtraction a researcher used to
do by hand in Photoshop. Purely a display transform: switching it, or EV, never re-simulates,
and the export / screenshot paths match the screen exactly. It has no effect under print tone
(greyed out there), suppresses the raypath-colour composite and the background photo while
active, and a non-defaultray_colortriggers a warning. JSON keyrender.display_mode
("normal"/"channel_br"). - Pick an angular-distance ring on the preview, instead of typing a number (#421). Both ring
families (sun-centred, and lens-centre-referenced) gain a Pick button: a live ring follows
the cursor with its angle read out, a left click adds it to that family's list (turning the
family's line display on if it was off), Esc or a right-click cancels. A location with no
direction — outside the lens circle, or clipped byvisible/front— shows no preview ring
and adds nothing on click. - A screenshot export options popup (#422). Save › Screenshot... now opens a popup,
pre-filled from what is currently on screen, before the file dialog: each overlay family's line
and label, and the display mode (Normal or Channel B−R, greyed out where the uploaded texture
cannot render it honestly), can be turned off for that one export — never turned on beyond what
the panel already shows, and never written back to the document. - The Edit Entry modal's Axis page highlights the active preset and summarises the crystal
(#418). The active preset button (Custom included) now fills with the accent colour instead of
leaving the choice to be read off the thumbnail alone; a new summary line under the preview
(<preset> · zenith <dist> · <type> · <height scalars…>) updates live as the edit buffers
change. - C API:
LUMICE_API_VERSION443 → 446 (#406, #407, #410).LUMICE_ContinueRender(server, infinite, additional_ray_num)(v4.44) is a pure append, no struct change — the Continue
feature above. ABI (v4.45):LUMICE_RenderParamgains a trailingglobe_back_fade
(sizeof 6452 → 6456) — the Globe far-side parameter above;0, the zero-initialised value, is
the pre-4.7.0 image. ABI (v4.46):LUMICE_RenderParamgains a trailingdisplay_mode
after it (sizeof 6456 → 6460) —LUMICE_DISPLAY_MODE_NORMAL(0, the default) /
_CHANNEL_BR, the display mode above. Both struct growths require recompiling against the new
header; neither moves an existing field's offset or changes its meaning, and an
unmodified / zero-initialised caller reads the same image as before.
Changed
- The filter editor's last OR row can be deleted (#399). It used to grey out the row's
x, a
rule copied from the crystal card where an empty list really is illegal; a filter list may
legitimately be empty, and deleting the last row now goes through the same invariant that
already refills a blank one. - Tab in the crystal editor's shape tables walks column-major (#399). Tab now walks the Value
column down every row before moving to the Spread column, matching how face distances are
actually edited; Shift+Tab reverses. - Settings and Summary windows keep the height you drag them to (#399). Both become
"semi-variable" under a newly documented sizing policy (doc/gui-visual-language.md): width
pinned per window, height free until the user drags it and then remembered for the session;
Summary still auto-fits its content until that first drag. - The Edit Entry popup has two shapes: Compact and Expanded (#401). A side-by-side tab layout
and a three-column prototype are gone; Compact (unchanged — the stacked layout almost
everyone already used) keeps the halo preview visible while editing, and the new Expanded
shows the crystal, axis and filter sections all at once, two columns, no tabs — chosen by the
owner from a measured comparison of all four candidate shapes. See⚠️ Breaking Changes for what
happens to a document that had chosen the removed shape. - The Edit Entry popup's height can be dragged, and its Expanded headers line up (#403). It
was fixed-height (AlwaysAutoResize); it now follows the window-sizing policy above — width
pinned, height free until dragged, then kept for the session — and a too-short work area no
longer pushes the OK button out of reach. Expanded'sCrystalandFiltersection headers,
previously aligned by coincidence, are now derived from one height and asserted to agree. All
three section headings switch to the standardSeparatorTextstyle used elsewhere in the GUI. - The top bar's buttons are grouped and sized consistently, and Continue gets its own colour
(#417, #420). Revert, Run / Stop / Continue, and New / Open / Save each share one height and
one width per group, with a proper separator between groups instead of a"/"character;
Continue is now violet, distinct from every other action colour. The always-reserved Revert
slot — previously a hole in the middle of the bar when nothing was unsaved — has moved to the
trailing end, next to the right-panel collapse chevron. - CPU multi-worker throughput no longer falls off a cliff as workers are added, and Linux's
automatic worker count is uncapped (#412). The queue handoff between simulation workers and
the consumer was tied to the 128-ray physics batch; under load, the resulting synchronization
rate created a runaway feedback loop that made adding workers past 10 counter-productive on
Linux, which is why the automatic count (num_workers = 0) was capped there. Workers now
aggregate batches locally before handing them off, decoupling the two; the automatic count on
Linux is now the full physical co...
v4.6.1
Added
- Raypath analysis from the command line:
Lumice analyze(#369, #394). The analysis the
4.6.0 GUI window runs is now a CLI subcommand that writes the same CSV the window's Export CSV
produces (one CSV builder, shared by both — a unit test pins the two byte-identical). The config
is the scene and the flags are the request, so no config field is added:--roi sky|frame|cone,
with--center alt,az+--radius degfor a cone and--render-idfor a frame;--symmetry
(read-time, defaultPBD);--rays/--seed/--workers;--csv <path>for a file, else
stdout (progress goes to stderr, so2>/dev/nullleaves the CSV alone). An infinite budget
rewrites the file periodically and a Ctrl-C ends the run with one complete final write and exit
0. The pass runs on the CPU whatever--backendsays, as in the GUI. New in the same cycle:
--chain-capacity <N>sizes the run's chain record, which until now was one compiled-in
constant (16384, calibrated on the GUI's multi-scatter scenes) that a single-crystal
--symmetry nonescene overflowed silently into theotherrow — a hexagonal prism alone has
29 212 distinct chains through 8 faces, so its 8-face set (0.84 % of the energy on the reference
scene) was never listed.0keeps the default; the ceiling is 2²⁰, and the cost is memory,
about 220 bytes per chain per worker. Chains with an adjacent repeated face (grazing-edge
numerical noise) are documented as a known shape rather than filtered out. Guide:
doc/user-manual/06-raypath-analysis.mdand the CLI quickstart. Lumice render --format npy: the accumulator as float32, with a metadata sidecar (#379).
Each renderer is written as a.npy(H×W×3 float32 XYZ, C order — a hand-written v1.0 header,
no new dependency) plus a same-name.jsoncarrying what a consumer needs to interpret it:
normalization,emitted_energy,total_pixels,intensity_factor,axis_solid_angle,
anchor_l99_sky,ev_mode,sim_ray_num,seed,renderer_id,width/height,
LUMICE_API_VERSIONand more. The values are the same unexposed snapshot the 8-bit image is
baked from — no background, no gamma, no clamp.--raw-normalization raw|normalized(or the new
top-level config keyraw_export.normalization; the flag wins) chooses between the physical
landed energy (default) and the absolute-mode exposure scale at EV 0;normalizedwith a zero
emitted_energyskips that renderer with a warning instead of mislabelling the file.
doc/configuration.mdstates the units, the conversions to radiance and to the 8-bit image, and
the fact thatnormalizeduses the absolute-mode scale regardless of the renderer'sev_mode.render --seed <N>(#370): the same fixed-seed optionanalyzehas, for a byte-identical
re-run of a render.benchmarkrejects it with its own message — a fixed seed collapses the
server to one worker, which would defeat the throughput measurement.- A Summary window: the whole configuration on one page, for a screenshot (#396). A new
Summary button in the Top Bar, beside Colors and Analysis, opens a read-only,
non-modal window laying the current document out for sharing — the version that drew it, the sun
and spectrum, the simulation budget, the lens / view / exposure / mode rows, the Settings-only
fields, and per layer two tables, Crystals (enabled, weight, name and type, the three axis
distributions each led by the preset it classifies as, the filter as the card summarises it) and
Shape (the editor's own columns for prism and pyramid), cross-referenced by row number.
Distributions print in one notation,letter centre(spread)with the panel's own two numbers.
1200 px wide, two columns, so a typical document fits a 1280×900 screen without scrolling. It
prints what the panels show, not what the simulator is asked for internally: alinearlens
readslinear, and nothing about your machine (overlays, background photo, panel layout) is on
the page. No export button of its own — take the picture with the operating system's screenshot
tool. Guide:doc/user-manual/07-config-summary.md. - Look At gains a Horizon series (#393). Four level bearings relative to the sun —
Toward sun / Sun +90° / Away from sun / Sun −90° — at elevation 0, for turning
the camera to a fixed bearing without dragging the Azimuth slider. The existing Sun-side
horizon entry is folded in as Toward sun. Presets still set only azimuth and elevation;
roll and fov are untouched. - The product version is shown everywhere, from one source (#391). The GUI window is titled
Lumice <version>;Lumice --versionprints it and exits 0; the first log line of a GUI or CLI
run names it; a saved.lmccarries it asapp_versionbesideschema_version(ignored on
load, never a difference row in Settings). A build that is not a tagged release carries a-dev
suffix, so a locally built binary never passes for a release. - C API:
LUMICE_API_VERSION439 → 443 (#369, #375, #391, #394). Three pure appends, with no
struct change:LUMICE_SceneGetRenderer(scene, index, out)(v4.40) reads a renderer back with
the engine's defaults applied — the inverse ofSceneAddRenderer, addressed by array position,
not.id;LUMICE_GetEngineIsaLevel()(v4.41) answers the ISA tier of the engine actually
loaded (baseline/x86-64-v3/x86-64-v4/native), which is also what the
[BENCHMARK]JSON'sisakey now reports instead of the shell's compile-time macro;
LUMICE_GetVersionString()(v4.42) is the product version above. ABI (v4.43):
LUMICE_RaypathAnalysisRequestgains a trailingchain_capacity(sizeof 88 → 96, recompile
against the new header);0— what a zero-initialised request, and the GUI, send — is
byte-for-byte the old request,1 … LUMICE_MAX_RAYPATH_CHAIN_CAPACITY(2²⁰) sizes the record,
anything else isLUMICE_ERR_INVALID_VALUE. Two documented semantics moved without a version
bump:LUMICE_GetBackendFallbackFlagnow also reports a GPU session that fell back because the
run's config had no renderer, more renderers than the backend serves, or a compatibility miss
(#372); andLUMICE_ServerConfig.num_workers = 0now means the full logical core count on
Windows (see the performance entry below) while Linux / macOS keep the physical count capped at
10 (#382).
Changed
- Engine and GUI log lines go to stderr; stdout carries only product output (#369, #370). The
CLI's console sink used to write its log lines to stdout, mixed in withSaved:/Stats:/
[BENCHMARK]/ColorClassSignal:and now the analysis CSV; those product lines stay on stdout
and everything logged moves to stderr, soLumice … 2>/dev/nullyields clean output and a
script that parsed stdout for one of the product lines no longer has to skip log lines. The GUI's
own console sink moves the same way. A script that captured log lines from stdout needs
2>&1. - A saved
.lmcreopens as the picture you saved, and stays re-exposable (#381, #384). The
texture section now stores the unexposed linear XYZ energy the live preview is built from — as
float16 with one global scale, zlib-compressed (format v6) — instead of the exposed, clipped
8-bit sRGB preview of v4; on reopen it goes up through the live path's own shader branch, so
EV,ev_mode, sky / paper colour and print mode all work on a reopened document exactly as
after a Run, and the reopened frame is byte-identical to the screenshot taken before saving
(the live preview is quantised through the same codec before upload, which is what makes that
hold by construction; GPU texture memory halves as a side effect). Files are larger than v4's
baked PNG — about 1.4 / 8.5 / 9.9 MB on the three beta scenes at the default resolution,
versus 0.2 / 5.0 / 5.3 MB before — because they now hold the dynamic range the PNG had clipped
away. v5 (the same content as float32, an intermediate format never released) and every v≤4 file
open unchanged; a file saved by this version cannot be opened by 4.6.0 or earlier. - Release packages: one executable per entry point, plus two internal engine libraries (#375).
4.6.0 shipped each Linux / Windows executable twice behind a launcher (Lumice.baseline/
Lumice.x86-64-v4,.baseline.exe/.x86-64-v3.exe). Those files are gone. Windows x64
now hasLumice.exe/LumiceGUI.exepluslumice-engine.baseline.dlland
lumice-engine.x86-64-v3.dll; each shell probes CPUID at startup and delay-loads the matching
DLL from its own directory by absolute path (never PATH or cwd),--isa=baseline|x86-64-v3
overrides. Linux x64 hasLumice/LumiceGUIpluslib/liblumice.soand
lib/glibc-hwcaps/x86-64-v4/liblumice.so; glibc's own loader (≥ 2.33) picks the AVX-512 copy
with no application code. The engine library is an internal component — its ABI changes freely
between versions,lumice.hdoes not ship with it. Measured on the reference box: the DLL
boundary itself costs nothing (100 % / 98 % single / multi-worker against a static build of the
same tier); the tier gain is 2.31× single- and 1.56× 16-worker on Windows, 2.03× on Linux. Two
accepted costs: a machine without AVX2 runs the
baseline engine 10–14 % slower than 4.6.0's static baseline executable (the export table rules
out link-time optimisation for that DLL), and the Windows zip unpacks to 21 MB. macOS and Linux
ARM64 packages are unchanged. - A config with several
renderentries now runs on the GPU (#372). Metal and CUDA used to
serve one renderer only; a multi-lens config on a GPU-preferred window or--backendfell back
to the legacy CPU path, silently. Both backends now accumulate up to four renderers per session
in one pass — a two-renderer run reaches 88–95 % of a single-renderer run's through...
v4.6.0
Added
- Raypath Analysis: a window that says which raypaths make the light, and how much each
contributes (#347, #349, #359, #360, #361, #366). A new Analysis button in the Top Bar, next
to Colors, opens an independent, non-modal window. Press Analyze and a dedicated,
non-rendering pass groups every outgoing ray by its complete raypath — root to exit, across
scattering layers,3-5for one crystal,C1(3-5)where a layer holds several, layers joined
root-first by an arrow — and lists each group's share of the total energy, sorted descending,
with a running Cumulative % and a +/- noise estimate per row. Three regions: Whole
sky, In frame (rays that land inside the picture as currently framed) and Point — a
cone around a marker drawn on the preview itself, placed by dragging it or by Pick on
preview, that follows the view every frame; its Radius slider re-sums the result at display
time, so one Analyze click serves a whole sweep of radii. P / B / D regroup the list on hand
without re-running, the same symmetries the filter editor uses. Select a row and press Exclude
this raypath to turn it into a filter on the crystal, then Run to see the picture without it;
Export CSV writes the list as displayed. The run has its own Infinite rays / Rays(M)
budget, ends only on that budget or on Stop (which keeps what accumulated), and holds a fixed
number of distinct raypaths — a greyed other row at the bottom carries whatever did not fit,
with a reported error bound. Analyze needs the document to have shown a picture once but not to
match it: a list describing a document you have since edited, Run or Reverted is marked stale in
the window rather than cleared. The pass always runs on the CPU, on every core: a Metal- or
CUDA-preferred window keeps a standing CPU worker pool that costs nothing while idle. It follows
the Adaptive ray allocation setting below, so a rare raypath on a low-proportion crystal gets
a far less noisy row (10× on the reference three-crystal scene); for that reason the table shows
no ray count. Guide:doc/user-manual/06-raypath-analysis.md. - The C API gains the analysis run (#347, #349, #360;
LUMICE_API_VERSION428 → 437).
LUMICE_StartRaypathAnalysis(server, scene, request)starts a pass on a scene handle directly
(no priorLUMICE_CommitScene), withLUMICE_RaypathAnalysisRequestnaming the region
(LUMICE_RAYPATH_ROI_FULL_SKY/_IN_FRAME/_CONE) and the run's owninfinite/ray_num
budget;LUMICE_FrameGetRaypathAnalysisInfo/LUMICE_FrameGetRaypathAnalysisread the result
back off aLUMICE_ResultFrameunder a symmetry bit set (LUMICE_RAYPATH_SYMMETRY_P|B|D), as
LUMICE_RaypathHistogramEntryrows (chain, display string, energy, count, per-ring energy for
a cone,error_bound) plus what the bounded record could not keep as rows (other_energy,
other_count,truncated_chain_count,max_row_error). Alongside:LUMICE_UnprojectPixel/
LUMICE_ProjectDirection(pixel ↔ direction under aLUMICE_AnnotationView) and
LUMICE_GetActiveBackend. ABI:LUMICE_SimLifecycleResultgains a trailingsession_kind
(LUMICE_SESSION_RENDER/_ANALYSIS; sizeof 16 → 24, recompile) — the server's own record of
what kind of run the current session is, so a client predicting the commit's consumer-reuse
decision no longer keeps a shadow copy.LUMICE_ServerConfig.num_workersis no longer ignored on
the GPU route: it now sizes the CPU analysis pool (0 = physical cores, capped). A non-zero
sim_seedcollapses the pool to one worker, so a fixed-seed analysis is bit-identical across
sessions and acrosspreferred_backend. - Adaptive ray allocation across a layer's crystals (#348, #364, #366). A new
scene.ray_allocationconfig field —"proportional"(today's behaviour) or"adaptive"—
decouples how many rays a crystal entry is dealt from how much energy it carries. Under
adaptivethe engine deals a layer's rays by a Neyman allocation measured online from the run's
own batches (q_i ∝ p_i·√E[e²]_i, recomputed every batch on every backend, CPU, Metal and CUDA
alike) and weights each ray byp_i/q_i, so the expected picture andemitted_energyare
unchanged while the per-pixel noise is minimised. The case it was built for: three prisms each
behind a single-raypathfilter_inat proportions 100 / 100 / 0.2, where the rare path's 120°
halo skirt used to be built from ~1/2600 the hits of the 98° halo and read as pure grain — its
relative pixel noise drops ≈10× at the same ray count, the 144° ring is untouched, total landed
energy within +0.15 %. Throughput cost on a single-crystal scene: −0.2 % Metal, −1.7 % CUDA.
In the GUI it is a document field that new documents default toadaptive; the switch
lives in Settings (not on the Simulation panel — after trying many configurations the
algorithm behaves stably enough not to need a panel toggle), and an.lmcsaved before the
field existed, or a core JSON imported without it, lands on the GUI default. The CLI default
staysproportional, so every existing config renders as before. C API:
LUMICE_SceneSetRayAllocation(scene, LUMICE_RAY_ALLOCATION_*)(LUMICE_API_VERSION437 → 438, a
pure append — no struct or ABI change). - A fifth overlay family: circles of constant angular distance from the lens centre (#357,
#362, #363, #365). The axis-referenced twin of the sun-centred circles: a framing and calibration
aid that stays put on the canvas while the view is turned — the fov/2 circle must be inscribed in
the short edge, so it also checks a lens setting at a glance. Config,.lmcand exported JSON
carry it asgrid.view_dist/grid.view_dist_line(default on) /grid.view_dist_label
(default off), shaped exactly like theangular_distthree; absent keys leave the family off,
so a document written before this release renders unchanged. The CLI composites it between the
sun circles and the horizon outline. In the GUI both ring families share one collapsed
Angular Distance from... section under the Overlay table, one row each — Sun and Lens
Center — with colour,Line,Labeland an angle-list fold apiece; new documents start the
Lens Center list at{22, 46, 90}(an empty default made "works" and "broken" look the same),
and the fold's presets are split by family (Sun{9, 22, 28, 46}, Lens Center{22, 46, 90}). LUMICE_RenderParamgainsview_dist[]/view_dist_count/view_dist_line/
view_dist_label;LUMICE_AnnotationRequestgainsview_dist_deg/view_dist_count(#357;
LUMICE_API_VERSION438 → 439, both appended at the end of their struct —LUMICE_RenderParam
sizeof 4904 → 6452,LUMICE_AnnotationRequest128 → 144, recompile against the new header; both
structs now carry an exact-sizestatic_assert). Labels come back with kind
LUMICE_ANNOTATION_VIEW_DIST(4). Same zero-initialisation caveat as the other three line
switches: the JSON default ofview_dist_lineis on, so a zeroed struct asks for no lines even
with a full angle list. There is deliberately no reference-direction field — the centre is the
view's own forward, independent oflens_shiftby construction.
Changed
- Release packages use the CPU and GPU they land on, without moving the support floor (#346).
One download, the choice made on your machine. Linux x64 ships every executable twice —
Lumice.baseline(x86-64-v1, as before) andLumice.x86-64-v4(AVX-512) — behind a CPUID
launcher installed under the unchanged namesLumice/LumiceGUI, which picks the variant the
CPU supports (--isa=<level>overrides); measured 1.99× on a Zen 5 desktop. Windows x64 does
the same with.baseline.exe(MSVC) and.x86-64-v3.exe(clang-cl, AVX2+FMA — MSVC gains nothing
from/arch, clang-cl gains 2.23×, and v4 adds nothing on top), covering every Haswell+/Zen1+
desktop; on a machine without AVX the launcher falls back to baseline. The CUDA fatbin now
carries nativesm_120(RTX 50 series), so first launch no longer JIT-compiles from PTX
(1.87 s → 0.22 s); the PTX floor stayscompute_61. macOS and Linux ARM64 packages are unchanged. - The GUI's ray budget reaches 10¹¹ rays (#354). The
Rays(M)slider — on the Simulation panel
and in the analysis window — now spans0.1 … 100 000million on a logarithmic track, so the low
end still resolves to 0.1 M while the top reaches 100 000 M; the input box takes any value
directly. Display format changes from one decimal to six significant digits..lmcand exported
JSON fields are unchanged. - A
lenswith neitherfovnorfnow runs with a per-type default instead of exiting (#355).
The docs always promised a default; the CLI exited with code 4. It now uses 90° (30° forglobe),
logs a warning naming the default, and renders. Nothing to do for a config that states either
field. While there,doc/configuration.mdnow says what the code always did:lens.fovis the
angle across the short edge of the frame, not the diagonal. - The Overlay panel's
Angular Dist.row moves into a collapsed section, and Reference Points
gets an All row (#362, #363, #365). The main Overlay table keeps three rows (horizon, elevation,
longitude); the sun circles now sit as the Sun row of the new Angular Distance from...
section beside the Lens Center row, collapsed on a new document (its open state is saved with the
document). In Reference Points the header-barAll/None/⋯buttons are gone; the
marker table's first row is an All family row in the same six columns —Line/Labelare
tri-states derived from the six entries (a square when mixed; click turns all on unless all are
already on),Alphabinds the shared marker alpha, the f...
v4.5.1
Added
- Print mode: ink laid on paper, for a halo on a light background (#337, #343). A white or
pale background used to erase the halo entirely. Light is added to the background and clamps
at white, so with a white sky every pixel was at the ceiling before any ray energy arrived —
while the grid and overlay lines, which are blended rather than added, stayed perfectly visible,
so the result read as a failed simulation rather than a colour choice. No foreground colour can
fix that under an additive operator, so the fix is a second operator.render.tonechooses it:
screen(the default, and byte-for-byte what every existing config rendered) orprint, under
which accumulated radiance becomes a neutral ink density laid onrender.paper(a new field,
default white):D = 11·log10(1+e),out = paper·10^(-D), so a white page can go dark. Print is
greyscale by construction — hue is given up on purpose, since "ink absorbs the complementary
colour" would make white-light features such as parhelic circles vanish on white paper — so a
print reads like a drawing and its arcs are told apart by position and shape. In the GUI the
Display panel'sModeswitch selects it and the ground-colour row becomesPaper Color. Because
ink owns the colour channel underprint, four things that colour pixels are mutually exclusive
with it and say so instead of silently doing nothing — the background photo overlay, raypath
colouring,ray_color, and per-family annotation colours: the CLI logs one warning per field it
will not read, and the GUI greys the control with the reason. Overlay text labels are ink too
(#343 closed the one path — the GUI's ImGui-drawn labels — the first cut missed, so under Print
every label was still drawn in its family's hue beside a black line). A contrast-headroom notice
guards the degenerate end of each operator through one shared predicate: ascreenbackground
within a few 8-bit levels of white, or aprintpaper within a few of black, both render a blank
picture from a configuration that looks reasonable — the CLI warns at commit, the GUI shows an
inline notice with a one-click repair (Switch to Print/Reset paper to white)..lmcand
exported JSON round-trip both fields.
ABI:LUMICE_RenderParamgains trailingtone/paper[3](LUMICE_API_VERSION426 → 427,
appended so every existing field keeps its offset;sizeofgrows, recompile against the new
header). A zero-initializedtoneisLUMICE_TONE_SCREEN, the operator this API has always
used — butpaper's JSON default is white while a zeroed struct names black paper, which under
printis an all-black page. Set it, or go through JSON. - An eyedropper for Sky Color that samples the background photo (#334). Next to the Sky Color
swatch, a pipette enters a picking mode in which the preview shows the photo alone — render,
auxiliary lines and lens border are suppressed, so what you see is what you get — and a colour
chip follows the cursor until you click. Sampling the sky of the photo is the point: the photo
blend isbackground·(1−α) + render·α, so the closerbackgroundis to the photo's own sky
colour, the less the photo is washed out and the more the halo comes through as pure increment.
The button is greyed, with the reason, while the photo is hidden or none is loaded. - A config can now ask for a grid family's numbers without its lines. (#323) The parallels, the
meridians and the sun's angular-distance circles each gain a line switch of their own —
grid.elevation_line,grid.longitude_line,grid.angular_dist_line, all defaulting to true, so
every existing config renders exactly as before. Previously only the horizon could be asked for
text without a line; for the other three, "do not draw me" could only be said by removing their
angles, which removed their labels too. The GUI's exported config follows suit: turning grid or
circle lines off while leaving their labels on used to drop the numbers from the CLI
render with no warning, and now exports exactly what the preview shows. LUMICE_RenderParamgainselevation_line/longitude_line/angular_dist_line(#323)
(LUMICE_API_VERSION425 → 426, appended at the end of the struct — recompile against the new
header). Note the defaults run the other way from every other annotation flag: the JSON default is
on, so a zero-initialized struct asks for no lines even when it carries a full angle list. Set
the three fields, or go through JSON.LUMICE_ConvertMillerIndexToWedgeAngle— one place to ask what Miller indices mean. (#336)
New C API function returning the wedge angle and a verdict on the indices themselves: valid, "no
cone this side", "not enough indices yet" (so a UI can call it on every keystroke without owning a
rule for when a row is finished), or invalid, with the offending slot named where one slot is at
fault. NoLUMICE_API_VERSIONchange — a new function, no struct or ABI change.- The wedge-angle dropdown takes custom Miller indices. (#336) Below the four built-in presets
the Crystal editor's Upper A / Lower A dropdowns now carry a row of index boxes in the same
{h,k,i,l}notation the preset labels use, so a face the table does not offer —{3,0,-3,1}, say
— can be asked for by name instead of converted to degrees by hand. The angle updates as you type
and is only written when you press Apply, so a triple being typed through never lands
half-finished.iis shown, not typed: it is-(h+k)by definition, and deriving it means the
four numbers can never contradict each other. Indices that name no buildable face are refused with
the reason spelled out and Apply greyed — a non-zerok(a second-order pyramidal face this
crystal model cannot express), a negative index, an h:l ratio outside the buildable range, andh = 0, which says "no pyramidal cap on this side" and belongs in the pyramid height rather than in
an angle. Nothing is stored: the dropdown writes the angle and no Miller indices enter the
document. - The Settings panel keeps the wedge angles you use. (#336) A new region under Settings >
Presets saves crystal faces by their Miller indices, and everything saved there is offered in the
Crystal editor's Upper A / Lower A dropdowns from then on, across restarts. The add row is the
same{h,k,i,l}control the dropdown carries, refusing the same triples for the same stated
reasons. Entries are stored as indices rather than as degrees, so a saved face keeps naming the
same face if the ice constants are ever corrected; a preset has no name of its own for the same
reason — the indices are the name. The four built-in presets are unaffected and cannot be deleted:
your list is added to them, and a triple they already cover is not saved twice. Nothing is written
until you press Save, and closing the panel discards the change. A file edited by hand can hold a
face that names no buildable cone; the panel shows that row with a warning and a live delete
button rather than hiding it, and loading one says which entries it dropped and why.
Changed
- The CLI no longer rounds every render up to a whole second (#324). The output loop used to
wait out its 1 s poll interval before checking whether the simulation had finished, so a
100-ray render and a 6,000,000-ray one both took 1.05 s and every render's wall time was a
whole-second staircase. It now checks first and waits only if there is something to wait for;
a 20k-ray render measures 1.02 s → 0.04 s, and the results file and stats are written no more
often than before. (The test suite does not get proportionally faster — under a parallel run the
waits already overlapped — so this is a latency change for a person at the terminal, not a CI
one.) - The Display panel's Rendering rows are regrouped and renamed (#338). The rows now read
Resolution‖EV Anchor/EV/ its read-out ‖Mode/ ground colour / warning, so the EV
read-out sits under the value it reports instead of two rows away. The ground-colour row is one
control with two faces —Sky Color(with the eyedropper) under Screen,Paper Colorunder
Print — because the renderer reads only one of the two fields in each mode, so one of two
permanently shown controls was always dead; both fields keep their own values,.lmckeys,
Settings rows and Revert baseline, and switching modes back and forth changes neither. Three
labels:Tone→Mode, theev_modeselector'sMode→EV Anchor(the same word as
anchor_l99_sky),Paper→Paper Color. - Importing a core/CLI config into the GUI now warns about the keys the GUI deliberately does
not support (#328). A non-zerosun.azimuth,render[0].lens_shiftorscene.geom_clockin
an imported JSON used to be dropped without a word — a hand-writtenazimuth: 30rotated the
whole picture by 30° in the CLI and by nothing in the GUI, with no hint why. All three now
raise the same import-warning popup the reader already uses for a complex filter it cannot hold.
This is a boundary, not a gap: the GUI fixes the sun's azimuth at 0 and the CLI keeps the
freedom; the change is that the boundary now speaks. - The "layer produces no rays" notice reads the weights that actually reach the engine (#320).
It used to fire only when every crystal in a layer was excluded; a layer whose crystals were all
included withWeight0 is the same engine configuration byte for byte and was not reported.
The notice now covers both, and names both controls. upper_indices/lower_indicesare now judged rather than partly ignored. (#336) These
arrays used to be read only when they held exactly three entries, and only entries 0 and 2 were
looked at, so three kinds of mistake passed silently: a four-index array —[1,0,-1,1], the
notation us...
v4.5.0
Added
- Lens border ring for fisheye projections (#283). Overlay gains a
Lens Borderrow (colour +
toggle, off by default) that draws the projection's own image-circle boundary — equal-area,
equidistant, orthographic and all four dual-fisheye variants — so a halo that does not fill the
frame no longer looks indistinguishable from the black surround outside the lens's valid domain.
linear, single-lensfisheye_stereographic,rectangularandglobeare unaffected: the first
three already fill the frame at every reachable field of view, andglobeis excluded as a
product choice despite having a bounded image circle of its own. - GUI background colour reaches the picture (#286). The preview, the three PNG exports
(screenshot / dual-fisheye / equirectangular) and the frame baked into a saved.lmcnow all
paint the configured background colour behind the halo — previously only the CLI did, so the GUI
colour picker moved and nothing on screen changed. The colour is composited additively in linear
RGB before the sRGB transfer curve, which makes a pixel carrying no halo energy render as exactly
the sRGB triple the picker showed; it is painted only where the lens actually images sky, so the
black surround outside a fisheye's image circle stays black. - Raypath-colour (composite) display honours the background colour (#286). With raypath
colouring on, the picture now carries the same background as with it off — previously the
composite was baked server-side with a black surround, so toggling colouring changed the
background out from under the user. - Two new C API entry points for the background colour (#286, ABI additions, non-breaking):
LUMICE_SetCompositeBackground(server, background_linear), a display-time push shaped exactly
likeLUMICE_SetCompositeExposure(no epoch bump, no re-simulation; all-zero is an algebraic
no-op, so a caller that never calls it sees byte-identical composites), and
LUMICE_XyzToSrgbUint8WithBackground(...), the existingLUMICE_XyzToSrgbUint8with an additive
linear-RGB background composited before the final clamp and gamma. ev_mode(#287, #299) — a first-class choice between two exposure anchors, reaching core
RenderConfig, the C API,.lmcdocuments and CLI JSON."absolute"anchors to the energy the
light source EMITTED, so two renders at the same EV are directly comparable."relative"(the
DEFAULT) anchors to the P99 radiance of a fixed, full-sky reference buffer built for the scene —
the same anchor the GUI preview has always used — so it does not depend on which view, lens,
visibleclip or output resolution a particular render asks for;ray_numstill co-determines
it, since the anchor is a statistic over the accumulated simulation. A config with noev_mode
key, or a misspelled value, rendersrelative. On the composite (raypath-colour) path the same
field selects between the participating-pixel self-anchor and the mono path's absolute scale,
sharing one scalar rather than re-deriving it.
ABI:LUMICE_RenderParamgains a trailingint ev_mode(LUMICE_EV_MODE_RELATIVE= 0,
LUMICE_EV_MODE_ABSOLUTE= 1);LUMICE_API_VERSIONmoves 415 -> 416 (the same step also removes
opacity, below). See Breaking Changes below for what selecting each mode does to an existing
config's brightness.
The GUI's own auto-EV anchor calculation moves into core alongside this, as two new pure C API
functions with no ABI-breaking half:LUMICE_ComputeP99Y(the P99 statistic itself, with an
optional coarse-grid downsample) andLUMICE_ComputeEvAuto(the stops-and-clamp arithmetic on
top of it) — so the GUI and any other consumer share one implementation of the anchor instead of
each carrying their own copy.LUMICE_RawXyzResult.emitted_energy(#287, C API, ABI addition): the total spectral energy
the light source emitted into a snapshot — the quantityabsolutemode normalizes by. A raw
total, unlike the neighbouring per-pixelsnapshot_intensity, and a different measurement, not a
rescaling of it, since one counts what went in and the other what landed. A consumer reproduces
the renderer's own absolute scale asintensity_factor * kNormScale * total_pixels / emitted_energy. Occupies pre-existing alignment padding, sosizeof(LUMICE_RawXyzResult)is
unchanged.- Core-side annotation layer: the CLI renderer can now draw every auxiliary line and marker the
GUI preview draws, from one shared implementation instead of two (#292, #305). Arender[]entry
can ask for parallels (grid.elevation), meridians (grid.longitude, new), circles of angular
distance from the sun (grid.angular_dist, renamed fromgrid.central— see Breaking Changes), a
front-hemisphere clip (front, new), text labels for each of those families
(horizon_label/grid_label/angular_dist_label, new), and — generalizing the existing
zenith/nadir ring into a named list — up to six sky-direction markers (grid.markers: zenith,
nadir, sun, subsun, anthelion, antisolar), each independently switched, coloured and drawn, with a
matching[Look At ▾]view preset in the GUI's View group. All of this previously existed in the
GUI only, or not at all in this generalized form; an exported config now reproduces it. New C API:
LUMICE_ComputeAnnotationOverlay/LUMICE_ReleaseAnnotationOverlay(pure, side-effect-free
geometry and label anchors for one view),LUMICE_ResolveAnnotationMarkerDirection(a named
marker as a world direction, for pointing the camera at it rather than finding its canvas
position), andLUMICE_ResolveSunHorizonDirection(the sun's azimuth carried down to the
horizon, for the same View-preset use).
ABI: a series ofLUMICE_RenderParamfield additions across this arc —elevation_grid
becoming rendered,longitude_grid,zenith_nadir,front, the three*_labelswitches, and
markers/markers_count/markers_opacity/markers_radius_px— every one appended so
existing fields keep their offsets, and every one opt-in: a zero-initialized struct draws none of
them, matching what a config with nogridobject already rendered.zenith_nadirand the
.lmcoverlay_zenith_nadir_*keys keep working unchanged; where bothzenith_nadirand a
non-emptymarkerslist are present,markerswins.LUMICE_API_VERSIONmoves 416 -> 421 for
therender[]fields above (#292); the marker generalization (#305) later resumes this same
counter at 423 (after the twoLUMICE_RawXyzResultadditions below take it there), moving it
423 -> 424 for the underlying named-direction table (LUMICE_AnnotationMarkerPointand the
LUMICE_ANNOTATION_MARKER_*ids, all appended at each struct's physical end) and 424 -> 425 for
markers[]itself — 425 in total, matchingHEAD. LUMICE_RawXyzResult.anchor_l99_skyand.axis_solid_angle(#299, C API, ABI additions):
the two quantities a consumer needs to reproduce therelative-mode scale itself —
anchor_l99_skyis the P99 radiance of the fixed full-sky reference buffer (identical on every
row of one call), andaxis_solid_angleis the solid angle this renderer's own on-axis pixel
subtends:scale = intensity_factor * TargetWhiteToLinear(135) / (axis_solid_angle * anchor_l99_sky).anchor_l99_skygrows the struct (64 -> 72 bytes) — a caller that was not
recompiled hands the API a shorter buffer;axis_solid_anglethen lands in the padding that
growth leaves behind, so it does not grow the struct a second time.LUMICE_API_VERSIONmoves
421 -> 422 for the first field, 422 -> 423 for the second (the annotation-layer entry above
continues the count from here to 425).- CLI
--workers N, and a matching GUI personal default (#314). Both front ends previously left
worker count at its zero-initialized automatic default;--workersnow lets a user pick it
explicitly (rejected outright, not silently clamped, if it is not a positive integer), and the
GUI Settings panel can save a preferred value the same way it saves other personal defaults. See
Breaking Changes below for the new automatic default this doesn't override. - GUI "Use GPU" can be saved as a personal default (#311). Previously the checkbox reset to
legacy CPU on every launch even after an explicit choice; it can now be saved via Settings like
other personal defaults. The factory default is unchanged (CPU), and a saved GPU preference on a
machine with no GPU backend available is silently ignored by the existing runtime fallback.
Changed
- Cylinder crystal height can go as low as
1e-4, down from0.01(#301) — the GUI slider's
floor was far above core's actualh > 1e-5acceptance, so this range was reachable by editing a
config by hand but not from the slider. Several sliders' display formats are also corrected
(axisfields,sun.diameter) where the digit shown did not change finely enough to track the
slider's own step size, making the control look frozen mid-drag. - Legacy CPU backend throughput improved via reduced allocation churn (#303). A per-batch
working buffer was reallocated on every batch; it is now reused for the worker's lifetime,
measured to cut large allocations from roughly 17,000 to about a dozen in one run. Measured
throughput gain ranges from +2.4% to +159.5% across two machines, worker counts and scenarios
(largest at 16 workers on a 16-core machine); peak memory dropped 57-61% in the same runs. Output
is bit-identical — noGetUniform()call sites were added or removed. - The renderer-count-exceeded error names the actual count and the limit (#294). Loading a
fifthrender[]entry used to fail with a bareerror code 3; the CLI now reportsconfig has 5 "render" entries, exceeding the limit of 4. The limit itself (4) is unchanged.
⚠️ Breaking Changes
- **`LUMICE_RenderParam:...
v4.4.3
What's Changed
- test: retire five dead observation channels and one lying marker by @LoveDaisy in #264
- fix(gui,core): 以 core 为参照收敛「静默替用户挑一个语义」的加载分支 by @LoveDaisy in #265
- docs: make a completeness claim carry the same burden of proof as adding code by @LoveDaisy in #266
- docs: 远程验证文档按「角色 / 主机绑定」分层,并写入新参照机 recipe by @LoveDaisy in #267
- fix(test): 把 setenv/unsetenv 的 #ifdef 收敛成一个有名字的 helper by @LoveDaisy in #268
- fix(build): 让 CMP0091 真正生效,Windows 发布产物链接静态 CRT by @LoveDaisy in #269
- docs(gui): 落盘视觉语言与布局架构两份 GUI 蓝图 by @LoveDaisy in #270
- feat(gui): 落地 GUI 视觉语言——单一 owner、比例字体、量化节奏、调色板与语义色 by @LoveDaisy in #271
- fix(gui): 底图纹理改用 mipmap + trilinear,修缩小显示时的欠采样混叠 by @LoveDaisy in #274
- feat(gui): 底图可平移缩放,让裁剪过的照片能与仿真结果对齐 by @LoveDaisy in #275
- feat(gui): 晶体卡新增「参与仿真」toggle,替代把权重拖到 0 by @LoveDaisy in #276
- feat(gui): Overlay 辅助线组改为 6 列表格形态 by @LoveDaisy in #277
- fix(core+gui): 全球面快路径补 roll 旋转对称条件,修滑条端点浮点漂移导致的采样路静默切换 by @LoveDaisy in #278
- refactor(gui): 颜色收口——色槽补齐 58/58 + 调用点裸字面量逐处 disposition by @LoveDaisy in #279
- fix(gui): Overlay 表格人工验收三条修复 by @LoveDaisy in #280
- fix(gui): 行末标签列左缘对齐 + 间距收敛为单一 owner by @LoveDaisy in #281
- 晶体卡片 layout 重排 + 晶体身份可寻址 + Colors 面板编号/失效态 by @LoveDaisy in #282
Full Changelog: v4.4.2...v4.4.3