# PIXELFLOW-SCRIPT KNOWLEDGE BASE

## OVERVIEW

- Cross-crate layering lives in `crates/AGENTS.md`; below only script-local behavior and traps.
- Script graph-construction shell over `pixelflow-core`; not filter-runtime or source-runtime semantics layer.
- Single-file crate. `src/lib.rs` holds public API, Rhai sandbox, source rewrite, graph build, inline tests.
- `Cargo.toml` stays thin: deps only `pixelflow-core`, `rhai`, `semisafe`.

## STRUCTURE

- `ScriptValue` + `ScriptParameter` parse embedding or CLI `--set name=value` input.
- `ScriptEngine` owns logger + `FilterRegistry`; `ScriptGraph` wraps built `Graph` plus the active `MetadataSchema` captured during evaluation.
- `evaluate_script()` flow: empty check -> `normalize_source()` -> `rewrite_filter_syntax()` -> output-count guard -> parameter injection -> assigned-var declaration -> Rhai compile/run -> `output` clip extraction -> `GraphBuilder::set_output()`.
- `build_engine()` / `register_graph_api()` define whole script host surface: `none`, `is_none`, `blob`, `register_prop`, `prop`, `source`, `filter`.
- `source_from_options()` and `filter_*_from_options()` are coercion boundary from Rhai values into core `SourceRequest` / `FilterOptions`.
- Lower half of file is scanner/rewrite/parser helpers plus behavior-locking tests.

## WHERE TO LOOK

- API entry points and constructors: `ScriptValue`, `ScriptParameter`, `ScriptEngine`, `ScriptGraph` in `src/lib.rs`.
- Graph-state bridge and evaluation pipeline: `evaluate_script`, `push_parameters`, `declare_assigned_variables`, `GraphBuilder::set_output` path in `src/lib.rs`.
- Rhai engine limits and registered host functions: `build_engine` and `register_graph_api` in `src/lib.rs`.
- Metadata registration, source option coercion, filter option coercion: `register_prop`, `source_from_options`, `filter_*_from_options` in `src/lib.rs`.
- Source normalization and sugar rewrite scanner: `normalize_source`, `rewrite_filter_syntax`, scanner helpers in `src/lib.rs`.
- Output-assignment scan, parameter parsing, diagnostic mapping: `count_output_assignments`, `ScriptParameter::parse_set`, `parse_error`, `eval_error` in `src/lib.rs`.
- Contract tests for sugar, namespaces, output rules, source preservation: `src/lib.rs` `#[cfg(test)]` block.

## CONVENTIONS

- Keep language sugar as source-to-source rewrite before Rhai compile. Do not split same behavior across CLI or runtime.
- Treat `output` as single top-level final clip assignment. Missing, duplicate, or non-clip `output` is hard error.
- `ScriptParameter::parse_set()` accepts bool, int, finite float, rational, else string. Names must pass script identifier rules.
- `source` options accept string, bool, int, rational. Special case: `fps` string parses as rational.
- `filter` options accept none, string, bool, int, finite float, array, rational, blob. Nested maps intentionally rejected.
- Filter resolution and plan selection always go through `FilterRegistry`. This crate rewrites names and coerces values; official filter semantics belong in `pixelflow-filters`, not here.
- Graph-building boundary only. Source placeholder media exists so graph validation can run before real indexing or probing.
- Keep Rhai sandbox tight: raw engine, strict variables, no modules/imports/files/process/network helpers unless script boundary changes on purpose.

## ANTI-PATTERNS

- Do not add std/plugin filter behavior here when planner in `pixelflow-filters` or plugin registry can own it.
- Do not add script parsing, sugar, or output rules in `pixelflow-cli`; CLI should pass source, params, logger, registry.
- Do not bypass `normalize_source()` or `rewrite_filter_syntax()` with ad-hoc parser branches.
- Do not treat placeholder source media (`1x1`, `yuv420p8`, unknown frame count/rate) as validated source truth.
- Do not loosen structured diagnostics like `script.invalid_parameter`, `script.invalid_argument`, `graph.missing_output`, `graph.invalid_output`, `graph.multiple_outputs`.
- Do not accept arbitrary filter names or option keys; identifier validation intentional.

## NOTES

- Supported sugar forms: `clip.filter(...)`, `clip.some_filter(...)`, `clip.std.resize(...)`, `clip.plugin.acme.blur(...)`, `std.resize(clip, ...)`, `plugin.acme.blur(clip, ...)`.
- Rewrite scanner ignores comments, strings, comparisons, nested expressions, spacing quirks, and no-arg method calls when counting/rebinding syntax.
- Multi-input filters use `filter([clip1, clip2, ...], name, options)`; every array entry must be `Clip`.
- `register_prop()` extends metadata schema at script time so metadata-validating filters can plan.
- `prop(...)` is runtime-backed despite script-time syntax: it resolves one frame metadata value through an injected resolver, and callers must already have built the clip prefix they want to inspect.
- `validation_plan()` can succeed before indexing; reachability and graph shape live here, real source validation elsewhere.
- Inline tests are contract map. Extend tests first when changing rewrite, namespace dispatch, output rules, or value coercion.
