Two implementations, the same cases
Conformance
The Tórculo conformance suite: the cases an implementation must pass to call itself conforming, and the latest report.
The suite every Tórculo implementation runs. A case is a small JSON file with an input and
the exact expected output; an implementation conforms to a kind of case when it passes
every non-pending case of that kind. Normative text: spec/SPEC.md.
Suite version and case count: manifest.json. Changes:
CHANGELOG.md.
Every pliego of the suite carries music and sound design (BANDA, EFECTOS, MEZCLA,
MÚSICA, AMBIENTE, ESPACIO and sonido: effects). The sounds are synthesized by
tools/sonido.py with the Python standard library and the canon's
randomness: the same bytes on every machine, dedicated to the public domain (CC0 1.0). They
are not committed; the generator and the runner make them (their SHA-256 are in the
manifest).
Layout
| Path | What | Made by |
|---|---|---|
pliegos/*.mesa | the pliegos: one per area of the language and per profile | hand |
pliegos/errores/*.mesa | one broken rule per pliego, named after what it breaks | hand |
pliegos/sonido/ | music cues (banda/) and effects and ambiences (efectos/) | tools/sonido.py |
manual/lee.json | the expected diagnostics of every pliego, written by hand | hand |
expected/ir/*.ir.json | the frozen IR of the reference implementation | tools/generar.py |
expected/huella/*.json | the fingerprints (§13.4) and soundtrack reports of the reference interpreter | tools/generar.py --huellas |
canon/vectores.json | the canon's test vectors, computed with independent integer arithmetic | tools/generar.py |
laminas/*.dibujo.json | the drawings whose láminas every producer must reproduce byte for byte | copied from pluma/pruebas/referencia |
tomas/*.take, tomas/fuentes/*.py | takes recorded by the engine fork, and the scenes that made them | the engine |
tomas/invalidas/*.take | one broken rule per take | tools/generar.py |
mesapack/*.mesapack | a minimal valid package and one broken rule per package | tools/generar.py |
materias/*.wgsl | shaders that break the pass convention | tools/generar.py |
cases/*.json | one case per file | tools/generar.py |
informe.json | the last report of the runner against the reference implementations | tools/ejecutar.py |
The oracles
Independent readers written from the specification, Python standard library only:
| Tool | Checks |
|---|---|
tools/jcs.py | RFC 8785 canonical JSON (self-test: python3 tools/jcs.py) |
tools/ir.py | the normative projection of an IR (§10.6) |
tools/toma.py | takes (§19); codes T001 to T007 |
tools/lamina.py | láminas (§17); codes L001 to L011 |
tools/mesapack.py | packages (§14); codes P001 to P009 |
tools/materias.py | the pass convention (§18); codes M001 to M006 |
tools/huella.py | fingerprints of a film (§13.4) and their tolerant comparison |
tools/adaptadores/manim_saorin.py | the adapter to the reference implementation of the language |
Regenerating and running
python3 conformance/tools/generar.py # everything except the fingerprints
python3 conformance/tools/generar.py --huellas # also renders the fingerprints (heavy)
python3 conformance/tools/ejecutar.py # the quick classes (about a minute)
python3 conformance/tools/ejecutar.py --pesado # everything: fingerprints, soundtrack, Rust láminas, package round tripRequirements: Python 3.11 or later; Node 22 or later (TypeScript canon and the WebAssembly
pen); rustc (Rust canon); ffmpeg (fingerprints); the Python with which manim-saorín
runs (MESA_PY, by default the Homebrew Manim one) and its folder (MESA_IMPL); the
pen's Python (TORCULO_PLUMA_PY). Heavy work goes through tools/pesado.
How expectations are made
So that nobody has to trust a single implementation:
- Diagnostics are written by hand in
manual/lee.json. The generator refuses to write a case if the reference disagrees, unless the case is marked pending with its reason. - Vocabularies are extracted from the parser (
spec/vocabularios/extrae.py): the specification is that table, and the case checks the parser still says it. - IRs are the reference's, reviewed and frozen. Other compilers are held to the normative projection; the reference, to the whole IR.
- Closure and mirror are properties: the runner computes both sides.
- Canon vectors are computed in
generar.pywith integer arithmetic written again, not with the generated canon. - Láminas: their hashes come from the WebAssembly build of the pen
(
pluma/pruebas/referencia/_laminas.json); Python and Rust must give the same bytes. - Takes, packages, shaders: one broken file per rule, with the code the oracle must give; the generator stops if the oracle disagrees.
- Fingerprints and soundtrack are the reference interpreter's, on the reference machine; they are compared with tolerance (§13.4) and the soundtrack against the targets of §8.2 (−14 LUFS ± 1 LU, true peak at most −1 dBTP, the cues and counts).
Case format
{"id": "lee-errores-afecto", "kind": "lee", "input": {"pliego": "pliegos/errores/afecto.mesa"},
"expect": {"valido": false, "errores": ["E010"], "avisos": ["W113"]}}id equals the file name; paths are relative to conformance/. A case MAY carry
"pendiente": "reason": a known deviation of the reference implementation (SPEC
Appendix D), reported apart.
| kind | input | expect | passes when |
|---|---|---|---|
vocabulario | the vocabularies file | iguales | extracting from the parser gives the same file |
lee | pliego | valido, errores, avisos | same validity and the same sets of codes |
ir | pliego | ir (path), sha256, proyeccion_sha256 | the normative projection's fingerprint matches; for the reference, the whole IR too |
cierre | pliego | iguales_modulo_procedencia, se_quita | canon(compile(p)) == canon(compile(escribe(p))) |
espejo | pliego | efectivo_igual | the effective workshop and hand of p, parte(p) and funde(parte(p)) are equal |
canon_azar | vectores, lengua | iguales | the language's canon gives every vector exactly |
canon_paleta | vectores, lengua | colores | every colour of the generated file matches canon.toml |
lamina | dibujo, implementacion | sha256, valida | the packed lámina has that hash and validates |
toma | toma | valida, resumen or codigo | the reader gives that summary, or rejects with that code |
mesapack | mesapack | valido, errores | the validator gives exactly those codes |
mesapack_referencia | pliego | valido, errores | the packager's package of that pliego validates |
materia | wgsl, modulos | conforme, errores | the checker gives exactly those codes |
huella | pliego, motor, calidad | huella, tolerancia | the rendered film's fingerprint matches |
banda | pliego, motor, calidad | loudness, true peak, cues, counts | the soundtrack report meets them |
Adapters
The runner talks to an implementation through an adapter (tools/adaptadores/). An adapter
for another compiler offers the same functions as manim_saorin.py: ir(pliego),
lee(pliego) returning codes, escribe, parte, funde, taller, and, for an
interpreter, toca_gx(pliego, out) writing the video, the master and the soundtrack report.
The report
ejecutar.py prints a one-line summary and writes informe.json: the suite version and
cases_sha256, the date, the implementations, the totals (pasa, falla, pendiente,
omitido), the totals per kind, and one result per case with its detail when it did not
pass.
The latest report
torculo-conformance 0.2.0 · 15 October 2026 at 14:13 · 62.8 s
89 passes 0 fails 5 pending 8 skipped
What was tested
| Part | Implementation |
|---|---|
lengua | manim-saorin · 1.0.0b1 · MESA v2 beta · 3f34801 |
canon | canon/gen (python, typescript, rust, wgsl, css) |
laminas | pluma (python, wasm, rust) |
tomas | conformance/tools/toma.py sobre tomas del fork |
mesapack | conformance/tools/mesapack.py y `mesa empaqueta` |
materias | conformance/tools/materias.py sobre materias/shaders |
By case class
| Class | passes | fails | pending | skipped |
|---|---|---|---|---|
banda | 2 | |||
canon_azar | 3 | |||
canon_paleta | 5 | |||
cierre | 3 | 1 | ||
espejo | 1 | |||
huella | 2 | |||
ir | 12 | |||
lamina | 6 | 3 | ||
lee | 31 | 3 | ||
materia | 10 | |||
mesapack | 10 | |||
mesapack_referencia | 1 | |||
toma | 8 | |||
vocabulario | 1 |
All 102 cases
| Case | Class | Status | Detail |
|---|---|---|---|
banda-estampas | banda | skipped | clase pesada: con --pesado |
banda-oficio | banda | skipped | clase pesada: con --pesado |
canon-azar-python | canon_azar | passes | |
canon-azar-rust | canon_azar | passes | |
canon-azar-typescript | canon_azar | passes | |
canon-paleta-css | canon_paleta | passes | |
canon-paleta-python | canon_paleta | passes | |
canon-paleta-rust | canon_paleta | passes | |
canon-paleta-typescript | canon_paleta | passes | |
canon-paleta-wgsl | canon_paleta | passes | |
cierre-dirigido | cierre | passes | |
cierre-escena | cierre | passes | |
cierre-servilleta | cierre | pending | La referencia rompe el cierre en una servilleta con banda: `mesa escribe` reescribe el preámbulo y pierde BANDA, EFECTOS, MEZCLA, MÚSICA y AMBIENTE (la IR escrita ya no suena). [/banda: sonido/banda ≠ None; /beats[0]/aspecto/ambiente: amb sala bucle ≠ None; /beats[0]/aspecto/musica: 1M1 ≠ None; /beats[1]/aspecto/ambiente: amb sala bucle ≠ None; /beats[1]/aspecto/musica: 1M1 ≠ None; /beats[2]/aspecto/ambiente: amb sala bucle ≠ None; /beats[2]/aspecto/musica: 1M1 ≠ None; /beats[3]/aspecto/ambiente: amb sala bucle ≠ None] |
cierre-taller | cierre | passes | |
espejo-taller | espejo | pending | La referencia rompe la ley del espejo con la MANO de este pliego: importada desde `manos/firme.mano`, `toca la truca: sí` se lee como `no`; escrita en línea, `precisión alta` queda como una clave sin normalizar al lado de `precision alta`; y `parte` pierde el nombre de la sección (`#TALLER collage` → `#TALLER`). [MANO efectivo distinto: toca la truca: no; toca la truca: si] |
huella-estampas | huella | skipped | clase pesada: con --pesado |
huella-oficio | huella | skipped | clase pesada: con --pesado |
ir-aspecto | ir | passes | |
ir-banda | ir | passes | |
ir-camara | ir | passes | |
ir-cartel | ir | passes | |
ir-dirigido | ir | passes | |
ir-escena | ir | passes | |
ir-estampas | ir | passes | |
ir-oficio | ir | passes | |
ir-oido | ir | passes | |
ir-raccord | ir | passes | |
ir-servilleta | ir | passes | |
ir-taller | ir | passes | |
lamina-manicula-python | lamina | passes | |
lamina-manicula-rust | lamina | skipped | Rust: con --pesado |
lamina-manicula-wasm | lamina | passes | |
lamina-nota-aqui-python | lamina | passes | |
lamina-nota-aqui-rust | lamina | skipped | Rust: con --pesado |
lamina-nota-aqui-wasm | lamina | passes | |
lamina-tintero-python | lamina | passes | |
lamina-tintero-rust | lamina | skipped | Rust: con --pesado |
lamina-tintero-wasm | lamina | passes | |
lee-aspecto | lee | passes | |
lee-banda | lee | passes | |
lee-camara | lee | passes | |
lee-cartel | lee | passes | |
lee-dirigido | lee | passes | |
lee-errores-accion | lee | passes | |
lee-errores-afecto | lee | passes | |
lee-errores-ambiente | lee | passes | |
lee-errores-ambiguedad | lee | passes | |
lee-errores-ancla | lee | passes | |
lee-errores-bestiario-ausente | lee | passes | |
lee-errores-collage | lee | passes | |
lee-errores-color-voz | lee | passes | |
lee-errores-cue | lee | passes | |
lee-errores-dibujo | lee | passes | |
lee-errores-efecto | lee | passes | |
lee-errores-epoca | lee | passes | |
lee-errores-espacio | lee | passes | |
lee-errores-evento | lee | passes | |
lee-errores-funcion | lee | passes | |
lee-errores-gritos | lee | passes | |
lee-errores-letra-preambulo | lee | pending | La referencia acepta `LETRA` (una línea de clip) en el preámbulo sin error y la descarta en silencio: el signo no llega a la IR. Por la ley de las tres sedes (§3) una línea fuera de su sitio es un error (E090), nunca una línea que desaparece. [esperado {'avisos': ['W113'], 'errores': ['E090'], 'valido': False}, da {'valido': True, 'errores': [], 'avisos': ['W113', 'W124']}; mensajes: ['sin «silencio final» declarado (se aplica 3)', 'línea 12 · página vacía · b01: el beat corta a un cuadro nuevo y no po']] |
lee-errores-letrero | lee | passes | |
lee-errores-movimiento | lee | passes | |
lee-errores-pantalla | lee | passes | |
lee-errores-pieza-sin-reparto | lee | passes | |
lee-errores-reservada | lee | pending | La referencia acepta `REPARTO nota: …` sin error y luego trata `nota:` como directiva (avisa «clip vacío»). §5.5 y §6.4 exigen E021 al declarar un hablante con una palabra reservada. [esperado {'avisos': ['W113'], 'errores': ['E021'], 'valido': False}, da {'valido': True, 'errores': [], 'avisos': ['W113', 'W116']}; mensajes: ['línea 12 · b01: clip vacío', 'sin «silencio final» declarado (se aplica 3)']] |
lee-escena | lee | passes | |
lee-estampas | lee | pending | La referencia avisa «clip vacío» (W116) en un clip cuyo único contenido es un signo (`LETRA`). Según §8.3 un signo es una pieza y el clip no está vacío. [esperado {'avisos': ['W112'], 'errores': [], 'valido': True}, da {'valido': True, 'errores': [], 'avisos': ['W112', 'W116']}; mensajes: ['porcentaje pobre 82 % (sano: ≥85 %). 7 líneas con cifras; el pliego di', 'línea 39 · b03: clip vacío']] |
lee-oficio | lee | passes | |
lee-oido | lee | passes | |
lee-raccord | lee | passes | |
lee-servilleta | lee | passes | |
lee-taller | lee | passes | |
materia-acuarela | materia | passes | |
materia-difuminado | materia | passes | |
materia-papel | materia | passes | |
materia-pincel | materia | passes | |
materia-risografia | materia | passes | |
materia-rota-m001 | materia | passes | |
materia-rota-m004 | materia | passes | |
materia-rota-m005 | materia | passes | |
materia-tejido | materia | passes | |
materia-transicion | materia | passes | |
mesapack-formato-futuro | mesapack | passes | |
mesapack-hash-roto | mesapack | passes | |
mesapack-ida-y-vuelta-dirigido | mesapack_referencia | skipped | clase pesada: con --pesado |
mesapack-minimo | mesapack | passes | |
mesapack-no-zip | mesapack | passes | |
mesapack-ruta-peligrosa | mesapack | passes | |
mesapack-sidecar-absoluto | mesapack | passes | |
mesapack-sin-declarar | mesapack | passes | |
mesapack-sin-manifiesto | mesapack | passes | |
mesapack-sin-pliego | mesapack | passes | |
mesapack-sin-versiones | mesapack | passes | |
toma-bano | toma | passes | |
toma-estampa | toma | passes | |
toma-invalida-codigo-desconocido | toma | passes | |
toma-invalida-cortada | toma | passes | |
toma-invalida-post-fuera-de-sitio | toma | passes | |
toma-invalida-sin-start | toma | passes | |
toma-invalida-tras-end | toma | passes | |
toma-invalida-version-upstream | toma | passes | |
vocabulario-parser | vocabulario | passes |