Extending the Language

The standard criticism of any codified language is that it is frozen at design time — the vocabulary its designers thought of is the vocabulary you get. IDSDL is built to escape that. The four core groups are just what the language ships with, and it has already grown past them, three different ways:

Route Who writes the code What came of it
Automatic the coding agent, following a skill six motif groups: stack, pyramid, pile, symmetry, facing, rings
Human in the loop a designer’s rules, the agent’s code MirrorStationGroup, WorkstationGroup, KitchenIslandGroup
Wrapping another tool a person, by hand SentenceASCIIGenerator — an LLM behind a group interface

Different origins, same destination: every one of these is a first-class group.

Automatic — the agent extends its own language

The six motif groups were not written the way the core groups were. They were authored by the coding agent itself, working from skills/add-placement-group/SKILL.md: a written procedure the agent follows whenever a build reveals an arrangement no existing group can express. The skill forces the judgment call first — does this arrangement earn a group? (a recurring, nameable relationship, not a one-off prop) — then constrains the implementation to a thin subclass of the anchored-group base that inherits the whole compile pipeline unchanged, and nothing ships until it passes both a numeric test and a rendered figure the agent actually inspects.

Group Motif
StackGroup objects stacked vertically, each resting on the one below
PyramidGroup centered tiers of decreasing count
PileGroup an organic scatter the inherited overlap solver relaxes apart
SymmetryGroup mirror-symmetric pairs flanking an anchor
FacingGroup two rows on opposite sides of an anchor, turned to face it
RingsGroup concentric rings around an anchor, each seat facing inward

Each one is used exactly like a core group:

with scene.RingsGroup() as g:
    table = scene.AddAsset("a large round dining table with a dark wood finish")
    g.set_anchor(table)
    chair = scene.AddAsset("an upholstered accent chair")
    g.place_rings([4 * chair, 8 * chair])   # inner ring of 4, outer ring of 8

And the results are indistinguishable from hand-written vocabulary:

agent-authored PyramidGroup agent-authored RingsGroup

Human in the loop — groups that emerged from recognized gaps

Some arrangements carry real design judgment — gap sizes, mounting conventions, clearance rules — that an agent alone shouldn’t invent. These groups were born when a person recognized a gap in a specific scene and supplied the domain rules, with the agent building and validating the code. The admission test stayed the same: generality. When a hair-salon scene needed a mirror with a chair facing it, the insight was not “salons need this” — it was “a wall mirror with a facing floor item is a relationship the DSL can’t express,” which covers gym treadmills, bedroom vanities, and dressing rooms in the same stroke.

Group Built for What it encodes
MirrorStationGroup hair-salon styling stations wall-mount height conventions; counter and shelf proportions
WorkstationGroup a dental-office reception desk the desk↔chair tuck gap; desktop items seated on the real writing surface
KitchenIslandGroup fitted U/L kitchen sets attachment rules read off the set’s real mesh footprint, with audited walkway clearances
with scene.MirrorStationGroup() as st:
    st.set_anchor(scene.AddAsset("a salon styling chair"))
    st.place_counter(scene.AddAsset("a narrow styling console"))
    st.place_mirror(scene.AddAsset("an arched gold-framed salon mirror"))

Each landed through the same add-placement-group workflow — and each fed its hard-won lessons back into the skill as a worked precedent, so the procedure gets smarter with every group it produces.

Wrapping another tool

A group’s place_* methods can hide any capable machinery — SentenceASCIIGenerator wraps a language model that draws text as ASCII-art glyphs and lays out copies of your object along them, all behind the standard group interface.

Every route ends in the same place

However a group entered the vocabulary — agent-authored motif, human-guided composite, hand-wrapped tool — it is a full citizen of the DSL:

  • Same lifecycle — placements recorded in the with block, compiled on close, frozen into a rigid unit that nests into a RoomGroup like anything else (Hierarchical Layout).
  • Same solver — subclasses of the anchored base inherit overlap solving, proportion checks, and gradient optimization for free; PileGroup is three lines of scatter math, and the inherited solver is what relaxes the pile apart.
  • Same knobs — every group takes the sparsity and jitter dials from the Groups page, each applied so its own contract holds: SymmetryGroup jitters both twins mirrored, KitchenIslandGroup wobbles only the stools.
  • Same constraint hooks — the full constraint vocabulary binds to every group dynamically; a RingsGroup accepts a ClearanceConstraint exactly as a RelativeGroup does.
  • Lint coverage, automatically — lint_program introspects the live API surface, so the moment a new group’s factory exists, invented methods and misspelled keywords on it are caught with did-you-mean hints.

New vocabulary can come from the agent itself, from a person and the agent working together, or from a wrapped tool — and the language treats all of it as if it had been there from the start.


Next: watch every group type come together in the Worked Examples.


This site uses Just the Docs, a documentation theme for Jekyll.