Guidance for AI agents working on the pure-C11 EXR loader/writer under
include/exr.h + src/*.c (private decls in src/exr_internal.h). The legacy
v1 tinyexr.h at the repo root is a dependency of older tests — leave it
untouched.
- DWA (DWAA / DWAB) is intentionally NOT supported — do not implement it.
We do not plan to support the lossy DCT DWA codecs. Leave
EXR_COMPRESSION_DWAA/EXR_COMPRESSION_DWABreturningEXR_ERROR_UNSUPPORTEDin the codec dispatch; do not add a DCT/zigzag/AC-DC decoder. Do not spend effort here. - HTJ2K / JPH (
src/exr_jph.c,EXR_COMPRESSION_HTJ2K*) is owned separately — don't refactor it without coordination.
make lib— buildbuild/libtinyexr3.a(-std=c11 -Wall -Wextra -Werror).make c11-gate— strict pure-C11 syntax gate over allsrc/*.c(must stay green).make test-c— unit tests under ASan+UBSan (note: runs withdetect_leaks=0).make fuzz-corpus— replaytest/unit/regression/*under ASan+UBSan+LSan (this is what catches error-path leaks; keep it green).make fuzz-corpus-asan— same corpus replay withASAN_OPTIONS=detect_leaks=0; use only in ptrace/sandboxed local sessions where LSan aborts with "LeakSanitizer does not work under ptrace". It is a crash/ASan/UBSan fallback, not a replacement for the LSan gate.make fuzz— clang/libFuzzer coverage-guided target (build/fuzz_v3).make bench— codec + SIMD-kernel throughput.
Validation method: cross-check against the legacy v1 loader
(LoadEXRImageFromFile / LoadDeepEXR) and OpenEXR CLI tools
(exrheader, exrmaketiled, exrmultipart). oiiotool is broken here
(missing libboost). For lossy codecs the bar is "OpenEXR reads my output and my
own decode matches"; for lossless it is byte-identical.
Audit new commits for accidentally-committed credentials with both
gitleaks and trufflehog. Tools default to ~/go/bin/ (trufflehog lives there;
gitleaks may also be on PATH). Both must exit 0 (no findings) before pushing.
# Scan only the new commits: RANGE = <last-known-clean>..HEAD (e.g. a tag or the
# upstream commit you branched from). Use --all / drop --log-opts for a full sweep.
RANGE=origin/release..HEAD
# gitleaks: history scan, redact matches, fail (non-zero) on any leak.
gitleaks detect --source . --log-opts="$RANGE" --no-banner --redact
# trufflehog: scan the same range; --fail makes it exit non-zero on a finding.
~/go/bin/trufflehog git "file://$(pwd)" --since-commit "${RANGE%%..*}" \
--results=verified,unknown --no-update --failBoth should report "no leaks found" / verified_secrets: 0 and exit 0. If either
flags something, do not push — rewrite history to drop the secret and rotate it.
.gitleaks.toml (auto-loaded from the repo root) allowlists the vendored
deps/zstd/ amalgamation, whose xxHash key-mixing intrinsics (key_lo/key_hi)
trip the generic-api-key heuristic — those are upstream constants, not secrets.
Keep first-party code out of the allowlist.
- Every new file gets the BSD-3-Clause header. Ported code keeps upstream
attribution (fpng = public domain, fpnge = Apache-2.0; see
NOTICE). - All hostile-input arithmetic goes through
exr_mul_ovf/exr_add_ovf. Error paths must leave outputs owning nothing (exr_part_freeon failure). - SIMD kernels use
__attribute__((target(...)))+ a runtime CPUID vtable so everything compiles at baseline; scalar fallback is always present and is the source of truth (SIMD must be bit-identical).
Build / test:
make tocio-c11-gate— C11 strict gate for allsandbox/tocio/src/*.cmake tocio-freestanding-gate— freestanding gate (nm-scans for libm symbols)make tocio-test— unit tests (ASan+UBSan)make wasm-tocio— WASM/emscripten build (build/tocio.mjs+.wasm)make wasm-tocio-test— WASM smoke check via Node (set PATH to emsdk's node)make tocio-wasm— WASM build of core
Phase 1 (FixedFunction) ██████████████░░░░ 70% 106 tests
Phase 2 (view_transform) ████████████████░░ 80% 118 tests
Phase 3 (CDL inverse) ████████████████░░ 80% 118 tests
Phase 4 (YAML keys) ████████████████░░ 80% 127 tests
Phase 5 (looks/aliases) ████████████████░░ 80% 127 tests
Phase 6 (file LUTs) ████████████████░░ 80% 127 tests
Done:
toc_ff_styleenum extended to 22 values intocio.h(invert-pair pattern)- All math kernels in
toc_builtins.c:- ACES Glow 03/10 fwd+inv (sigmoid, saturation-weighted)
- ACES Red Mod 03/10 fwd+inv (B-spline hue lobe, quadratic inv)
- ACES DarkToDim 10 fwd+inv (luma gamma scale)
- ACES GamutComp 13 fwd+inv (per-axis distance compression, 7 params)
- RGB_TO_HSV / HSV_TO_RGB (extended range: negative V, S>1)
- XYZ_TO/TO_xyY, XYZ_TO/TO_uvY, XYZ_TO/TO_LUV (D65)
- Freestanding
ff_atan2f(minimax rational) replaces libcatan2f toc_lower_fixedfuncstring parser extended for all styles + params- AOT-C codegen: helper
staticsource strings + per-opcase TOC_OP_FIXEDFUNC:emit - GLSL codegen: per-style inline math emit for all styles (RedMod inv & GamutComp13 fall back to CPU)
- Round-trip tests in
toc_test.cfor all 10 style pairs
Remaining:
- Verify GLSL output compiles in a real shader (glslangValidator not always available)
Done:
toc_cfg_find_view_transform— look up ViewTransform by name fromview_transformtop-level seqtoc_processor_from_display_viewhandles three cases:- Simple views (colorspace key) → src → colorspace
- VT views (view_transform key) → src → reference → vt.from_reference → display_colorspace
- Both support optional looks
- Config introspection:
toc_config_num_view_transforms,toc_config_view_transform_name
Done:
- CDL inverse decomposed at lowering time into basic ops:
- Inverse saturation → MatrixTransform
- Inverse power → ExponentTransform
- Inverse (slope, offset) → RangeTransform
- Forward CDL remains a single TOC_OP_CDL (all backends handle it)
- Round-trip test cdlin↔lin passes within 2e-3
Done:
view_transformparsing (seq of ViewTransform nodes with from_reference/to_reference)looksparsing (seq of Look nodes with name/process_space/transform/inverse_transform)active_displays/active_viewsparsing (seq of names)- Config introspection:
toc_config_num_looks,toc_config_look_name - Config introspection:
toc_config_num_active_displays,toc_config_active_display_name
Done:
- Views can reference looks via comma-separated
lookskey toc_cfg_view_looksparses the comma-separated list- Processor applies each look: convert to process_space → apply transform → convert back
- Color space aliases already handled by
name_matchesintoc_config.c - Roles already handled by
toc_cfg_resolve_role
Done:
.cube1D/3D (toc_lutfile.c).spi1d/.spi3d(toc_lutfile.c).clf(Common LUT Format, XML-based ACES standard) (toc_clf.c)- Supports: Matrix, Range, Exponent, Log, LUT1D, LUT3D
- Minimal single-pass XML parser (no DOM tree)
- Content sniffing via
<?xmlor<ProcessListprefix
- FileTransform resolution via
toc_file_readerhook - Round-trip tests for all formats
toc_builtins.cis freestanding core — never use<math.h>or libm symbols- Every new FixedFunction style must have an inverse pair (
_INV = forward+1) - GamutComp13 is complex — AOT-C and GLSL codegens fall back to interpreter for it
- Decompose inverse ops at lowering time (not in backends) for backend-agnostic support