Repository

lib-prism

kinogaki-prism reads and writes Prism documents, Kinogaki's native file format, in Rust.

GitHub
kinogaki/lib-prism (private)
Folder
libs/lib-prism/
Crates
1
Rust lines
6,110 in src, 2,519 in tests
This repository Used by other repositories Prism documents for Kinogaki.kinogaki-prism6.1k lines core6 crates
depends on (an edge implied by others shows when you point at a box)used by tests alone

Crates

CrateWhat it isFilesLinesTest lines
kinogaki-prismPrism documents for Kinogaki.256,1102,519

README

lib-prism

kinogaki-prism reads and writes Prism documents, Kinogaki's native file format, in Rust. A document is a flat, ordered set of path-addressed elements. Each element has a type token, typed properties (any of 14 dtypes, any shape, optionally animated by keyframes), string metadata, and slot connections to other elements. Kinogaki Studio stores its projects as Prism documents.

The same document has three encodings, chosen by content, never by file name:

EncodingStarts withUse
.prisma text#prisma 4.0Diffable, hand-editable
binary cratePRSMC\0Compact and fast; optionally LZ-compressed
packagePRSMZ\0One scene plus asset blobs

The format and document model are those of luce-prism and the original C++ kinogaki-core before it. lib-prism reads and writes the same bytes: binary crates and packages are byte-identical to the C++ reference, and differential tests check both directions against it. See the format summary.

The text form of that document:

What is here

  • Document model: elements with def/over, properties with keyframes (held, linear, Bézier with handles), catalog type names (vec3f, matrix4d, …), metadata, connections and wildcard links, layer tombstones (unset, delete, disconnect, reorder).
  • Readers and writers for text, binary (plain and compressed) and packages. Decoding is bounded by Limits and fails with an error (with line and column for text), never a panic.
  • Composition: overlay applies layers, diff computes one, compose flattens reference arcs through a caller-supplied resolver.
  • resolve (values at a time) and eval (values read through connections).

Left out of luce-prism's scope (outside Kinogaki's needs): the document database (WAL, store, sockets, file locks), queries and indexes, schemas, columnar views, logic and math surface syntax, foreign codecs (JSON, Markdown, HTML, SVG), node evaluation and editor projections.

Build and test

Rust 1.98.1. No dependencies. Owned code forbids unsafe.

Architecture · Format · Benchmarks · Third-party notices

Its documents

The repository's architecture document as written there (code blocks and links left out), and the opening of each other document.

Architecture (libs/lib-prism/docs/ARCHITECTURE.md)

Architecture

One crate, kinogaki-prism, with no dependencies. Owned code forbids unsafe.

