- nodeparamview: add visited set to get_distance_between_nodes, fixing
unbounded recursion (stack overflow) when the node graph has a cycle
- viewerdisplay: give texture_ a consistent owner via assign_texture();
borrowed queue textures are now retained, created ones freed, fixing
a dangling pointer that corrupted the heap and crashed in the GL driver
- playbackcache: resignal_requests() iterates a copy, handlers may
clear_request_range() while iterating (ASan container-overflow)
- preview C API: preview request ticket lambdas captured the request
state raw; after oakengine_preview_request_free the ticket outlived
the request and the finished callback wrote into freed memory
(heap-use-after-free). The finished flag is now a shared_ptr captured
weakly by the callbacks
- playback: oak_playback_frame regains a timestamp (num/den) filled
from olive::Frame; the viewer queue append no longer uses Rational()
for every frame, which made append_timewise drop all but the first
frame and froze the picture during playback
- mainwindow: open_node_in_viewer refuses sequence nodes; sequences
already have the Sequence Viewer, and saved layouts could otherwise
resurrect a redundant floating Viewer bound to the sequence
The full pure-C facade used by the app: node/project/timeline/viewer/
undo/task/events/serializer/playback/preview/renderer/gizmo/color/
audio/footage/proxy/encoding/exporter/config/disk/ipc/plugin/worker
families, plus undo-group semantics, display renderer handles,
NodeFactory accessors, and per-family pure-C engine tests.
- clip-level loop mode (off/loop/clamp), undoable
- per-channel audio levels as one-frame RMS at a timestamp
- waveform min/max buckets over a footage range, rendered on demand
- two real fixes uncovered by this family: conform completion signals
were starved by msleep-only waits (audio renders always came back
empty), and incomplete tickets from conform-pending renders are now
retried until the conform is ready