runtime/ is a standalone Rust library (MIT) that plays .p2lrt models compiled by PSD2Live: parameter-driven deformation, physics and motion clips, with a C ABI for other languages and engines. The editor calls the same library through :format-eval (JNA).
| Part | Location | License |
|---|---|---|
.p2lrt writer and the p2lrt export target |
:targets:runtime (P2lrt, P2lrtTarget) |
MIT |
| Reading, evaluation, physics, motion clips, C ABI | runtime/ (crate p2l-runtime, cdylib + staticlib) |
MIT |
| C header | runtime/include/p2l_runtime.h |
MIT |
| JVM binding | :format-eval (P2lRuntime, NativeGeometryEvaluator) |
MIT |
| Web player | :targets:web (export target web: page, p2l.js, WebAssembly build of the runtime) |
MIT |
Godot 4 node P2LCharacter |
runtime/godot/ (GDExtension, godot-rust, Godot 4.3+), demo project runtime/godot/demo/ |
MIT |
Building: run cargo build --release in runtime/; the library lands in runtime/target/release/ (p2l_runtime.dll on Windows). Gradle's buildRuntime builds it automatically when cargo is present: ./gradlew run uses it, and packaging ships it with the app resources; without cargo it is skipped and the app falls back to the editor evaluator. P2lRuntime.locate() looks in turn at the system property psd2live.runtime.library, the environment variable PSD2LIVE_RUNTIME (file or folder), the system property psd2live.runtime.dir, the packaged app's resource folder and java.library.path.
The editor's export baking (Spine, DragonBones, glTF) evaluates through NativeGeometryEvaluator when the runtime library is available and with the engine evaluator otherwise; the two agree pose for pose. The editor's software preview (RigCanvasSupport.evaluate) also uses the runtime through NativePreview: the first time a preview model appears, the engine answers and the model is compiled in the background, and later frames go through the runtime; temporary models during a drag do not wait for compilation, and on failure or with -Dpsd2live.preview.runtime=false the engine stays in use. One evaluation on the tml sample with a skeleton takes about 0.5 ms, on par with the engine (the JNA call and result conversion offset the native speed).
The preview canvas can use the runtime as its preview backend (PSD2Live runtime (p2lrt), see CANVAS_RENDERER.md (Chinese)): the preview model is compiled to .p2lrt (with simulation) in the background, the runtime evaluates the full pose given by the workspace clock, and P2lrtGlRenderer draws it by the web player's rules in the window's own Skia OpenGL context. With extended (advanced) mode on, each view's model enables skinning and exact links; live simulation and collision follow the preview's physics toggle (when on, p2l_update_stages(dt, P2L_STAGE_SIM) advances only live simulation by frame time and keeps running while paused; when off, the baked swing shows). Physics already composed by the workspace is preserved, and the runtime's procedural behaviors stay off. Background compilation also prepares and caches skinning weights; views create independent pose and simulation instances from the shared model, so mode switches do not fit weights on the render thread. When the Cubism SDK is unavailable, the preview switches to it automatically.
Advanced mode only affects data carried by the model. The preview status explains when no extensions apply, or when simulation is the only available extension and physics is off. Unbaked simulations provide no live simulation data. If an enabled baked simulation cannot build or calibrate, compilation reports that simulation instead of silently dropping it.
Little-endian binary, a compact representation of the compiled IR: canvas, parameters, deformers (parents first), parts, meshes, Glue, draw tree, texture pages, physics groups, motion clips and parameter roles (such as EyeBlink, LipSync). All references are indices.
Exports write version 2 by default: a file header, a chunk directory and 16-byte-aligned chunks with CRCs, strings collected in a string table, encoded arrays aligned to 4 bytes, plus the parameter panel (PGUI) and generator info (META). The layout and evolution rules are in P2LRT_V2.md. The export option v1 writes version 1 (a single sequential stream; the field-by-field layout is in the doc comment of P2lrt.kt) for players not yet updated; compress deflates the chunks that get smaller (except texture pages), and strip_names removes display names and keeps ids.
The reader (runtime/src/rig.rs, container.rs) reads both versions into the same Rig, so there is only one copy of the evaluation code; it validates references, mesh indices, keyform sizes, chunk positions and alignment, and that each chunk's records are read exactly to the end; it skips unknown optional chunks and reports the tag of an unknown required chunk. Chunks may be deflate- or zstd-compressed (export setting compress, plus zstd for zstd).
Evaluation outputs each mesh's vertices in canvas pixel coordinates (y down), opacity, multiply/screen color and draw order. All rules were obtained by black-box probing of the editor evaluator (WarpProbeTool, RuntimeConformanceTool), without referring to its implementation:
- Parameters: clamped to their range; looping parameters wrap only values outside the range, and both ends keep their value.
- Keyform grids: multilinear interpolation. A value within 0.001 of a key takes that key. Missing cells in a sparse grid count as zero shapes (zero offset for meshes, the origin as a deformer's absolute shape), and weights are not redistributed. Flag channels (flips) take the key not greater than the current value.
- Warp: inside the unit square, bilinear per cell or two triangles split along the (1,0)–(0,1) diagonal; outside, within [-2, 3]², extended with one layer of coarser virtual cells whose extra grid points take the average affine frame of the grid's four corners, interpolated by triangles; farther out, only the affine frame.
- Rotation deformers: the angle is the keyform angle plus the base angle. Under a rotation parent, the origin goes through the parent transform, and angle and scale compose with the parent's; 8000 when the parent flips vertically or the parent's scale is negative, the child frame turns an extra 180°; the parent's horizontal flip does not affect the child frame's direction; the deformer's own flip acts only on child coordinates. Under a warp parent, the frame stays rigid: the origin goes through the grid mapping, the angle turns with the grid's direction along v at 0.1 above the origin, and scale is inherited from the nearest rotation above the grid.
- Blend shapes: each binding blends linearly between neighboring keys by parameter (not snapped to keys); the neutral key and keys without a shape contribute nothing; the weight is multiplied by each limiting parameter's piecewise-linear factor. Deformer and channel shapes are target values, and what is added is "target − value at the default pose"; mesh shapes are target offsets, and what is added is "target offset − offset at the default pose".
- Opacity: each level (deformer, mesh) is clamped to [0, 1] before multiplying with its parent; part opacity is not included in mesh opacity.
- Glue: after all deformation, each vertex of a pair moves toward the other by its own weight times the strength.
- Draw order: each draw group sorts its children by draw order; a child group is keyed by its part's draw order resolved from channels and blend shapes (the static value without a part); a key is rounded after adding one thousandth, ties keep tree order; hidden meshes are not drawn. The rule is taken from the documentation of the engine's render-order interface and has not been compared frame by frame with native rendering.
Extension chunks in the file provide features beyond Cubism-equivalent evaluation, all off by default; p2l_set_advanced turns them on, and with them off, evaluation is bit-identical to core evaluation. Layout and semantics are in P2LRT_V2.md.
- Runtime skinning (
P2L_SKIN): meshes that bend at joints such as knees and elbows are sampled at keys when baking and interpolated along the chord between keys. The runtime blends the bone frames per vertex with two-bone linear blending and adds "current blend − blend interpolated between keys". This is zero at every key and turns the chord into an arc between keys. Weights are fitted to the baked keyforms when turned on; bones the skeleton trimmed or folded away ship in the file as virtual bones, and hosts can read bone transforms throughp2l_bone_*to attach objects. - Exact links (
P2L_EXACT_LINKS): pivots keyed on the same circle interpolate along the arc. - Live simulation (
P2L_SIM) and collision (P2L_COLLISION): baked cloth and hair run live with the editor's XPBD solver, replacing the baked pattern parameters and pendulums; colliders are circles or capsules along mesh vertices.
Verification: advanced_tests (an arm, links, virtual bones and a hanging mesh with known analytic solutions); p2lrt-conformance --advanced requires every sample to match the bake at skinning keys (all 0); --sim compares the solvers on random cloth trajectories from RuntimeConformanceTool.simulation, bit-identical to the editor (collision included); --sim-rig compares end to end with the baked simulation of the tml back hair: 0.21 pixels error over the first half second, 0.22 pixels on average over the whole run. The difference comes from floating-point differences of about 1e-4 pixels when the two sides evaluate the targets, which the cloth amplifies.
Implemented the way the Cubism runtime reads physics3.json, including its peculiar behaviors: inputs are normalized around the midpoint of the parameter range; with a fixed step (Fps), inputs are interpolated between frames and outputs between the last two steps; without a step limit, outputs lag one frame; translation reuses the already rotated x; only angle outputs carry scale, translation outputs having a scale of zero; accumulated time over 5 seconds is reset. Compared frame by frame with the editor's PhysicsEngine on random pendulum groups and sample models, the error is below 2e-5.
Three more Cubism runtime features: vertical (Y) input and output (PHYS version 2, see P2LRT_V2.md; neither the editor nor Cubism Editor produces vertical ones, and the physics3 export can write them), wind (p2l_physics_wind, added to the force on every particle, the same as physics3's Wind) and stabilization (p2l_physics_stabilize, which places each pendulum directly at its rest position along the force direction for the current pose and writes the outputs, the same as Cubism's Stabilization). With zero wind, results are bit-identical to before.
Curve segments are the same as in the editor (linear, Bezier (time control points restricted to the segment), stepped, inverse stepped); looping clips wrap by duration, and one-shot clips stop at the end. Player plays one clip at a time and crossfades on switching by the clips' fade times (1 second by default, cosine easing); a clip's weight is the product of its fade-in and fade-out factors (as in Cubism), every replaced clip fades out completely, and rapid successive switches do not drop clips still fading out.
Hosts can turn on: blinking (the EyeBlink role, once every 2–6 seconds, closing 0.1 s, holding 0.05 s, opening 0.15 s, multiplied onto the current value; p2l_blink_settings changes the interval and phase durations), breathing (Breath set to a sine within its range, with small swings added to AngleX/Y/Z and BodyAngleX), gaze (look_at(x, y) followed with critical damping, driving AngleX/Y, BodyAngleX, EyeBallX/Y, with AngleZ tilting by x·y) and lip sync (lip_sync(level), the LipSync parameter taking the larger value; p2l_lip_sync_samples uses the RMS of a block of audio samples times a gain as the opening). p2l_behavior_strength adjusts the breathing swing amplitude, the gaze turn amplitude and the follow speed. Roles are written when the editor compiles, according to which standard parameters exist; moc3's model3.json still writes only the EyeBlink and LipSync groups. Update order: motion clips → behaviors → physics → deformation.
One handle corresponds to one loaded model and holds the parameters, motion player and physics state. The typical flow: p2l_rig_load → set parameters (p2l_parameter_values / p2l_set_parameter) → p2l_update(dt) (motion, physics, deformation) or p2l_evaluate → read p2l_mesh_vertices, p2l_mesh_opacity, p2l_mesh_colors, and draw back to front by p2l_render_order, with textures from p2l_texture_png (p2l_texture_info gives the texture page kind: embedded PNG, KTX2 or a file next to the model). p2l_rig_load_ex can require chunk CRC checks, and p2l_format_support lists the supported versions and chunks. Expressions (p2l_expression) are layered after motion clips and before procedural behaviors; p2l_hit_test returns the hit area under a canvas point; p2l_mesh_user_data reads a mesh's user data; p2l_pose_show switches part exclusion groups (export setting pose_groups), the replaced parts fading out with p2l_update. p2l_parameter_values is the pose the host sets; every p2l_update starts from it again to layer motions, expressions, behaviors and physics, and the layered result is not written back, so blinking, breathing and additive expressions do not accumulate frame over frame; the values actually used this frame are read with p2l_parameter_current. Every function accepts a null handle; returned pointers stay valid until the handle is freed (pose data until the next evaluation).
Added in ABI 1.1 (all new functions; 1.0 functions behave the same, except that out-of-range indices now return a distinguishable value):
- Models and instances:
p2l_model_loadreads a file once into a read-only model that can be shared across threads, andp2l_rig_createcreates handles on it that each hold their own parameters, motion and physics state; handles already created keep the model afterp2l_model_free;p2l_rig_loadis the two in one. A handle cannot be used by two threads at once (not even for reading;p2l_bone_transformupdates the handle). - Staged updates and the host layer:
p2l_update_stages(dt, stages)runs only the selected stages (motion, expression, behavior, part pose, physics, live simulation, in unchanged order); unselected stages neither advance nor layer.p2l_set_parameter_override(index, value, weight)is the host layer: after motions and expressions and before behaviors and physics, it pulls a parameter toward the given value by weight (face tracking and the like), and physics still swings with it;p2l_parameter_valuesremains the bottom layer. - Parts and isolated groups:
p2l_part_*give the part id, name, parent part, flags and grouping (pass-through, sorted as a group, isolated).p2l_render_commandsis the draw order keeping isolated group boundaries (mesh indices;-2 - partopens a group,-1closes it); a group's blend mode, alpha compositing mode and masks (mask parts expanded to their meshes) come fromp2l_part_group/p2l_part_masks, and the evaluated group opacity and multiply/screen color fromp2l_part_composite: the static value combined with the channel overrides of that group in the draw tree, as in the editor.p2l_set_part_opacitysets the host's part opacity, which multiplies onto every mesh beneath it like the part pose.p2l_mesh_partgives a mesh's part, andp2l_mesh_alpha_blenda mesh's alpha compositing mode. - File info: parameter names and flags (looping, blend-shape parameter), snap values and the parameter panel (
PGUI: 2D joysticks, group tree, tags), the canvas origin and pixels per unit, motion clip names, groups, durations, frame rates, looping and fades, the clip playing, its current time and seeking (p2l_clip_seekmoves only the clip time, fade-in proceeds as usual), display names of expressions, hit areas and meshes, and generator info (META). - Other:
p2l_behavior_seedresets the blink random seed, and handles after the first in a process get their own seeds automatically; getters given out-of-range indices return NaN (numbers) or -1 (indices, modes), and a null handle still returns 0;p2l_allocallocates exactly by length and zeroes;p2l_texture_pngon a non-PNG texture page writes a length of 0;p2l_parameter_indexlooks up by the stringp2l_parameter_idgives.
Added in ABI 1.2 (likewise new functions only):
- Motion layers: up to
P2L_MAX_LAYERS(16) layers, each playing one clip at a time, stacked over lower layers in layer order;p2l_set_layer_weightsets the weight with which a layer overrides those below.p2l_play_layer(layer, clip, priority)starts only when the priority is not lower than that of the clip playing on that layer (0 once a clip ends or stops), matching the Cubism motion manager's priorities;p2l_playandp2l_clip_*act on layer 0. - Motion events and non-parameter curves: from
CEXT(see P2LRT_V2.md). Events passed in each update come fromp2l_event_count/p2l_event(text, layer, clip, time). Part opacity and overall opacity set by clips multiply onto meshes;p2l_evaluateevaluates only the host pose and so leaves them out; blink and lip-sync effects act on the corresponding role parameters. Curves with their own fades take weights by Cubism's rules. - Multiple expressions:
p2l_expression_add/p2l_expression_removelet several expressions take effect at once (each with its own fades, layered by start order), andp2l_expressions_playinglists the active ones;p2l_expressionstill keeps only one. - Overall opacity and color overrides:
p2l_set_opacitymultiplies onto every mesh (multiplied with the overall opacity set by clips;p2l_opacityreads the product);p2l_set_mesh_colors/p2l_set_part_colorsreplace the evaluated multiply/screen colors with host-given ones, a mesh's own setting taking precedence over the nearest setting on its part chain. - Physics and behaviors:
p2l_physics_wind,p2l_physics_stabilize,p2l_blink_settings,p2l_behavior_strength,p2l_lip_sync_samples; see "Physics" and "Procedural behaviors" above.
ABI 1.3 adds p2l_render and p2l_render_texture (see "Software rendering" below). ABI 1.4 adds:
- Change flags:
p2l_mesh_changesgives whether a mesh is drawn (in the draw order with opacity above 0), and whether its visibility, opacity, draw order value, position in the draw order, vertices or multiply/screen color changed since the previous evaluation, matching Cubism's dynamic flags, so hosts upload only what changed; after the first evaluation all flags are set. Every entry point that changes the pose (p2l_update,p2l_update_stages,p2l_evaluate,p2l_set_advanced,p2l_physics_stabilize) updates them when it finishes. - Logging:
p2l_set_logreceives load failures, handle invalidation and texture pages the renderer cannot decode. - Host allocator:
p2l_set_allocatormakes all of the runtime's later memory come from the host's allocate and free functions (size and alignment passed through); it must be called before any other part of the runtime is used, on a single thread, and returns false without changing anything once the runtime has already requested memory from the system. It is provided by thehost-allocatorfeature, on by default (off when the runtime is used as a Rust library, as by the Godot node). Measured with the C example: all 7570 allocations when rendering the tml sample go through the host allocator.
Writer side: the IR's Clip gains events and targetCurves, Curve gains fadeIn / fadeOut, and PhysicsSource gains Y; the p2lrt export writes CEXT and, when needed, PHYS version 2; the moc3 export's motion3 writes UserData, PartOpacity and Model (Opacity, EyeBlink, LipSync) curves and curve fades, and physics3 writes Y; cmo3 has no vertical physics and leaves out vertical inputs and outputs. The editor's motion editing does not yet offer a way to edit events and non-parameter curves; this data comes from the IR.
The drawing rules are written at the top of the header, with SoftwareRasterizer as the reference: textures are straight alpha and the rules compute on premultiplied colors; a fragment's color is first multiplied by the multiply color, then screened with c + s·a − c·s, and finally multiplied by opacity and mask coverage (the maximum texture alpha of the mask meshes at that point, inverted taking 1 − coverage; real-time players may approximate with an alpha ≥ 0.5 stencil); with culling on, only faces clockwise in canvas coordinates (y down) are drawn; Cubism's normal, additive and multiply follow its premultiplied formulas (additive and multiply keep the destination alpha), and extended modes follow the W3C compositing formulas; blend modes and alpha compositing modes have P2L_BLEND_* and P2L_ALPHA_* constants.
A panic inside the runtime during a call (a bug, or a model the reader accepted but cannot evaluate) does not unwind into the host: that call returns as for a null handle, the handle is invalidated, p2l_rig_failure gives the reason, and from then on it behaves like a null handle until p2l_rig_free; on a panic while loading (including the first evaluation), p2l_rig_load returns null and writes the reason. Panics in the WebAssembly build still abort the instance (that target does not support unwinding).
ABI version: the header's P2L_ABI_VERSION_MAJOR / P2L_ABI_VERSION_MINOR and the library's p2l_abi_version() (major << 16 | minor). Adding functions bumps the minor version; changing or removing functions bumps the major version; hosts check with P2L_ABI_COMPATIBLE(p2l_abi_version()), and a library without p2l_abi_version predates 1.0. p2l_version is only the library's build version. When loading the library, the JVM binding requires the same major version and a minor version no lower than the binding needs, and otherwise treats the runtime as absent; it throws when a handle becomes invalid after evaluation or update (so the software preview falls back to the editor evaluator, while the runtime preview backend and exports report an error). cargo test checks that the functions declared in the header match the functions the library exports one to one, and that the version numbers agree.
cargo test: one small-model unit test per evaluation rule (values from the probe results), plus warp and motion clip tests;format_testsassembles version 2 files chunk by chunk, covering compression, CRCs, the chunk directory and the various rejection cases, all array encodings, texture page kinds and the parameter panel.RuntimeConformanceTool(PSD2LIVE_TOOLS=1): generates reference poses for 80 random models (warp, rotation, nesting, sparse grids, blend shapes, Glue, channels, parts, mixed), the samples and local projects, plus physics trajectories;cargo run --release --bin p2lrt-conformance -- ../build/tools/runtime-conformance(runtime-physicsfor physics) compares case by case. Each case is written as version 2, version 1, and deflate- and zstd-compressed version 2; all four must read as the sameRigand give bit-identical poses. The vertex error limit is 0.02 pixels; poses beyond it are judged by their own condition number: the farthest any vertex moves when any parameter moves by a millionth of its range (sensitivity), and the error must not exceed twice that. Interpolation inside meshes uses double precision. Everything currently agrees; the only pose beyond 0.02 is one of blend-6 (0.10 pixels, with a sensitivity also of 0.10, the mesh's opacity at that point being 3e-5).
- GPU rendering is left to the host: the runtime provides geometry and attributes and draws masks and all compositing modes with
p2l_render(CPU software rendering, see the next section); GPU hosts draw by the same rules themselves (for the web target's drawing, see Export targets).
p2l_render (ABI 1.3) draws the latest evaluation into a host-supplied RGBA8 image, by the same rules as the reference rasterizer SoftwareRasterizer: bilinear premultiplied sampling, pixel centers with the top-left rule, masks by texture alpha (including inverted), isolated group layers, every color and alpha compositing mode, and culling. The transform is a canvas-to-image affine (with a null pointer, the canvas is fitted uniformly into the image's top left), the output is premultiplied or straight alpha, and it can draw over existing content. Embedded PNG texture pages are decoded by the runtime's own decoder (non-interlaced, all color types); KTX2 and external file pages get their pixels from the host through p2l_render_texture. It serves hosts without their own renderer (C/C++, engine plugins, thumbnails and checks) and runs pixel by pixel on the CPU. Verification: SoftwareRenderParityTest compares pixel by pixel with the reference rasterizer on models covering 90 compositing combinations, isolated groups (channel-driven opacity and color, mesh and part masks, inversion, nesting), masks, multiply/screen color and culling, at three poses and scales, with a maximum channel difference ≤ 1/255; deliberately breaking the conjoint overlap weight makes the test report a 77/255 difference.
See runtime/bindings/README.md:
- C/C++:
runtime/examples/c/render_example.cloads through a shared model, plays one second and writes a BMP withp2l_render;build_example.batcompiles it once as C and once as C++ with MSVC (/W4 /WX, no warnings from the header), and the two outputs are byte-identical. Unreal uses the same header and library as a third-party module. - .NET / Unity:
bindings/csharp/P2lNative.csholds all P/Invoke declarations generated from the header bygenerate.py(cargo testchecks it against the exported functions), andP2l.csprovides string helpers and the auto-releasingP2lRig;Smoke.cs, compiled as C# 6, resolves all 129 entry points withMarshal.Prelinkand loads, plays and renders the tml sample.bindings/unity/P2LCharacter.csdraws the model into aTexture2Deach frame with software rendering; it has only been type-checked against stubs of the Unity APIs it uses and has not run in Unity. - Android:
runtime/build_android.shbuildslibp2l_runtime.sowith the NDK's clang for arm64-v8a, armeabi-v7a and x86_64, each exporting all 129 functions. iOS must be built on a Mac and has not been verified here.
The web export target writes a ready-to-deploy folder: index.html, p2l.js (ES module P2LPlayer), p2l_runtime.wasm and the model. The player draws with WebGL by the header's compositing rules, covering every color and alpha blend combination and isolated groups: normal (over) and Cubism's additive and multiply use blend functions, and the other combinations first copy the backdrop within the mesh's bounding box to a texture and composite in a shader; isolated groups are drawn into a layer by p2l_render_commands and, on closing, composited with the group's mode, opacity, multiply/screen color and mask; masks go through the stencil buffer (clipped at texture alpha 0.5, inversion supported); meshes with culling on draw only front faces. WebPlayerCheckTool (PSD2LIVE_TOOLS=1) generates models covering 90 combinations and 16 isolated groups with per-pixel reference images, and headless Edge differs from the references by at most 1/255; the page offers motion and expression choices and a lip-sync slider, the gaze follows the pointer, and a click shows the hit area under it; when the file carries advanced mode data, the page offers a toggle. The WebAssembly build is committed to the repository as a resource; after changing the runtime, refresh it with ./gradlew :targets:web:updateWasm (needs rustup target add wasm32-unknown-unknown); a unit test checks that every function the player calls is exported by that build. The tml sample displays correctly in headless Edge.
runtime/godot/ is built with cargo build --release; the properties and methods of P2LCharacter are in its README. The node draws by the header's drawing rules: each mesh in its own canvas item sorted by render_commands, textures premultiplied on load, and a shader applying multiply/screen color, opacity and mask coverage before compositing by the color and alpha compositing modes — normal + over, Cubism additive/multiply (any alpha mode) and out use hardware blending, and the rest read the color beneath and compute in the shader. Isolated groups draw their content into an offscreen viewport (layer) the size of the target viewport and composite it at the group's place with the group's blend mode, alpha mode, opacity, multiply/screen color and mask, and can nest; back-buffer copies in Forward+ and Mobile lose alpha, so each item inside a layer that reads the backdrop gets its own viewport, which first copies the layer content so far and then reads it; outside groups, the screen is read and the backdrop is treated as opaque (an approximation under transparent windows). Masks are drawn into coverage viewports (three mask sets per viewport), meshes and layers read coverage at their own pixels, and inverted masks take 1 − coverage; where masks of the same set overlap, alpha adds (the reference takes the maximum). Meshes with culling on submit only front-facing triangles ((b − a) × (c − a) < 0 in canvas coordinates); masks are not culled. The feature models of RuntimeSamplesTool match the software rasterizer's reference images in Godot (difference ≤ 2/255, inverted masks included); GodotLayerSamplesTool (PSD2LIVE_TOOLS=1) generates flat-color models with expected values covering every color × alpha compositing mode (on an opaque background and inside isolated groups) as well as group color, opacity, blend mode, mesh/part masks, inverted masks and nesting, and Godot 4.7 under Forward+, Mobile and Compatibility differs by ≤ 1.2/255 at every sample point. Verification: the demo project's smoke_test.gd loads a model in headless mode and advances one second; --shot renders one frame in a window, and the tml and ds samples display correctly.