ModuleResponsibility
dtypeWire dtype codes, widths and names; binary16 ⇄ binary32 conversion
valueValue (shape + Data, one typed Vec per dtype), typed slice access, lerp
path, patternValidated paths stored as canonical strings; */** globs
aliasesThe fixed catalog of named types
curveKeyframe curve maths over f64 components (held, linear, Bézier) through the Keys trait; allocation-free
propertyDefault value, sorted keyframes, catalog label; time resolution through curve
elementType token, properties, metadata, override flag, tombstones
documentOrdered elements with path and child indexes, connections, wildcard links, layer directives, resolve/eval
composeoverlay, diff, compose (references through a resolver)
binary/Crate reader (bounded cursor), writer (streaming), LZSS codec
text/Lexer, reader (directives, elements, keyframes), literal parser, writer, number formatting
packagePackage table, scene and assets
encodingEncoding detection; Document::decode/encode/load/save/write_to
limits, errorDecoding bounds; errors with kind, message and text location

Decisions

Typed storage. Components live in a Vec of their Rust type (Vec<f64>, Vec<u32>, …), not in a byte buffer. Callers borrow them as slices (as_f64s) or take them without a copy (into_f64s). Binary decoding is one bounds check and a bulk conversion per array; encoding writes through a 16 KiB stack block. Booleans and chars keep raw bytes so a non-canonical crate re-encodes identically.

Paths are strings. A Path is its validated canonical string, so lookups take &str (HashMap<Path, usize> with Borrow<str>) and ordering is bytewise, which is luce-prism's order and equals the C++ segment order for every path the C++ reader accepts.

Deterministic by construction. Properties, metadata and tombstones are BTreeMap/ BTreeSet; connections are a target→source map; wildcard links a sorted set. Every encoding is a function of content, so equal documents encode to equal bytes and the binary writer matches the reference without a sort pass.

Bounded, fail-closed decoding. Every count is checked against the remaining bytes (at the smallest possible record size) and against Limits before anything is allocated; declared array shapes are checked the same way in text. Malformed input is an Error with a kind, never a panic (truncation and mutation tests feed every prefix and thousands of random corruptions). Text errors carry line and column.

Streaming where it pays. write_to/save stream binary and text through a buffered writer without building the file in memory; loading reads the file once and decodes from the slice. Large numeric arrays dominate cost, and those paths run at memory bandwidth (see BENCHMARKS.md). Compression is optional and slow by comparison because its match search matches the reference byte for byte.

Composition is explicit. Decoding never follows reference arcs or touches the file system; compose takes a resolver, which is the security boundary.

Differences from the reference implementations

  • From the C++ reference: names may contain - (luce-prism); the text reader rejects a property set twice in a block, out-of-range integers, multi-byte chars and invalid UTF-8; it reads general bool arrays, quoted animated property names and float3[], which the C++ reader cannot; the writer keeps signed zero and full-precision keyframe times; binary decoding rejects duplicate records, non-slot connection endpoints, unknown flags and unsorted keyframes instead of repairing them. overlay applies wildcard links stated in a layer and clears tombstones on elements it adds; diff deletes removed top-level elements (the C++ diff omits them, so its overlay cannot reproduce the target).
  • From luce-prism: numbers are written in shortest round-trip form rather than %.9g/%.17g (both read back to identical values). The asset store is a separate Assets map used with packages, not part of Document.

Testing

  • tests/fixtures.rs: the C++-generated crate, compressed crate and package fixtures are reproduced byte for byte; luce-prism's eight pinned model digests (SHA-256 of the reference crate) through all five encodings.
  • tests/serialization.rs, tests/values.rs, tests/hardening.rs, tests/packages.rs and tests/layers.rs: serialization and compression, values and animation, hostile input, packages, and overlay, diff and reference cases.
  • tests/robustness.rs: 400 generated documents (layers included) through text, binary, compressed, package-binary and package-text with byte-stable re-encoding; every truncated prefix; random mutations; hostile counts; limits.
  • tests/oracle.rs (hand_run_, with PRISM_ORACLE; failing without it): both directions against the C++ reference on the fixtures, any PRISM_CORPUS directories and 300 generated documents; overlay/diff on layers.

Benchmarks (libs/lib-prism/docs/BENCHMARKS.md)

luce-prism was timed on the same files with a release Luce 0.24.3 program calling Document.load, encode and save (5 samples binary, 3 text). lib-prism is 47× faster saving binary (28× in memory), 3.7× loading binary, 5× encoding text and 6× loading text. Binary decoding runs at memory bandwidth; file rows add the kernel copy. Compression matches the C++ encoder's match search byte for byte, which costs encode time; Studio saves uncompressed crates.

Prism v4 format summary (libs/lib-prism/docs/FORMAT.md)

The storage contract is the one specified by kinogaki-core (include/kinogaki/Serialize.h, src/SerializeBinary.cpp, SerializePackage.cpp, Compress.cpp, TextParser.cpp at ce92a4b37887d4ebb14194ca2ba4084af657c34a) and documented in luce-prism's docs/FORMAT.md. This page summarises it and records where lib-prism follows luce-prism rather than the C++ reader.