From 4ba6fc15eb2fc8d12e55fbf6008545523cace67c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 28 Aug 2026 13:12:24 +0000 Subject: [PATCH 1/2] Add honest Kotoba v1 OCIO config-header binding Parse ocio_profile_version and the raw colorspace name from a 71-byte fixture. No color transform pipeline, no FFI, i64-v1 wasm32 only. Co-authored-by: Jun Kawasaki <04.feasts_minded@icloud.com> --- .github/workflows/kotoba.yml | 34 ++++++ kotoba/.gitignore | 3 + kotoba/README.md | 57 ++++++++++ kotoba/checks.sh | 182 ++++++++++++++++++++++++++++++++ kotoba/fixtures/tiny.ocio | 4 + kotoba/ocio.kotoba | 195 +++++++++++++++++++++++++++++++++++ 6 files changed, 475 insertions(+) create mode 100644 .github/workflows/kotoba.yml create mode 100644 kotoba/.gitignore create mode 100644 kotoba/README.md create mode 100755 kotoba/checks.sh create mode 100644 kotoba/fixtures/tiny.ocio create mode 100644 kotoba/ocio.kotoba diff --git a/.github/workflows/kotoba.yml b/.github/workflows/kotoba.yml new file mode 100644 index 000000000..b6f151020 --- /dev/null +++ b/.github/workflows/kotoba.yml @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright Contributors to the OpenColorIO Project. +# +# Fork-only Kotoba v1 checks. Not part of AcademySoftwareFoundation/OpenColorIO CI. + +name: kotoba v1 + +on: + push: + paths: + - "kotoba/**" + - ".github/workflows/kotoba.yml" + pull_request: + paths: + - "kotoba/**" + - ".github/workflows/kotoba.yml" + +permissions: + contents: read + +jobs: + ocio-header: + name: config header fields + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "22" + + - name: Run kotoba/checks.sh + run: bash kotoba/checks.sh diff --git a/kotoba/.gitignore b/kotoba/.gitignore new file mode 100644 index 000000000..6ca046993 --- /dev/null +++ b/kotoba/.gitignore @@ -0,0 +1,3 @@ +*.wasm +*.compile.json +.kotoba-cli/ diff --git a/kotoba/README.md b/kotoba/README.md new file mode 100644 index 000000000..45aca805e --- /dev/null +++ b/kotoba/README.md @@ -0,0 +1,57 @@ +# Kotoba v1 — OCIO config header + +This directory is a first-class language tree on the `kotoba-lang/OpenColorIO` +fork, next to `src/OpenColorIO` and `src/bindings/{java,python}`. It is **not** +part of AcademySoftwareFoundation/OpenColorIO upstream. + +## Honest scope + +Kotoba binding **v1** parses only: + +- the config header key `ocio_profile_version:` (the field `OCIOYaml.cpp` + requires before it will load a config) +- the **major** profile version as an ASCII digit, and **minor** `0` when + the token has no `.` (same split as `OCIOYaml.cpp`) +- the ColorSpace **name** `raw` on the vendored fixture + +from `fixtures/tiny.ocio` (71 bytes). That file is the version line plus +the `colorspaces` / `!` / `name: raw` lines from the built-in +raw profile in `src/OpenColorIO/Config.cpp` (`INTERNAL_RAW_PROFILE`), +with the float-bearing middle (roles, displays, `bitdepth: 32f`) dropped. +It is enough to identify those fields and is **not** a complete OCIO +config. + +This is **not** a color engine and **not** a replacement for +libOpenColorIO. It does not implement processors, GPU/CPU paths, looks, +viewing rules, config merge, or a LUT/transform pipeline. It is **not +robotics-ready**. + +The interesting OCIO surface (matrices, LUT samples, luma coefficients, +transfer functions, `bitdepth: 32f`) is IEEE-float. Kotoba 0.7.2 wasm32 is +**i64-v1** — no IEEE floats — so this tree stays on integer/byte header +fields and says so. + +## Language constraints + +- Kotoba CLI **0.7.2** +- `kotoba compile --target wasm` → `wasm32-kotoba-v1` +- value profile **i64-v1** (no IEEE floats) +- no FFI / no host imports + +`ocio.kotoba` embeds the fixture as integer bytes and uses only `+`, `*`, +`if`, `=`, `<`, and `and`. `main` returns a packed i64 of the parsed fields. + +## Checks + +`checks.sh` compiles with Kotoba 0.7.2, runs the wasm32 module, and +compares the packed result to fields read from the fixture bytes. It does +not invent pass/fail. + +```sh +# requires kotoba 0.7.2 on PATH, or downloads the linux-amd64 0.7.2 CLI +./checks.sh +``` + +## Operator + +awai.network / Ryo Awai diff --git a/kotoba/checks.sh b/kotoba/checks.sh new file mode 100755 index 000000000..76f42da43 --- /dev/null +++ b/kotoba/checks.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: BSD-3-Clause +# Copyright Contributors to the OpenColorIO Project. +# +# Compile ocio.kotoba with Kotoba 0.7.2 (wasm32, i64-v1) and assert +# config header fields against the vendored fixture. +# Fail closed. Do not print success unless every comparison ran. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")" && pwd)" +FIXTURE="$ROOT/fixtures/tiny.ocio" +SRC="$ROOT/ocio.kotoba" +WORKDIR="$(mktemp -d "${TMPDIR:-/tmp}/kotoba-ocio-v1.XXXXXX")" +trap 'rm -rf "$WORKDIR"' EXIT + +KOTOBA_VERSION="0.7.2" +KOTOBA_TARBALL="kotoba-linux-amd64.tar.gz" +# sha256 of https://github.com/kotoba-lang/kotoba/releases/download/v0.7.2/kotoba-linux-amd64.tar.gz +KOTOBA_SHA256="95e225461e1b8a21849b251e8c8b654693d2c8a516b258532771651e978e1977" + +fail() { + printf 'kotoba/checks.sh: %s\n' "$*" >&2 + exit 1 +} + +command -v python3 >/dev/null 2>&1 || fail "python3 is required" +command -v node >/dev/null 2>&1 || fail "node is required to run the wasm32 module" + +if [[ -n "${KOTOBA:-}" ]]; then + KOTOBA_BIN="${KOTOBA}" + [[ -x "${KOTOBA_BIN}" ]] || fail "KOTOBA=${KOTOBA_BIN} is not executable" +elif command -v kotoba >/dev/null 2>&1; then + KOTOBA_BIN="$(command -v kotoba)" +else + uname_s="$(uname -s)" + uname_m="$(uname -m)" + if [[ "${uname_s}" != "Linux" || "${uname_m}" != "x86_64" ]]; then + fail "kotoba is not on PATH (need CLI 0.7.2); automatic install is linux-amd64 only (this host is ${uname_s}/${uname_m})" + fi + command -v curl >/dev/null 2>&1 || fail "curl is required to fetch Kotoba ${KOTOBA_VERSION}" + cache="${ROOT}/.kotoba-cli/${KOTOBA_VERSION}" + mkdir -p "${cache}" + archive="${cache}/${KOTOBA_TARBALL}" + if [[ ! -x "${cache}/kotoba" ]]; then + url="https://github.com/kotoba-lang/kotoba/releases/download/v${KOTOBA_VERSION}/${KOTOBA_TARBALL}" + printf 'downloading Kotoba %s from %s\n' "${KOTOBA_VERSION}" "${url}" + curl -fsSL -o "${archive}" "${url}" + got="$(sha256sum "${archive}" | awk '{print $1}')" + if [[ "${got}" != "${KOTOBA_SHA256}" ]]; then + fail "checksum mismatch for ${KOTOBA_TARBALL}: got ${got} expected ${KOTOBA_SHA256}" + fi + tar -xzf "${archive}" -C "${cache}" kotoba + fi + KOTOBA_BIN="${cache}/kotoba" + [[ -x "${KOTOBA_BIN}" ]] || fail "extracted kotoba binary missing" +fi + +printf 'kotoba binary: %s\n' "$KOTOBA_BIN" + +CURRENT_LINK="${KOTOBA_HOME:-$HOME/.local/share/kotoba}/current" +if [ -L "$CURRENT_LINK" ]; then + INSTALLED="$(readlink "$CURRENT_LINK")" + printf 'kotoba install current: %s\n' "$INSTALLED" + if [ "$INSTALLED" != "v0.7.2" ]; then + fail "refusing kotoba $INSTALLED (need v0.7.2)" + fi +fi + +[ -f "$FIXTURE" ] || fail "missing fixture $FIXTURE" +[ -f "$SRC" ] || fail "missing module $SRC" + +# Independent field read from the fixture. These numbers come from the +# file bytes, not from the .kotoba source. Version split matches OCIOYaml.cpp. +eval "$(python3 - "$FIXTURE" "$SRC" <<'PY' +import sys +from pathlib import Path + +fixture = Path(sys.argv[1]) +src = Path(sys.argv[2]).read_text() +raw = fixture.read_bytes() +key = b"ocio_profile_version:" +if not raw.startswith(key): + sys.exit("fixture does not start with ocio_profile_version:") +if raw[21:24] != b" 2\n": + sys.exit("fixture version token is not a single ASCII digit 2") +if b"name: raw" not in raw: + sys.exit("fixture has no colorspace name field 'name: raw'") +if raw[67:70] != b"raw": + sys.exit("fixture colorspace name bytes at 67:70 are not raw") +for i, b in enumerate(raw): + needle = f"(= i {i}) {b}" + if needle not in src: + sys.exit(f"ocio.kotoba is missing fixture byte {i} = {b}") +# Same split as OCIOYaml.cpp load(Config): one token => minor 0. +version = raw[len(key):].split(b"\n", 1)[0].strip().decode("ascii") +parts = version.split(".") +if len(parts) == 1: + major = int(parts[0]) + minor = 0 +elif len(parts) == 2: + major = int(parts[0]) + minor = int(parts[1]) +else: + sys.exit(f"unusable ocio_profile_version token {version!r}") +name_ok = 1 +packed = 1 + 10 * major + 100 * minor + 1000 * name_ok +print(f"FIXTURE_LEN={len(raw)}") +print(f"FIELD_MAJOR={major}") +print(f"FIELD_MINOR={minor}") +print(f"FIELD_NAME_OK={name_ok}") +print(f"PACKED_EXPECT={packed}") +print(f"EMBEDDED_BYTES={len(raw)}") +PY +)" + +printf 'fixture: %s (%s bytes)\n' "$FIXTURE" "$FIXTURE_LEN" +printf 'fixture fields: major=%s minor=%s name_ok=%s\n' \ + "$FIELD_MAJOR" "$FIELD_MINOR" "$FIELD_NAME_OK" +printf 'embedded fixture bytes in ocio.kotoba: %s\n' "$EMBEDDED_BYTES" +printf 'packed expect (from fixture bytes): %s\n' "$PACKED_EXPECT" + +COMPILE_JSON="$WORKDIR/compile.json" +WASM="$WORKDIR/ocio.wasm" +set +e +"$KOTOBA_BIN" compile "$SRC" --target wasm -o "$WASM" --json >"$COMPILE_JSON" 2>"$WORKDIR/compile.err" +compile_rc=$? +set -e +if [ "$compile_rc" -ne 0 ]; then + cat "$COMPILE_JSON" "$WORKDIR/compile.err" >&2 || true + fail "kotoba compile failed (exit $compile_rc)" +fi + +python3 - "$COMPILE_JSON" "$WASM" <<'PY' +import json +import sys +from pathlib import Path + +report = json.loads(Path(sys.argv[1]).read_text()) +wasm = Path(sys.argv[2]) +if report.get("kotoba.cli/ok?") is not True: + sys.exit(f"compile JSON ok? is {report.get('kotoba.cli/ok?')!r}") +if report.get("kotoba.cli/code") != "emitted": + sys.exit(f"compile JSON code is {report.get('kotoba.cli/code')!r}") +data = report.get("kotoba.cli/data") or {} +profile = data.get("value-profile") +compat = data.get("compatibility") or {} +target = compat.get("target") +features = data.get("wasm-features") or [] +if profile != "i64-v1": + sys.exit(f"value-profile {profile!r} is not i64-v1") +if target != "wasm32-kotoba-v1": + sys.exit(f"target {target!r} is not wasm32-kotoba-v1") +blocked = [f for f in features if f in ("simd", "floats", "float", "nontrapping-fptoint")] +if blocked: + sys.exit(f"unexpected floating/SIMD wasm features: {blocked}") +if not wasm.is_file() or wasm.stat().st_size == 0: + sys.exit("compile did not write a wasm artifact") +magic = wasm.read_bytes()[:4] +if magic != b"\x00asm": + sys.exit(f"artifact magic {magic!r} is not wasm") +print(f"compile: value-profile={profile} target={target} wasm-features={features} bytes={wasm.stat().st_size}") +PY + +GOT="$(node --input-type=module - "$WASM" <<'JS' +import fs from "node:fs"; +const wasm = fs.readFileSync(process.argv[2]); +const { instance } = await WebAssembly.instantiate(wasm); +if (!instance.exports.main) { + throw new Error("wasm module has no exported main"); +} +const value = instance.exports.main(); +const n = typeof value === "bigint" ? value : BigInt(value); +process.stdout.write(n.toString()); +JS +)" + +printf 'wasm main returned: %s\n' "$GOT" +if [ "$GOT" != "$PACKED_EXPECT" ]; then + fail "packed result $GOT != fixture-derived $PACKED_EXPECT" +fi + +printf 'kotoba/checks.sh: compile i64-v1 wasm32 and fixture fields matched\n' diff --git a/kotoba/fixtures/tiny.ocio b/kotoba/fixtures/tiny.ocio new file mode 100644 index 000000000..2e1db1a43 --- /dev/null +++ b/kotoba/fixtures/tiny.ocio @@ -0,0 +1,4 @@ +ocio_profile_version: 2 +colorspaces: + - ! + name: raw diff --git a/kotoba/ocio.kotoba b/kotoba/ocio.kotoba new file mode 100644 index 000000000..adcfbef30 --- /dev/null +++ b/kotoba/ocio.kotoba @@ -0,0 +1,195 @@ +; SPDX-License-Identifier: BSD-3-Clause +;; Copyright Contributors to the OpenColorIO Project. +;; +;; Honest Kotoba v1: OCIO config header (profile version + colorspace name). +;; wasm32, i64-v1, no FFI, no IEEE floats. Not a color transform pipeline. +;; Fixture bytes match fixtures/tiny.ocio (version + colorspace name +;; lines from Config.cpp INTERNAL_RAW_PROFILE; no transform pipeline). + +(ns ocio) + +(defn fixture-len [] + 71) + +(defn fixture-byte-0 [i] + (if (= i 0) 111 + (if (= i 1) 99 + (if (= i 2) 105 + (if (= i 3) 111 + (if (= i 4) 95 + (if (= i 5) 112 + (if (= i 6) 114 + (if (= i 7) 111 + 0))))))))) +(defn fixture-byte-8 [i] + (if (= i 8) 102 + (if (= i 9) 105 + (if (= i 10) 108 + (if (= i 11) 101 + (if (= i 12) 95 + (if (= i 13) 118 + (if (= i 14) 101 + (if (= i 15) 114 + 0))))))))) +(defn fixture-byte-16 [i] + (if (= i 16) 115 + (if (= i 17) 105 + (if (= i 18) 111 + (if (= i 19) 110 + (if (= i 20) 58 + (if (= i 21) 32 + (if (= i 22) 50 + (if (= i 23) 10 + 0))))))))) +(defn fixture-byte-24 [i] + (if (= i 24) 99 + (if (= i 25) 111 + (if (= i 26) 108 + (if (= i 27) 111 + (if (= i 28) 114 + (if (= i 29) 115 + (if (= i 30) 112 + (if (= i 31) 97 + 0))))))))) +(defn fixture-byte-32 [i] + (if (= i 32) 99 + (if (= i 33) 101 + (if (= i 34) 115 + (if (= i 35) 58 + (if (= i 36) 10 + (if (= i 37) 32 + (if (= i 38) 32 + (if (= i 39) 45 + 0))))))))) +(defn fixture-byte-40 [i] + (if (= i 40) 32 + (if (= i 41) 33 + (if (= i 42) 60 + (if (= i 43) 67 + (if (= i 44) 111 + (if (= i 45) 108 + (if (= i 46) 111 + (if (= i 47) 114 + 0))))))))) +(defn fixture-byte-48 [i] + (if (= i 48) 83 + (if (= i 49) 112 + (if (= i 50) 97 + (if (= i 51) 99 + (if (= i 52) 101 + (if (= i 53) 62 + (if (= i 54) 10 + (if (= i 55) 32 + 0))))))))) +(defn fixture-byte-56 [i] + (if (= i 56) 32 + (if (= i 57) 32 + (if (= i 58) 32 + (if (= i 59) 32 + (if (= i 60) 32 + (if (= i 61) 110 + (if (= i 62) 97 + (if (= i 63) 109 + 0))))))))) +(defn fixture-byte-64 [i] + (if (= i 64) 101 + (if (= i 65) 58 + (if (= i 66) 32 + (if (= i 67) 114 + (if (= i 68) 97 + (if (= i 69) 119 + (if (= i 70) 10 + 0)))))))) + +(defn fixture-byte [i] + (if (< i 8) + (fixture-byte-0 i) + (if (< i 16) + (fixture-byte-8 i) + (if (< i 24) + (fixture-byte-16 i) + (if (< i 32) + (fixture-byte-24 i) + (if (< i 40) + (fixture-byte-32 i) + (if (< i 48) + (fixture-byte-40 i) + (if (< i 56) + (fixture-byte-48 i) + (if (< i 64) + (fixture-byte-56 i) + (if (< i 71) + (fixture-byte-64 i) + 0)))))))))) + +;; Identifying header key used by OCIOYaml.cpp load(Config): "ocio_profile_version" +(defn header-key-ok [] + (and (= (fixture-byte 0) 111) + (= (fixture-byte 1) 99) + (= (fixture-byte 2) 105) + (= (fixture-byte 3) 111) + (= (fixture-byte 4) 95) + (= (fixture-byte 5) 112) + (= (fixture-byte 6) 114) + (= (fixture-byte 7) 111) + (= (fixture-byte 8) 102) + (= (fixture-byte 9) 105) + (= (fixture-byte 10) 108) + (= (fixture-byte 11) 101) + (= (fixture-byte 12) 95) + (= (fixture-byte 13) 118) + (= (fixture-byte 14) 101) + (= (fixture-byte 15) 114) + (= (fixture-byte 16) 115) + (= (fixture-byte 17) 105) + (= (fixture-byte 18) 111) + (= (fixture-byte 19) 110) + (= (fixture-byte 20) 58))) + +;; ASCII '0'..'9' -> 0..9. Color matrices / LUT samples stay out (IEEE float). +(defn ascii-digit [b] + (if (= b 48) 0 + (if (= b 49) 1 + (if (= b 50) 2 + (if (= b 51) 3 + (if (= b 52) 4 + (if (= b 53) 5 + (if (= b 54) 6 + (if (= b 55) 7 + (if (= b 56) 8 + (if (= b 57) 9 + -1))))))))))) + +;; "ocio_profile_version: 2\n" — one token, so minor is 0 (OCIOYaml Split on '.') +(defn profile-major [] + (if (and (header-key-ok) + (= (fixture-byte 21) 32) + (= (fixture-byte 23) 10)) + (ascii-digit (fixture-byte 22)) + -1)) + +(defn profile-minor [] + (if (and (header-key-ok) (= (fixture-byte 23) 10)) + 0 + -1)) + +;; ColorSpace name "raw" at the fixture's name field (INTERNAL_RAW_PROFILE). +(defn colorspace-raw-ok [] + (and (= (fixture-byte 61) 110) + (= (fixture-byte 62) 97) + (= (fixture-byte 63) 109) + (= (fixture-byte 64) 101) + (= (fixture-byte 65) 58) + (= (fixture-byte 66) 32) + (= (fixture-byte 67) 114) + (= (fixture-byte 68) 97) + (= (fixture-byte 69) 119))) + +;; Packed i64: 1 + 10*major + 100*minor + 1000*name_ok +(defn main [] + (if (header-key-ok) + (+ 1 + (* (profile-major) 10) + (* (profile-minor) 100) + (* (if (colorspace-raw-ok) 1 0) 1000)) + 0)) From 1b52582563480cfeddf67b87174524de96d04e17 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 28 Aug 2026 13:47:55 +0000 Subject: [PATCH 2/2] QA hold: path-ignore native CI on kotoba-only changes Skip the VFX/macOS/GPU matrix when only kotoba/** or kotoba*.yml change. README drops unverified wasm/packed claims; fixture stays 71B. Co-authored-by: Jun Kawasaki <04.feasts_minded@icloud.com> --- .github/workflows/ci_workflow.yml | 7 +++++++ .github/workflows/gpu_workflow.yml | 4 ++++ kotoba/README.md | 9 ++++++--- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_workflow.yml b/.github/workflows/ci_workflow.yml index 68d02377a..cdade8a9e 100644 --- a/.github/workflows/ci_workflow.yml +++ b/.github/workflows/ci_workflow.yml @@ -16,6 +16,10 @@ on: tags-ignore: - v0.* - v1.* + # Fork-only Kotoba tree: do not enqueue the VFX/macOS/Windows matrix. + paths-ignore: + - "kotoba/**" + - ".github/workflows/kotoba*.yml" pull_request: branches-ignore: - RB-0.* @@ -24,6 +28,9 @@ on: tags-ignore: - v0.* - v1.* + paths-ignore: + - "kotoba/**" + - ".github/workflows/kotoba*.yml" jobs: # Linux jobs run in Docker containers, so the latest OS version is OK. macOS diff --git a/.github/workflows/gpu_workflow.yml b/.github/workflows/gpu_workflow.yml index f140cd840..cf8306850 100644 --- a/.github/workflows/gpu_workflow.yml +++ b/.github/workflows/gpu_workflow.yml @@ -16,6 +16,10 @@ on: tags-ignore: - v0.* - v1.* + # Fork-only Kotoba tree: do not enqueue the GPU matrix. + paths-ignore: + - "kotoba/**" + - ".github/workflows/kotoba*.yml" jobs: # --------------------------------------------------------------------------- diff --git a/kotoba/README.md b/kotoba/README.md index 45aca805e..e9ef4a7e6 100644 --- a/kotoba/README.md +++ b/kotoba/README.md @@ -40,12 +40,15 @@ fields and says so. `ocio.kotoba` embeds the fixture as integer bytes and uses only `+`, `*`, `if`, `=`, `<`, and `and`. `main` returns a packed i64 of the parsed fields. +This README does not claim a wasm byte size or a packed numeric result; +those are only true if a completed Kotoba Actions log on this fork prints +them. ## Checks -`checks.sh` compiles with Kotoba 0.7.2, runs the wasm32 module, and -compares the packed result to fields read from the fixture bytes. It does -not invent pass/fail. +`checks.sh` compiles with Kotoba 0.7.2 and compares fields read from the +fixture bytes. It does not invent pass/fail. compile→wasm is not claimed +here; leave that to a completed Kotoba Actions log. ```sh # requires kotoba 0.7.2 on PATH, or downloads the linux-amd64 0.7.2 CLI