The model: Document, Element, Path, Value

The Document

A Document is a set of Elements indexed by Path, each carrying typed Propertys; a Property is a Value, optionally varying over time. That is the model. It lives in memory as one value type: it copies, compares with ==, and serializes whole.

The Document is the one source of truth. The bytes you save, the scene you edit, and the graph you evaluate are the same Document, not three structures kept in sync. Comparing two with ==, taking the diff between a before and an after, and handing a copy to another thread are all plain operations on plain data.

Element: a node in the tree

An Element is one node: a Path, a type token, a map of typed Properties, and a map of string metadata.

def object "ball" {
    float3 position = (0, 1, 0)
    float  radius   = 1.5
}

The type token (object, group, heading, rect, folder, and so on) is a plain string. It tells a reader how to interpret the Element, and Kinogaki Core treats it as a label, not behaviour. That is deliberate: the same container holds a 3-D object, a document heading, and a directory entry, because the structure stays general across every domain. Domains live in the vocabulary of type tokens and property names a reader agrees on, and Kinogaki Core stays neutral.

Path: the only identity

A Path is a slash-separated address (/world/ball) with an optional trailing .slot naming a Property: /world/ball.radius. The Path alone identifies an Element. Its location in the tree is its identity.

This one decision pays off everywhere:

Property: a typed, time-aware value

A Property is a named, typed value, optionally carrying time samples. Ask for it at a moment in time and you get a concrete value; with samples present, floating-point properties interpolate between keyframes.

def object "ball" {
    float radius = 1.5                       # a constant
    float3 position = {                       # animated: keyframed samples
        0:  (0, 0, 0)
        24: (0, 5, 0)
    }
}

Animation is one axis on the property, available on every numeric property of every element, in every kind of document. An animated GIF, a moving camera, and a value that ramps over a slider drag are the same mechanism.

Value: the universal datum

A Value is a typed datum: a dtype (bool, the integer and float widths, str) plus a shape (a scalar, a vector, or a multi-dimensional array), over one flat buffer. A 2-D point, a 4×4 matrix, a colour, and an image row are all shapes over a dtype, expressed through the one class.

That uniformity is the quiet engine of the whole platform. One Value type is simultaneously:

Codecs lean on this directly: an EXR channel, a vertex buffer, and an SVG point list are all typed arrays, so the canonical models the codecs target reduce to the same typed values, one shared set rather than four unrelated schemas.

Tree and graph are the same elements

Because a connection joins two property paths, a Document of elements is also a directed graph. The tree you save and the dataflow you evaluate are the same elements, read two ways.

def object "ball" {
    float3 albedo = (1, 1, 1)
    connect albedo = </world/mat.out>
}

An evaluator pulls values through those connections on demand; a dependency graph tracks what must recompute when an input changes. The document is the program.

Pure and portable

The model rests on a few deliberate choices (path is the only identity, every datum is a typed value, references are connections) and on one constraint that makes it embeddable anywhere: Kinogaki Core depends only on the C++ standard library, and installs in Python with pip install kinogaki. It compiles on anything with a C++20 compiler and is exhaustively tested. The whole library stays small enough to understand in an afternoon.

With the model in hand, see how Core works over it: querying (filter, aggregate, join), indexes, columnar scans, schema and null semantics, and versioning and history (diff, merge, snapshots, the event log).