Caustica docs 12. Code structure Edit on GitHub

12. Code structure

Caustica is plain JavaScript with no framework or build step, in /assets/caustica/. Edit the files and reload the page.

FileLinesRole
lens.js~600Physics on the CPU, no page access
renderer.js~2100WebGL2 drawing; the lensing itself runs per pixel on the GPU
main.js~10200Everything else: state, interface, overlays, configs, recording
style.css~3000Layout and theming
gif.js, gif.worker.js GIF encoder (third-party)

lens.js

renderer.js

The Renderer class owns the WebGL2 context. main.js hands it the scene with setScene(...), plus properties for the background, observation, masks and detector. One fragment shader repeats the physics of lens.js on the GPU (ray tracing, deflections, source profiles, potentials, colour maps), so each pixel is computed independently. Its limits, MAX_PLANES (8), MAX_OBJECTS (32 lenses and 32 sources), MAX_PASTED (4 pasted images) and MAX_MASKS (16), are exported so the interface can warn about them. On a GPU that cannot fit 32 objects, the shader is rebuilt with fewer (down to 8), and renderer.maxObjects reports the number used.

Each frame is one of:

main.js

State. state holds the scene and the focused view’s settings. state.planes is the list of planes, sorted by redshift, each with its objects (type, model, cx, cy, params, hidden, and hybridId for the two halves of a hybrid). Masks live on state.obs, beside the planes, so nothing that traces rays sees them. CONFIG_DEFAULTS gives the starting value of every view setting. record() takes an undo snapshot just before a scene edit (beginAction() / commitAction() do this once for a whole drag or slider move), and undo() / redo() restore them.

Views. _panes holds one or two panes, each with its tabs and its own copy of the per-view settings, and _focusPane is the one last clicked. state always holds the focused pane’s values; _withPane(i, fn) swaps a pane’s values in while it is drawn. PANE_OWN_KEYS (curve, caustic and marker toggles) always belong to each pane, and the LINK_GROUPS do once unlinked (setLink). setVizMode sets the focused pane’s view.

Drawing a frame. redraw() schedules _doRedraw(), which pushes the background, observation and detector settings to the renderer (syncBackdrop, syncDetector), draws the second pane (if any) and copies it to that pane’s canvas, then draws the first pane. _renderGL() chooses what the renderer draws for the current view. drawOverlay() then draws, per pane, everything on the 2D canvas above the image: critical curves and caustics, markers, point-source images, shape outlines, ruler, legend, scale bar, and line art (drawLineArtBase).

Interface. buildDOM() builds the page once. The right-hand panels are rebuilt from templates by renderSidebar() (renderViewPanel() and others), the scene drawer by renderDrawer() and renderSightLine(), and the tab strips by renderTabs(). Pointer handling is in attachImageHandlers (the image), attachPlaneCardHandlers (plane viewers), attachSightHandlers (the line-of-sight strip) and attachPaneHandlers (tabs and split views); keys are in attachHandlers, with the list in SHORTCUTS.

Configs. configToYaml() writes the scene and settings, with _paneLayoutYaml() adding the view layout. parseYamlConfig() reads a file back and checks every value (known model names, colour format, numeric ranges); missing settings fall back to CONFIG_DEFAULTS, and _applyPaneLayout() restores the layout last. Picture names (bgImage, obsImage, image:) must pass isPresetBgName(), a single fixed folder, so a config cannot load an arbitrary address. Presets are listed in PRESETS, with their files in /images/caustica-presets/.

Where to find things

FeatureWhere
Point-source imagesfindPointSourceImages → imageSearchGrid + solveImages
Critical curves for the viewviewCritCurves, computeCritCurvesForZs (cached)
Image number mapimageNumberCounts, drawImageNumberGrid
Fermat markers and time delaysfindStationaryPoints, _computeFullFermat
Stretch whiskerscomputeStretchField
Einstein-radius readoutcomputeThetaE
Changing an object’s modelswitchModel (CARRY_PARAMS)
Use lensed imageuseLensedAsObservation
PNG, video, GIFcaptureSnapshot, buildCompositeCanvas, startRecording / stopRecording, startProgrammaticRecording
SVG exportexportLineArtSVG
Full screensetPresenting
Tour, shortcut liststartTour, toggleKbdOverlay

style.css

Colours are CSS variables, with html[data-theme="dark"] overrides for the dark theme. The layout is a CSS grid: on a computer, the tool column, the image area and scene drawer, the panels, then the icon column on the right. At 640 px wide or less it becomes one scrolling column, and short landscape windows (480 px tall or less) ask for a taller window. In the light theme the lensed-image canvas is inverted with a CSS filter (kept in step with glCanvasInverted() in main.js).

gif.js + gif.worker.js

A local copy of the gif.js library, loaded only when a GIF is recorded. Its worker has one change: colour lookups are cached, since every frame uses the same 256-colour palette.