DeutschGitHub
partialpackages/lsystem/src

L-System

@ralphschuler/lsystem

Rewrites symbol sequences with normalized weights and injectable randomness.

grammarproceduralstochastic

01 · Problem

What is it for?

Growth rules need to operate on complete generations while keeping randomness reproducible.

02 · Mental model

How should you think about it?

An axiom is generation zero. Each next generation rewrites every old symbol independently.

In this repository

Rewrites symbol sequences with normalized weights and injectable randomness. Output can grow exponentially. Rendering and caller-specific output budgets are separate from grammar rewriting.

03 · Control flow

What happens in which order?

  1. Start from the axiom.
  2. Find rules for each old symbol.
  3. Choose one rule by normalized weight.
  4. Append its complete replacement sequence.
  5. Repeat for the requested generation count.

04 · Building blocks

The important contracts

LSystem / generateRewrite an axiom using validated weighted rules.
Rule / SymbolTypeDescribe symbols, replacements and relative weights.

05 · Build it yourself

Implement a small core

Start with this small independent implementation, then use the steps below to make its contract explicit.

  1. Start from the axiom.
  2. Find rules for each old symbol.
  3. Choose one rule by normalized weight.
  4. Append its complete replacement sequence.
  5. Repeat for the requested generation count.
Concept sketch · may reference omitted helpers
type Rule<S> = { weight: number; replacement: readonly S[] };

function expand<S>(
  input: readonly S[],
  rules: ReadonlyMap<S, readonly Rule<S>[]>,
  random: () => number,
) {
  const output: S[] = [];
  for (const symbol of input) {
    const rule = chooseWeighted(rules.get(symbol) ?? [], random());
    output.push(...(rule?.replacement ?? [symbol]));
  }
  return output;
}

06 · Verify

What should you test?

  • Check exact output after several generations.
  • Preserve symbols without matching rules.
  • Reject invalid iterations and invalid rule weights.

Try it yourself

Turn the first verification claim into a failing test, implement the smallest fix, then add a boundary case.

Check exact output after several generations.

Compare your approach

Your check should fail when the stated invariant is deliberately broken, and pass again after the fix. Keep setup deterministic and assert an observable result.

07 · Limits

Trade-offs and failure modes

  • Output size can grow exponentially.
  • Rewriting and turtle rendering are separate stages.
  • Limit output size before exposing generation to uncontrolled input.
Important

Output can grow exponentially. Rendering and caller-specific output budgets are separate from grammar rewriting.

08 · Keep exploring

Source code and related ideas

yarn tsx packages/lsystem/examples/basic.ts