# Split Rail Fence Lab

Draw a path. Build a fence. Edit the result.

**[Open the live demo](https://maxliebscher.github.io/SplitRailFenceThreejs/)** — no installation needed. ·
[Watch the 19-second making-of — 1080p MP4, 1.5 MB](assets/split-rail-procedural-19s-1080p.mp4) ·
[GitHub publication setup](PUBLISH.md)

Standalone procedural fence editor · eigenständiger prozeduraler Zaun-Editor.
Worm / zigzag, stake-and-rider, post-and-rail, Skigard and hybrid fences, with
terrain sampling, editable paths and a static gate. MIT licensed.

![Generated Skigard example](assets/skigard.webp)

## Five construction families / Fünf Bauarten

| Worm / zigzag | Stake-and-rider | Post-and-rail |
| --- | --- | --- |
| ![Worm fence](assets/wurm.webp) | ![Stake-and-rider fence](assets/stake.webp) | ![Post-and-rail fence](assets/pfosten.webp) |

| Skigard | Modern hybrid |
| --- | --- |
| ![Skigard](assets/skigard.webp) | ![Modern hybrid](assets/hybrid.webp) |

All images and the clip are renders from this generator, not historical reference
photographs. The clip is a prepared preview; the app offers camera-tour playback,
not an in-browser MP4 exporter.

## Start / Starten

Node.js 20 or newer / oder neuer:

```sh
npm start
```

Open / Öffnen: **http://127.0.0.1:8807**. No `npm install` is needed to run the
demo or geometry tests. All runtime dependencies are included locally. After
download the application needs no Internet connection. A local HTTP server is
required for ES modules and workers; opening `index.html` via `file://` is not
supported. Any static host also works, including a repository subdirectory on
GitHub Pages. Deploy the package contents unchanged; no build step or secrets.

Kein `npm install` zum Starten oder für die Geometrietests nötig. Alle
Laufzeitabhängigkeiten sind lokal enthalten. Nach dem Download funktioniert die
Demo ohne Internet. Wegen ES-Modulen und Workern ist ein HTTP-Server nötig;
`index.html` nicht per Doppelklick öffnen. Alternativ einen beliebigen statischen
Webserver verwenden. Kein Build-Schritt, Backenddienst oder API-Schlüssel nötig.

## Use / Bedienung

- Language: automatic from browser preferences (usually inherited from the OS),
  or choose Deutsch / English. The preference is saved locally. `?lang=en` and
  `?lang=de` override it for a shared link. No browser API reliably reads an
  independent OS language setting.
- Draw new: drag on the ground, release to validate. Edit points: drag yellow
  handles, double-click to insert, Delete to remove. Escape cancels drawing or a
  pending validation. Invalid proposals preserve the last valid model.
- Undo/Redo, local browser storage and JSON export/import keep the editable path,
  seed and settings—not an opaque mesh. Imports are revalidated.
- The 24-second camera tour is opt-in and stops on interaction. Nothing moves
  automatically, including when reduced motion is preferred.

- Sprache automatisch aus den Browserpräferenzen (meist vom Betriebssystem
  übernommen) oder manuell DE/EN. Die Wahl wird lokal gespeichert; `?lang=de`
  bzw. `?lang=en` ist als Link möglich.
- Neu zeichnen: auf dem Boden ziehen, loslassen zum Prüfen. Punkte: gelbe Griffe
  verschieben, Doppelklick zum Einfügen, Entf zum Löschen. Escape bricht ab.
  Ungültige Vorschläge ersetzen den gültigen Zaun nicht.
- Undo/Redo, lokaler Speicher und JSON-Dateien erhalten Pfad, Seed und Parameter.
  Importierte Daten werden erneut geprüft.
- Die 24-Sekunden-Kameratour startet nur auf Klick und endet bei einer Eingabe.

## Reuse the generator / Generator weiterverwenden

```js
import {prepareFencePath} from './modules/zaun-pfad.js';
import {compileFence} from './modules/zaun-gesamt.js';
import {terrainPreset} from './modules/zaun-terrain.js';

const settings = {
  type: 'skigard', seed: 7, form: 'spaltkeil', layers: 6,
  slope: 0, height: 1.25, spacing: 0.85, density: 2,
  direction: 1, binding: 'weide', terrain: terrainPreset('crest')
};
const prepared = prepareFencePath([{x:-4.5,z:0},{x:4.5,z:0}], {layout:'plain'});
const plan = compileFence(prepared, settings);
if (!plan.diagnostics.valid) throw new Error('Unbuildable proposal');
// plan.parts: named solids; part.surface: triangles with points and UVs.
// plan.contacts: measured bearings; plan.diagnostics: mesh/ground/collision audit.
```

Types: `wurm`, `stake`, `pfosten`, `skigard`, `hybrid`. Use path layout `worm`
for the first two, `plain` for the others. Coordinates are metres, Y is up.
For real terrain supply `settings.terrain = {x,z,step,columns,rows,heights}`;
row-major heights use the cell diagonal documented in `zaun-terrain.js`. Raster
queries outside the snapshot fail rather than silently extrapolating.

The geometry core has no Three.js dependency. Batch its immutable surfaces into
your renderer. Geometry changes require new arrays because audit caches use
surface identity. Reuse the host application's selection, history, storage and
render scheduler; do not embed a second editor loop. The optional AI-agent skill
in `skills/procedural-fence/SKILL.md` summarizes the integration contract.

Der Geometriekern benötigt kein Three.js. Die unveränderlichen Oberflächen lassen
sich in den eigenen Renderer übernehmen. Anwendungsintegration: vorhandene
Auswahl-, History-, Speicher- und Renderlogik anbinden, nicht doppelt einbauen.
Die optionale Agent-Anleitung steht in `skills/procedural-fence/SKILL.md`.

## Test / Prüfen

```sh
npm test
```

Built-in Node assertions cover paths, joins, terrain, mesh validity, collisions
and old/new collision-algorithm parity. No test libraries required. Browser
checks are separate: open both languages, draw/edit, reject invalid input,
undo/redo, JSON roundtrip, cancel, start/stop the tour, check idle rendering.

## Clip

`assets/split-rail-procedural-19s-1080p.mp4`: 19 seconds, 1920×1080, 30 FPS,
silent H.264. Real editor actions show path drawing, rail counts, five fence
families, proportions, finishes, terrain and hybrid transitions. Computation waits
are cut. Reproduce with `node tools/render-x-makingof.mjs`; output goes to
`output/makingof/` with the same optional Playwright/FFmpeg requirements below.

`assets/fence-tour.mp4`: locally rendered H.264, 1280×720, 30 FPS, silent,
24 seconds. Intended as a reusable social preview, not a published post.
See `tools/render-media.mjs` for regeneration; it optionally uses Playwright and
an installed FFmpeg. Neither is required for the demo itself.

Optional authoring / optionale Medienerzeugung (with the local server running):

```sh
npm install --no-save playwright
npx playwright install chromium
node tools/test-browser.mjs
node tools/render-media.mjs
```

Install FFmpeg separately and expose `ffmpeg` on PATH (or set the `FFMPEG`
environment variable to its executable). `--stills` renders only the WebP images.
The clip uses five deterministic 4.8-second shots sampled from the tour camera;
the interactive tour orbits the currently selected fence for 24 seconds.

## Limits / Grenzen

2.3–65 m per fence; one gate/transition per open path. No gate/hybrid on closed
rings. Gate leaves do not swing. Hybrid detail is a modern independent support,
not a historically authenticated joint. Bindings are simplified. Terrain demo
rasters span 80×80 m. No structural certification, load calculations or universal
guarantee for arbitrary parameter combinations. Invalid combinations are rejected.

2,3–65 m je Zaun; ein Tor/Übergang je offenem Verlauf, keine beweglichen Torflügel.
Hybrid und Bindungen sind vereinfachte Konstruktionsmodelle. Keine Statik oder
baurechtliche Freigabe. Unzulässige Kombinationen werden abgewiesen.

Dense 64 m Skigard: roughly 3–4 s construction/audit in local CPU tests, dependent
on hardware. Validation runs in a cancellable worker. Rendering is invalidated
on change, capped at 30 FPS, with no idle frames. GPU time is not measured.

## Licensing

Own code and generated media: [MIT](LICENSE). Three.js r185 retains its own MIT
notice: [third-party notices](THIRD_PARTY_NOTICES.md). No reference photography
is redistributed. No analytics, telemetry, CDN requests or external services.
