Panel Core#
The resolved intermediate format, between the authored source and emitted SVG.
*.panel.yaml → IR → *.core.json → *.svg
authored validated resolved rendered
Why this tier exists#
Vega-Lite compiles a high-level grammar into a lower-level one before emitting SVG, rather than going straight to pixels. The same split pays for itself here, in four ways:
Tests target stable numbers, not markup. Golden-file tests compare Panel Core JSON, which changes only when layout genuinely changes. Diffing SVG text is brittle: reordered attributes or a changed path-rounding convention produce enormous diffs that mean nothing.
Layouts can be adjusted by hand. An artist who wants one balloon two millimetres left can edit the Core file, without touching the source or re-running the solver.
One input, several emitters. SVG, the debug overlay, and later formats all consume the same resolved tier.
The high-level grammar stays a strict subset of the low-level one, so nothing expressible in the source is inexpressible after resolution.
Properties#
Fully numeric. Every position, size and scale is an absolute number in panel units. No
left_third, nomedium_shot— those are resolved away.Still named. Actors, balloons and anchors keep their source identifiers, so a Core file is readable and diffable. This is what separates it from SVG.
Deterministic. Floats are rounded to a fixed precision on emission, so identical input yields byte-identical Core output on any platform.
Shape#
{
"panel": { "width": 1000, "height": 1000 },
"actors": [
{
"id": "alice",
"reference": "alice",
"pose": "pointing",
"transform": { "x": 208.33, "y": 140.0, "scale": 0.72, "mirrored": false },
"anchors": { "mouth": [263.1, 268.4], "eyes": [259.0, 251.2], "feet": [270.0, 680.0] },
"face_exclusion": { "cx": 259.0, "cy": 255.0, "r": 46.8 },
"gaze": [1.0, 0.0],
"hull": [[180.2, 140.0], [340.5, 140.0], "..."]
}
],
"balloons": [
{
"id": "b0",
"speaker": "alice",
"order": 0,
"box": { "x": 60.0, "y": 55.0, "width": 330.0, "height": 118.0 },
"lines": ["You forgot your", "umbrella!"],
"tail": { "kind": "straight", "from": [225.0, 173.0], "to": [263.1, 268.4] }
}
]
}
lines is the resolved line breaking, not the source string. Wrapping is decided during
compilation using real font metrics, so the emitter never re-measures and never disagrees with the
solver about how wide a balloon needs to be.