Composition & references

Linking across files

Connections link properties inside one document. Composition links across documents: an element can reference another .prisma/.prism file and pull its subtree in at that point. Model an asset once; reference it many times; fix it in one place and every use updates.

def group "garage" {
    def object "car1" { reference "car.prisma" }
    def object "car2" { reference "car.prisma" }   # the same model, twice
}

The reference directive names another document, and optionally a path inside it: reference "parts.prisma" "/parts/bolt" pulls in just that element. It is its own statement, not a string property, so a reference always reads as a reference. At author time the link is recorded; resolution happens when you ask for the composed document.

In code, reference is a method on the element handle; chain an override straight after it:

doc.append(Path("/garage"), "group");
doc.append(Path("/garage/car1"), "object")
   .reference("car.prisma")
   .set("color", Float3(0, 0, 1));      // local override wins over the referenced colour

Referencing a set with wildcards

A concrete path names one element, and that element becomes the referencing element. A pattern names a set, and each match arrives as a child of the referencing element, which stays in place. Two wildcards build a pattern:

Pull every part under /parts into one assembly:

def group "hardware" {
    reference "parts.prisma" "/parts/*"
}

Composing that gives /hardware/bolt, /hardware/nut, /hardware/washer, each with its own subtree. The /parts group itself stays behind; only what sits under it comes through. ** reaches deeper and brings the elements between the prefix and each match along, so the tree stays whole:

def group "scene" {
    reference "rig.prisma" "/rig/**/camera"   # every camera under /rig, at any depth
}

The pattern is the same string in code, on both the C++ handle and the Python binding:

doc.append(Path("/hardware"), "group").reference("parts.prisma", "/parts/*");

The same glob queries a document you already hold. find returns every element a pattern selects, in document order; edit each one through its path:

for (const Element* cam : doc.find("/world/**/camera"))
    doc.edit(cam->path()).set("exposure", Float(1.2));

Resolving

loadComposed reads a file and flattens every reference it (transitively) names into one document:

Document doc;
doc.loadComposed("garage.prisma");        // resolves references relative to the file on disk

For environments that resolve their own bytes (a server, a sandbox, a bundle), the portable entry point resolves against any resolver you supply, so composition works against in-memory documents just as well:

Document flat = compose(doc, Path("/garage"), [&](std::string ref) -> std::optional<Document> {
    return lookUpSomehow(ref);                     // you decide where bytes come from
});

From the CLI:

kinogaki compose garage.prisma > flat.prisma          # references resolved, one Document

Local opinions win

A referencing element can override properties of what it pulls in: local values take precedence over referenced ones. That is what makes shared assets practical. Reference the standard car, then override its colour on car2 without forking the file.

A safe, memoized DAG

References can be transitive (a referenced document may reference others), forming a directed acyclic graph. Resolution memoizes each document, so a diamond (two elements referencing the same file) loads it once rather than exponentially. Cycles are rejected, and a missing reference is a clean, located error.

Why it matters

Linking instead of copying is what turns a folder of files into a connected project, and what lets a program or an agent edit one shared file instead of every copy. "Swap the shared material to the blue one" is a single edit to one referenced document, not a search-and-replace across dozens of files that might miss one. Connections and composition are the same idea at two scales: identity is a path, and a link is just another path, within a document or across them. That principle is also what makes a directory tree expressible as a Document, which is the basis of bundles.