Architecture notes for contributors and agents¶
Orientation only: where things live and what the terms mean. The math, the worker pipeline and the loop mechanics are in ../explanation/architecture.md.
Module layering¶
Five plain scripts, each attaching a window global, loaded in order (file://-safe, no ES modules):
perlin.js→MTPerlin— math +WORKER_SOURCE.perlin-presets.js→MTPerlinPresets—GRADIENTS,BUILTIN_PRESETS,resolveParams(named gradient → concrete stops).perlin-engine.js→PerlinEngine— headless renderer: owns the worker, the RAF loop, and FPS smoothing. Consumers passonTick/onFrame/transformFramecallbacks.perlin-studio.jsandperlin-element.jsare consumers of the engine. The studio is the full editor;<perlin-noise>is the embeddable renderer-only element. Keep render/animation logic in the engine, not duplicated in either consumer.perlin-gl.js→MTPerlinGL— optional WebGL2 renderer, a GLSL port ofperlin.js(same permutation table, same gradients).PerlinEngine.setBackend("gpu")routes frames through it; unsupported modes fall back to the CPU path. Not in the standalone bundle yet (the<perlin-noise>element renders on the CPU).perlin-export.js→MTPerlinExport— pure compositing helpers (computeFitRect,compositeOverColor,hexToRgb) used by the studio's video export. No DOM; Node-requirable (smoke-tested).
File layout¶
.
├── index.html # UI markup
├── styles.css # all styling (dark + light, system-following with toggle override)
├── perlin.js # math/API (noise, color modes, worker source)
├── perlin-presets.js # gradients, built-in presets, resolveParams (shared)
├── perlin-engine.js # headless render engine (worker lifecycle + RAF loop)
├── perlin-gl.js # optional WebGL2 renderer (GLSL port of the noise maths)
├── perlin-gl-check.js # CPU-vs-GPU parity check, runs in any browser (not loaded by the studio)
├── perlin-studio.js # controller (UI bindings, recording, presets) — consumes the engine
├── perlin-element.js # <perlin-noise> embeddable renderer-only custom element
├── perlin-export.js # pure compositing math for video export (window.MTPerlinExport)
├── manifest.webmanifest # PWA manifest (installable)
├── assets/ # static assets (favicon.svg, icon-{192,512}.png,
│ # apple-touch-icon.png, hero.png, gallery-*.png
│ # + legacy default-atlas.webp for the archive)
├── examples/
│ ├── embed.html # <perlin-noise> embedding demo
│ ├── gpu-check.html # runs perlin-gl-check.js on a device (phone, other machine)
│ └── marble.json # resolved preset export for the src demo
├── scripts/
│ ├── check-syntax.mjs # syntax gate (external perlin*.js + inline <script>), gates the deploy
│ ├── smoke-test.js # math + resolveParams invariants, gates the deploy
│ ├── check-no-abs-paths.mjs # CORE-META-14 gate: no absolute paths in tracked *.md
│ ├── check-gl-parity.mjs # CPU vs GPU pixel comparison, by hand (needs Playwright)
│ ├── deploy-page.sh # publish committed HEAD to pages.jkaindl.de (rsync)
│ ├── bump-version.sh # native version bump (macos/packaging/Info.plist)
│ ├── render-readme-images.js # renders assets/*.png from perlin.js (+ oxipng)
│ ├── render-guide-images.js # renders docs/images/*.png for the docs
│ ├── render-readme-videos.js # renders docs/examples/*.webm (Node → ffmpeg)
│ └── build-standalone.js # concatenates the runtime scripts into dist/perlin-noise.js
├── macos/ # native companion app (Swift + Metal); see macos/README.md
├── ios/ # native iOS/iPadOS companion "Perlin Live Photo"; see ios/README.md
├── dist/
│ └── perlin-noise.js # generated standalone bundle (scripts/build-standalone.js) — do not edit
├── _archiv/ # historical predecessors (READ-ONLY!)
│ ├── perlin_noise/ # Python original
│ ├── texturizer.ipynb # Texturizer notebook
│ └── old_texturizer_combined/ # first browser version
├── docs/
│ ├── README.md # documentation index (Diátaxis)
│ ├── tutorials/ how-to/ reference/ explanation/ # user documentation, one page per need
│ ├── internal/ # architecture notes and decisions for contributors and agents
│ └── examples/ # rendered WebM loops (for README + embedding)
├── .claude/rules/ # path-triggered agent rules (perlin-core.md)
├── .editorconfig # consistent indentation/encoding/EOL
├── .well-known/
│ └── security.txt # RFC 9116 contact (deployed with the site)
├── .gitea/
│ └── issue_template/ # bug report + feature request forms
├── README.md # quickstart + entry point
├── ANGEBOT.md # what the repo delivers to others (PROF-MEDIA-01)
├── CONTRIBUTING.md # developer workflow
├── SECURITY.md # threat model + reporting
├── CHANGELOG.md
├── LICENSE # AGPL-3.0 (code)
├── LICENSE-DOCS # CC BY-SA 4.0 (documentation)
├── LICENSING.md # licensing overview / dual-license option
├── CLA.md # contributor license agreement
└── AGENTS.md # agent core and index
_archiv/ documents the project's genealogy and may only be added to (e.g. when another snapshot is preserved), never edited or deleted.
Glossary¶
| Term | Meaning |
|---|---|
| fBm | Fractal Brownian Motion — sum of Perlin octaves with decreasing amplitude and increasing frequency |
| Octave | A frequency level in fBm. More octaves = more detail, more compute |
| Persistence | Amplitude factor between octaves (typically 0.5) |
| Lacunarity | Frequency factor between octaves (typically 2.0) |
| scaleT | UI label "Time scale" — scales the time axis of the noise. Default 0.15 |
| loop | When true: 4D path with a circular orbit in (z, w). When false: 3D path with linear t |
| loopProgress | Current position on the circular orbit in [0, 1). Becomes α = 2π·progress |
| loopRadius | How far the image travels over one full rotation |
| safeProgress | ((p % 1) + 1) % 1 — protects the loop identity against the float-rounding of Math.sin(2π) |
| Worker source | The full perlin.js code as a string in MTPerlin.WORKER_SOURCE. Loaded into an inline Blob worker — file://-compatible |