T4 round 1: the precommitted fill oracle, and nothing has rendered against it
Pin 13 makes this sequencing a hard rule -- "points that must be ink" chosen after seeing candidate output is not a test -- so this crate draws nothing and depends on no rendering, tessellation or windowing crate. It derives the oracle from the typed glyph outlines and writes it out as data. Candidates render against it in a later packet. Twenty-seven sample points across five glyphs, all derived programmatically by even-odd point-in-path over the flattened PathCommand outline, never chosen by eye, each at least eight device pixels clear of any outline edge so antialiasing cannot explain a result. Every background point is proven to lie inside a BOUNDED HOLE -- enclosed by the outer silhouette yet unfilled -- since a point merely outside the glyph is passed trivially by a renderer that fills holes solid. fClef instead carries one ink point inside each of its three filled subpaths, tagged by subpath index so the oracle proves every component is covered rather than three points that could all land in the bowl. The status model distinguishes a satisfied disjoint-component result from a failed hole result: a requirement enum, per-requirement required/satisfied pairs, and one authoritative satisfied flag. Mutations, each with kill evidence in the test module. A perturbed ink point is rejected outside the glyph. Shrinking the clearance floor admits strictly more points, so the filter is not a no-op. Every hole point sits inside a naive outer-contour-only fill but outside the real whole-outline fill -- the positive proof that inner-subpath handling is load-bearing, which stands in for a fill-rule flip that CANNOT reclassify anything on correctly wound contours. And filling only fClef's largest subpath misses both dot points, which is the disjoint-component analogue and the reason that class exists. Two proofs about fClef's topology, because one was not enough. Vertex-level non-nesting cannot see two subpaths whose edges cross with no vertex of either inside the other, so it is paired with pairwise bounding-box disjointness, which rules out crossing, touching and containment together. The grid-based corroboration is named for what it actually checks -- one finer 0.005 grid -- rather than claiming any resolution. The oracle's adaptive flattening is authoritative for the recorded signed areas; an earlier coarser fixed-step measurement is marked superseded where it appears. The first four-glyph pass is kept in DECISIONS.md under an explicit SUPERSEDED -- NEVER COMMITTED banner, because its two findings are what caused the contract amendment, and deleting the discovery would erase the reason. Verified: 12/12 tests; fmt clean; cargo doc clean; regeneration byte-identical by SHA-256 from the crate directory. All 27 coordinates were additionally cross-validated by an independent point-in-path implementation with zero mismatches, and the new bounding-box assertion was mutation-killed against a nested glyph to prove it is not vacuous. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
This commit is contained in:
parent
b724e94faf
commit
0a35697d8e
|
|
@ -549,6 +549,20 @@ version = "2.13.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"constant_time_eq",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block"
|
||||
version = "0.1.6"
|
||||
|
|
@ -797,6 +811,12 @@ dependencies = [
|
|||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
|
|
@ -891,6 +911,15 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
|
|
@ -1198,6 +1227,52 @@ version = "0.35.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13ee4e1f553a3584c301f3a56ff1a775f1384781396cea301c8d952e9b93f560"
|
||||
|
||||
[[package]]
|
||||
name = "epiphany-core"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"epiphany-determinism",
|
||||
"getrandom 0.2.17",
|
||||
"num-bigint",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"slotmap",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "epiphany-determinism"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "epiphany-glyphs"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"epiphany-layout-ir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "epiphany-layout-ir"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"epiphany-core",
|
||||
"epiphany-determinism",
|
||||
"epiphany-ops",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "epiphany-ops"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"epiphany-core",
|
||||
"epiphany-determinism",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
|
|
@ -1528,6 +1603,17 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
|
|
@ -2163,6 +2249,12 @@ dependencies = [
|
|||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.22.4"
|
||||
|
|
@ -2608,6 +2700,36 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
|
|
@ -3483,6 +3605,16 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
||||
|
||||
[[package]]
|
||||
name = "round1-oracle"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"epiphany-glyphs",
|
||||
"epiphany-layout-ir",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.20.0"
|
||||
|
|
@ -3618,6 +3750,19 @@ dependencies = [
|
|||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.21"
|
||||
|
|
@ -4176,6 +4321,15 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-script"
|
||||
version = "0.5.8"
|
||||
|
|
@ -4312,6 +4466,12 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.4+wasi-0.2.12"
|
||||
|
|
@ -5600,6 +5760,12 @@ dependencies = [
|
|||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.5.1"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ members = [
|
|||
"probe-egui",
|
||||
"probe-vello",
|
||||
"probe-iced",
|
||||
"round1-oracle",
|
||||
]
|
||||
|
||||
# a11y-verifier is a standalone Python script (a11y-verifier/verify.py), not
|
||||
|
|
|
|||
|
|
@ -137,3 +137,151 @@ behavior — that is round 1's job):
|
|||
|
||||
All three document both rules explicitly. No candidate is eliminated on
|
||||
this desk-survey item; round 1 is where it is actually tested.
|
||||
|
||||
## Round 1 — the precommitted oracle: SUPERSEDED FIRST PASS
|
||||
|
||||
> **SUPERSEDED DISCOVERY — NEVER COMMITTED.** This section records the
|
||||
> four-glyph oracle built against the pre-Revision-6 contract, kept because its
|
||||
> two findings are what *caused* the amendment. It was never committed and is
|
||||
> not the oracle any candidate renders against; **the authoritative account is
|
||||
> the Revision 6 amendment section below** (five glyphs, two check classes,
|
||||
> explicit status model). In particular, `fClef`'s `background_satisfied =
|
||||
> false` below is the superseded encoding — under Revision 6 `fClef` is a
|
||||
> *satisfied* disjoint-component result — and every "committed" in this section
|
||||
> describes an intent that this pass never reached.
|
||||
|
||||
**This is the oracle only — no candidate has rendered against it yet.**
|
||||
`round1-oracle/` (new spike-workspace member) derives, for `gClef`, `fClef`,
|
||||
`timeSig8`, `accidentalFlat` from `BravuraGlyphCatalog::render_data`: a
|
||||
pinned staff-space -> device-pixel transform, Bezier-flattened outlines
|
||||
(recursive de Casteljau, 0.0005 staff-space flatness tolerance), an even-odd
|
||||
/ nonzero point-in-path classifier, and a grid search (0.01 staff-space
|
||||
step) that derives >=3 ink and >=3 bounded-hole background sample points per
|
||||
glyph, each with >=8 device-px clearance from any outline edge, entirely
|
||||
programmatically — no coordinate here was chosen by eye. Output:
|
||||
`round1-oracle/oracle.json` (committed, machine-readable) and
|
||||
`round1-oracle/ORACLE_SUMMARY.md` (human-readable). **No rendering,
|
||||
tessellation, or windowing crate is a dependency of this crate** — see its
|
||||
Cargo.toml and module doc comment.
|
||||
|
||||
**Transform (pinned):** `device = (staff.x * scale + tx, ty - staff.y *
|
||||
scale)`, `scale = 100` device px per staff space, `tx`/`ty` center each
|
||||
glyph's own flattened bounding box in the pin-4 1920x1080 target — one
|
||||
uniform rule applied identically to all four glyphs, not a per-glyph fudge.
|
||||
|
||||
**Subpath counts.** All four match the contract's recorded starting point
|
||||
exactly: `gClef` 4, `fClef` 3, `timeSig8` 3, `accidentalFlat` 2 (asserted in
|
||||
`subpath_counts_match_the_recorded_starting_point`).
|
||||
|
||||
**Finding — `fClef` has no bounded hole at all.** Its three subpaths (the
|
||||
solid clef bowl, area 2.534, plus two disjoint dot subpaths, areas 0.153 and
|
||||
0.148) are not nested — the search finds **zero** grid hits inside the outer
|
||||
contour and outside the even-odd fill, at both the main search's resolution
|
||||
and an independent finer corroboration (0.005 staff-space step, no
|
||||
clearance floor at all;
|
||||
`finding_corroboration::fclef_has_no_bounded_hole_at_any_resolution_or_clearance`).
|
||||
Per Round 1's hole-check clause ("asserted by the derivation, not assumed"), no fallback/relaxed point was substituted:
|
||||
`fClef`'s oracle carries 3 ink points and **zero** background points, and
|
||||
`background_satisfied = false` is recorded in `oracle.json` alongside the
|
||||
diagnostic. The other three glyphs each got 3 ink + 3 background points with
|
||||
no relaxation needed (thousands of qualifying grid candidates for each).
|
||||
|
||||
**Finding — even-odd and nonzero agree on every hole point found.** Mutation
|
||||
(b) (flip even-odd to nonzero on the selected hole points) was run for
|
||||
`gClef`, `timeSig8`, `accidentalFlat`'s hole points and **none reclassify**:
|
||||
Bravura's outer contour and its hole wind in *opposite* directions (the
|
||||
well-formed-font convention), so nonzero winding cancels to 0 at exactly the
|
||||
points where the even-odd crossing count is 2 — both rules correctly exclude
|
||||
the hole. This is real information about the font data (recorded, not
|
||||
smoothed over), and it means the literal "flip the rule" mutation does not,
|
||||
for this data, demonstrate that fill-*rule* choice is load-bearing. A
|
||||
supplementary mutation
|
||||
(`mutation_b_supplement_hole_points_reclassify_under_naive_outer_only_fill`)
|
||||
demonstrates the property the check actually needed — that respecting inner
|
||||
subpaths at all is load-bearing — by showing every selected hole point *is*
|
||||
inside a naive outer-contour-only fill (what a renderer ignoring holes
|
||||
entirely would wrongly paint as ink) while being outside the real
|
||||
whole-outline fill.
|
||||
|
||||
Full mutation evidence (a/b/b-supplement/c) is in
|
||||
`round1-oracle/src/lib.rs`'s `tests` and `finding_corroboration` modules, run
|
||||
via `cargo test -p round1-oracle -- --nocapture`.
|
||||
|
||||
## Round 1 — Revision 6 amendment (five glyphs, two check classes, status model)
|
||||
|
||||
**Both findings above changed the contract, not just this oracle.**
|
||||
`fClef` having no bounded hole and criterion 1's fill-*rule* framing being
|
||||
moot on Bravura's correctly (oppositely) wound contours were reported as
|
||||
findings against the pre-Revision-6 contract; the coordinator's response was
|
||||
to amend `spec/CONTRACT_EDITOR_T4_SPIKE.md` to Revision 6 rather than treat
|
||||
`fClef` as a shortfall. Round 1 is now **five glyphs in two check classes**,
|
||||
and this oracle is amended to match (uncommitted diff on top of the original
|
||||
oracle; the mechanics below are unchanged — same flattening, same
|
||||
point-in-path classifier, same transform, same clearance floor — only the
|
||||
requirement model and the glyph set changed):
|
||||
|
||||
- **Bounded-hole check** (unchanged mechanics): `gClef`, `timeSig8`,
|
||||
`accidentalFlat`, and now **`noteheadHalf`** (new: two subpaths, measured
|
||||
`ring_signed_areas = [0.903, -0.368]` — the authoritative figure, now also
|
||||
what the contract records; an earlier `[0.902, -0.367]` came from a coarser
|
||||
fixed-step flattening). Still >=3 ink + >=3 background points per glyph, every
|
||||
background point inside a bounded hole.
|
||||
- **Disjoint-component check** (new): `fClef` alone, no longer folded into
|
||||
the bounded-hole class it never actually belonged to. It carries **no
|
||||
background requirement** — that is its design (bowl plus two solid,
|
||||
disjoint dots) — and instead requires >=1 ink point inside **each** of its
|
||||
three filled subpaths, each point tagged with `subpath_index` so the
|
||||
oracle proves coverage of every component rather than three ink points
|
||||
that could all land in the bowl. Verified two ways: a finer (0.005
|
||||
staff-space step) grid corroboration (renamed
|
||||
`fclef_has_zero_bounded_hole_grid_hits_at_a_finer_0_005_grid`, since the
|
||||
old name overclaimed "any resolution" for what was actually one grid) and
|
||||
a genuinely resolution-independent topological check,
|
||||
`subpaths_are_mutually_non_nested` /
|
||||
`fclef_subpaths_are_topologically_non_nested`, which asserts no subpath's
|
||||
vertices lie inside another subpath — a property of the flattened polygon
|
||||
itself, not of any sampling step, and what actually justifies the
|
||||
stronger claim.
|
||||
|
||||
**Status model.** `GlyphOracle` now carries an explicit `Requirement` enum
|
||||
(`BoundedHole` / `DisjointComponents`), per-requirement booleans
|
||||
(`background_required`/`background_satisfied`,
|
||||
`subpath_coverage_required`/`subpath_coverage_satisfied`), and one overall
|
||||
`satisfied: bool` — the field to read. `fClef` is `satisfied = true` with
|
||||
`background_required = false` and zero background points; a bounded-hole
|
||||
glyph that failed to find enough background points would be
|
||||
`satisfied = false`. The two are no longer distinguishable only by an absent
|
||||
field, which was the defect the amendment closes.
|
||||
|
||||
**Fill-rule equivalence, now asserted, not merely reported.** The original
|
||||
oracle's `mutation_b_...` test printed `reclassifies_under_nonzero=false` for
|
||||
every hole point without asserting on it. `derive_bounded_hole_oracle` now
|
||||
asserts `!ev.nonzero_filled` for every selected background point at
|
||||
derivation time (fails loudly if the opposite-winding assumption is ever
|
||||
false for a glyph's real data), and
|
||||
`mutation_b_nonzero_rule_equivalence_on_hole_points_is_asserted` asserts the
|
||||
same independently at the test level. `ring_signed_areas` — the measured
|
||||
signed area of every subpath, in ring order — is now recorded on every
|
||||
glyph's oracle output (`oracle.json` and `ORACLE_SUMMARY.md`), not just
|
||||
observed in this log: `gClef [8.702, -0.691, -1.803, -0.509]`, `timeSig8
|
||||
[2.674, -0.435, -0.515]`, `accidentalFlat [1.040, -0.257]`, `noteheadHalf
|
||||
[0.903, -0.368]`, `fClef [2.534, 0.153, 0.148]` — all within measurement
|
||||
tolerance of the contract's recorded values.
|
||||
|
||||
**New kill test.** `mutation_d_largest_contour_only_fill_misses_the_dot_points`
|
||||
is the disjoint-component analogue of the outer-contour-only mutation kept
|
||||
from the original oracle: it asserts that filling only `fClef`'s largest
|
||||
subpath (the bowl) fails to contain either dot's ink point — the literal
|
||||
"tessellator keeps only the largest contour" scenario Round 1 names as the
|
||||
reason this check exists. The original outer-contour-only mutation
|
||||
(`mutation_b_supplement_...`) is unchanged and still runs, now over all four
|
||||
bounded-hole glyphs including `noteheadHalf`.
|
||||
|
||||
**Citations.** Every `pin 13 item N` citation in `round1-oracle/src/lib.rs`,
|
||||
`src/main.rs`, `ORACLE_SUMMARY.md`, and this file has been replaced with a
|
||||
direct citation to the Round 1 clause's own wording — pin 13 has no numbered
|
||||
items in Revision 6 (or any prior revision), so those citations pointed at
|
||||
nothing.
|
||||
|
||||
`oracle.json` and `ORACLE_SUMMARY.md` are regenerated from the amended code
|
||||
(`cargo run -p round1-oracle` from `spikes/editor-toolkit/round1-oracle/`).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "round1-oracle"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
# CONTRACT_EDITOR_T4_SPIKE.md, Round 1 (criterion 1, fill correctness).
|
||||
# Builds the precommitted oracle ONLY: sample points, their expected
|
||||
# ink/background class, and the render transform they are pinned against.
|
||||
#
|
||||
# This crate draws NOTHING. It depends on no rendering, tessellation, or
|
||||
# windowing crate of any kind (no egui, no lyon, no vello, no wgpu, no
|
||||
# winit). Round 1's own wording requires the oracle file, the render transform,
|
||||
# and the expected class per point to be "committed before any candidate
|
||||
# renders", so candidate runs happen in a later packet, only after the user
|
||||
# has reviewed and committed this. This crate's only job is deriving the oracle programmatically
|
||||
# from the typed glyph outlines and writing it out as data.
|
||||
#
|
||||
# The two epiphany dependencies below are the only epiphany dependencies, by
|
||||
# path, exactly as the packet's brief requires.
|
||||
|
||||
[dependencies]
|
||||
epiphany-glyphs = { path = "../../../crates/epiphany-glyphs" }
|
||||
epiphany-layout-ir = { path = "../../../crates/epiphany-layout-ir" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
# Round 1 precommitted oracle — compound-path fill correctness
|
||||
|
||||
Generated by `round1-oracle` (`spikes/editor-toolkit/round1-oracle`). Governed by `spec/CONTRACT_EDITOR_T4_SPIKE.md` Revision 6, Round 1 ("criterion 1, compound-path fill correctness"). No rendering crate was used to produce this file — every point below is derived from the typed `PathCommand` outline alone.
|
||||
|
||||
Two check classes, testing two different properties: **bounded-hole** (`gClef`, `timeSig8`, `accidentalFlat`, `noteheadHalf`) requires ≥3 ink and ≥3 must-be-background points inside a bounded hole; **disjoint-component** (`fClef`) has no bounded hole by design and instead requires ≥1 ink point inside each of its three filled subpaths, tagged with `subpath_index`.
|
||||
|
||||
## Render transform (pinned)
|
||||
|
||||
`device = (staff.x * scale + tx, ty - staff.y * scale); scale = 100 device px per staff space; tx/ty center the glyph's own flattened bounding box in a 1920x1080 target (pin 4); staff-space is y-up, device space is y-down`
|
||||
|
||||
- Flatten tolerance: `0.0005` staff-space units (Bezier chord deviation).
|
||||
- Clearance floor: `8` device px.
|
||||
|
||||
## Overall status
|
||||
|
||||
| glyph | requirement | satisfied | ring_signed_areas |
|
||||
|---|---|---|---|
|
||||
| `gClef` | BoundedHole | **true** | [8.702, -0.691, -1.803, -0.509] |
|
||||
| `timeSig8` | BoundedHole | **true** | [2.674, -0.435, -0.515] |
|
||||
| `accidentalFlat` | BoundedHole | **true** | [1.040, -0.257] |
|
||||
| `noteheadHalf` | BoundedHole | **true** | [0.903, -0.368] |
|
||||
| `fClef` | DisjointComponents | **true** | [2.534, 0.153, 0.148] |
|
||||
|
||||
`fClef` is `satisfied = true` with zero background points — that is the correct, designed-for outcome for its `DisjointComponents` requirement class, not a shortfall. The `satisfied` column above is the field to read; `background_satisfied` alone would make that correct outcome indistinguishable from a bounded-hole glyph's genuine failure.
|
||||
|
||||
## `gClef`
|
||||
|
||||
- Requirement class: BoundedHole (≥3 ink, ≥3 background inside a bounded hole)
|
||||
- Overall status: **satisfied = true**
|
||||
- Subpaths: 4 (matches recorded 4)
|
||||
- Ring signed areas (staff-space², shoelace, ring order): `[8.702, -0.691, -1.803, -0.509]`
|
||||
- Bounding box (staff space): `[0.0, -2.631999969482422, 2.684000015258789, 4.392000198364258]`
|
||||
- Outer contour ring index: 0
|
||||
- Transform: scale=100, tx=825.800, ty=628.000, target=1920x1080
|
||||
- background_required=true, background_satisfied=true
|
||||
- subpath_coverage_required=false, subpath_coverage_satisfied=true
|
||||
- Ink candidates found (>= 8px clearance): 27069
|
||||
- Background/hole candidates found (>= 8px clearance): 18066
|
||||
- Ink point spacing relaxed: false
|
||||
- Background point spacing relaxed: false
|
||||
|
||||
| class | subpath_index | staff (x, y) | device (x, y) | clearance (device px) | hole evidence |
|
||||
|---|---|---|---|---|---|
|
||||
| Ink | - | (0.9400, -1.9220) | (919.80, 820.20) | 39.879 | - |
|
||||
| Ink | - | (0.8500, -2.0520) | (910.80, 833.20) | 31.153 | - |
|
||||
| Ink | - | (1.2700, 1.8880) | (952.80, 439.20) | 29.458 | - |
|
||||
| Background | - | (1.7600, 3.2580) | (1001.80, 302.20) | 32.236 | inside_outer_contour=true, even_odd_filled=false, nonzero_filled=false (outer ring 0) |
|
||||
| Background | - | (1.7000, 3.1180) | (995.80, 316.20) | 31.015 | inside_outer_contour=true, even_odd_filled=false, nonzero_filled=false (outer ring 0) |
|
||||
| Background | - | (1.8200, 3.4080) | (1007.80, 287.20) | 29.839 | inside_outer_contour=true, even_odd_filled=false, nonzero_filled=false (outer ring 0) |
|
||||
|
||||
## `timeSig8`
|
||||
|
||||
- Requirement class: BoundedHole (≥3 ink, ≥3 background inside a bounded hole)
|
||||
- Overall status: **satisfied = true**
|
||||
- Subpaths: 3 (matches recorded 3)
|
||||
- Ring signed areas (staff-space², shoelace, ring order): `[2.674, -0.435, -0.515]`
|
||||
- Bounding box (staff space): `[0.07999999821186066, -1.0360000133514404, 1.6640000343322754, 1.0360000133514404]`
|
||||
- Outer contour ring index: 0
|
||||
- Transform: scale=100, tx=872.800, ty=540.000, target=1920x1080
|
||||
- background_required=true, background_satisfied=true
|
||||
- subpath_coverage_required=false, subpath_coverage_satisfied=true
|
||||
- Ink candidates found (>= 8px clearance): 8030
|
||||
- Background/hole candidates found (>= 8px clearance): 5882
|
||||
- Ink point spacing relaxed: false
|
||||
- Background point spacing relaxed: false
|
||||
|
||||
| class | subpath_index | staff (x, y) | device (x, y) | clearance (device px) | hole evidence |
|
||||
|---|---|---|---|---|---|
|
||||
| Ink | - | (1.3300, -0.1960) | (1005.80, 559.60) | 28.647 | - |
|
||||
| Ink | - | (1.1200, -0.0460) | (984.80, 544.60) | 28.211 | - |
|
||||
| Ink | - | (0.9600, 0.0140) | (968.80, 538.60) | 26.413 | - |
|
||||
| Background | - | (0.6900, -0.5360) | (941.80, 593.60) | 34.514 | inside_outer_contour=true, even_odd_filled=false, nonzero_filled=false (outer ring 0) |
|
||||
| Background | - | (0.8400, -0.5860) | (956.80, 598.60) | 31.745 | inside_outer_contour=true, even_odd_filled=false, nonzero_filled=false (outer ring 0) |
|
||||
| Background | - | (1.0000, 0.5940) | (972.80, 480.60) | 29.916 | inside_outer_contour=true, even_odd_filled=false, nonzero_filled=false (outer ring 0) |
|
||||
|
||||
## `accidentalFlat`
|
||||
|
||||
- Requirement class: BoundedHole (≥3 ink, ≥3 background inside a bounded hole)
|
||||
- Overall status: **satisfied = true**
|
||||
- Subpaths: 2 (matches recorded 2)
|
||||
- Ring signed areas (staff-space², shoelace, ring order): `[1.040, -0.257]`
|
||||
- Bounding box (staff space): `[0.0, -0.699999988079071, 0.9039999842643738, 1.75600004196167]`
|
||||
- Outer contour ring index: 0
|
||||
- Transform: scale=100, tx=914.800, ty=592.800, target=1920x1080
|
||||
- background_required=true, background_satisfied=true
|
||||
- subpath_coverage_required=false, subpath_coverage_satisfied=true
|
||||
- Ink candidates found (>= 8px clearance): 1381
|
||||
- Background/hole candidates found (>= 8px clearance): 1175
|
||||
- Ink point spacing relaxed: false
|
||||
- Background point spacing relaxed: false
|
||||
|
||||
| class | subpath_index | staff (x, y) | device (x, y) | clearance (device px) | hole evidence |
|
||||
|---|---|---|---|---|---|
|
||||
| Ink | - | (0.7500, 0.3100) | (989.80, 561.80) | 14.124 | - |
|
||||
| Ink | - | (0.7600, 0.1500) | (990.80, 577.80) | 13.243 | - |
|
||||
| Ink | - | (0.1500, 0.3800) | (929.80, 554.80) | 12.985 | - |
|
||||
| Background | - | (0.4000, 0.1200) | (954.80, 580.80) | 22.160 | inside_outer_contour=true, even_odd_filled=false, nonzero_filled=false (outer ring 0) |
|
||||
| Background | - | (0.3700, -0.0300) | (951.80, 595.80) | 18.269 | inside_outer_contour=true, even_odd_filled=false, nonzero_filled=false (outer ring 0) |
|
||||
| Background | - | (0.4600, 0.2600) | (960.80, 566.80) | 13.874 | inside_outer_contour=true, even_odd_filled=false, nonzero_filled=false (outer ring 0) |
|
||||
|
||||
## `noteheadHalf`
|
||||
|
||||
- Requirement class: BoundedHole (≥3 ink, ≥3 background inside a bounded hole)
|
||||
- Overall status: **satisfied = true**
|
||||
- Subpaths: 2 (matches recorded 2)
|
||||
- Ring signed areas (staff-space², shoelace, ring order): `[0.903, -0.368]`
|
||||
- Bounding box (staff space): `[0.0, -0.5, 1.1799999475479126, 0.5]`
|
||||
- Outer contour ring index: 0
|
||||
- Transform: scale=100, tx=901.000, ty=540.000, target=1920x1080
|
||||
- background_required=true, background_satisfied=true
|
||||
- subpath_coverage_required=false, subpath_coverage_satisfied=true
|
||||
- Ink candidates found (>= 8px clearance): 949
|
||||
- Background/hole candidates found (>= 8px clearance): 1919
|
||||
- Ink point spacing relaxed: false
|
||||
- Background point spacing relaxed: false
|
||||
|
||||
| class | subpath_index | staff (x, y) | device (x, y) | clearance (device px) | hole evidence |
|
||||
|---|---|---|---|---|---|
|
||||
| Ink | - | (0.4000, 0.2800) | (941.00, 512.00) | 12.348 | - |
|
||||
| Ink | - | (0.7600, -0.2900) | (977.00, 569.00) | 12.308 | - |
|
||||
| Ink | - | (0.8900, -0.2000) | (990.00, 560.00) | 11.678 | - |
|
||||
| Background | - | (0.5900, 0.0000) | (960.00, 540.00) | 21.286 | inside_outer_contour=true, even_odd_filled=false, nonzero_filled=false (outer ring 0) |
|
||||
| Background | - | (0.4600, -0.0800) | (947.00, 548.00) | 20.683 | inside_outer_contour=true, even_odd_filled=false, nonzero_filled=false (outer ring 0) |
|
||||
| Background | - | (0.7200, 0.0800) | (973.00, 532.00) | 20.108 | inside_outer_contour=true, even_odd_filled=false, nonzero_filled=false (outer ring 0) |
|
||||
|
||||
## `fClef`
|
||||
|
||||
- Requirement class: DisjointComponents (no background requirement; ≥1 ink point per filled subpath)
|
||||
- Overall status: **satisfied = true**
|
||||
- Subpaths: 3 (matches recorded 3)
|
||||
- Ring signed areas (staff-space², shoelace, ring order): `[2.534, 0.153, 0.148]`
|
||||
- Bounding box (staff space): `[-0.019999999552965164, -2.5399999618530273, 2.7360000610351563, 1.0479999780654907]`
|
||||
- Outer contour ring index: 0
|
||||
- Transform: scale=100, tx=824.200, ty=465.400, target=1920x1080
|
||||
- background_required=false, background_satisfied=true
|
||||
- subpath_coverage_required=true, subpath_coverage_satisfied=true
|
||||
- Ink candidates found (>= 8px clearance): 3
|
||||
- Background/hole candidates found (>= 8px clearance): 0
|
||||
- Ink point spacing relaxed: false
|
||||
- Background point spacing relaxed: false
|
||||
|
||||
| class | subpath_index | staff (x, y) | device (x, y) | clearance (device px) | hole evidence |
|
||||
|---|---|---|---|---|---|
|
||||
| Ink | 0 | (0.5000, -0.0200) | (874.20, 467.40) | 41.560 | - |
|
||||
| Ink | 1 | (2.5160, 0.5000) | (1075.80, 415.40) | 21.974 | - |
|
||||
| Ink | 2 | (2.5240, -0.4960) | (1076.60, 515.00) | 21.149 | - |
|
||||
|
||||
|
|
@ -0,0 +1,651 @@
|
|||
{
|
||||
"contract": "spec/CONTRACT_EDITOR_T4_SPIKE.md Round 1 (criterion 1, compound-path fill correctness)",
|
||||
"round": "round1",
|
||||
"render_transform_rule": "device = (staff.x * scale + tx, ty - staff.y * scale); scale = 100 device px per staff space; tx/ty center the glyph's own flattened bounding box in a 1920x1080 target (pin 4); staff-space is y-up, device space is y-down",
|
||||
"flatten_tolerance_staff_space": 0.0005,
|
||||
"clearance_floor_device_px": 8.0,
|
||||
"glyphs": [
|
||||
{
|
||||
"name": "gClef",
|
||||
"requirement": "BoundedHole",
|
||||
"subpath_count": 4,
|
||||
"expected_subpath_count": 4,
|
||||
"transform": {
|
||||
"scale": 100.0,
|
||||
"tx": 825.7999992370605,
|
||||
"ty": 628.0000114440918,
|
||||
"target_width": 1920.0,
|
||||
"target_height": 1080.0
|
||||
},
|
||||
"bbox_staff": [
|
||||
0.0,
|
||||
-2.631999969482422,
|
||||
2.684000015258789,
|
||||
4.392000198364258
|
||||
],
|
||||
"outer_contour_ring_index": 0,
|
||||
"ring_signed_areas": [
|
||||
8.70199089321805,
|
||||
-0.6913140433690712,
|
||||
-1.8034304355375201,
|
||||
-0.5093689639942818
|
||||
],
|
||||
"points": [
|
||||
{
|
||||
"staff": [
|
||||
0.9400000000000006,
|
||||
-1.9219999694824352
|
||||
],
|
||||
"device": [
|
||||
919.7999992370605,
|
||||
820.2000083923353
|
||||
],
|
||||
"class": "Ink",
|
||||
"clearance_device_px": 39.879224940156384,
|
||||
"hole_evidence": null,
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
0.8500000000000005,
|
||||
-2.0519999694824342
|
||||
],
|
||||
"device": [
|
||||
910.7999992370605,
|
||||
833.2000083923352
|
||||
],
|
||||
"class": "Ink",
|
||||
"clearance_device_px": 31.152574135700984,
|
||||
"hole_evidence": null,
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
1.270000000000001,
|
||||
1.8880000305175677
|
||||
],
|
||||
"device": [
|
||||
952.7999992370607,
|
||||
439.200008392335
|
||||
],
|
||||
"class": "Ink",
|
||||
"clearance_device_px": 29.458331559341232,
|
||||
"hole_evidence": null,
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
1.7600000000000013,
|
||||
3.258000030517541
|
||||
],
|
||||
"device": [
|
||||
1001.7999992370607,
|
||||
302.2000083923377
|
||||
],
|
||||
"class": "Background",
|
||||
"clearance_device_px": 32.23617311771167,
|
||||
"hole_evidence": {
|
||||
"inside_outer_contour": true,
|
||||
"even_odd_filled": false,
|
||||
"nonzero_filled": false,
|
||||
"outer_contour_ring_index": 0
|
||||
},
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
1.7000000000000013,
|
||||
3.118000030517544
|
||||
],
|
||||
"device": [
|
||||
995.7999992370607,
|
||||
316.2000083923374
|
||||
],
|
||||
"class": "Background",
|
||||
"clearance_device_px": 31.015291567040098,
|
||||
"hole_evidence": {
|
||||
"inside_outer_contour": true,
|
||||
"even_odd_filled": false,
|
||||
"nonzero_filled": false,
|
||||
"outer_contour_ring_index": 0
|
||||
},
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
1.8200000000000014,
|
||||
3.4080000305175377
|
||||
],
|
||||
"device": [
|
||||
1007.7999992370607,
|
||||
287.200008392338
|
||||
],
|
||||
"class": "Background",
|
||||
"clearance_device_px": 29.838877367007576,
|
||||
"hole_evidence": {
|
||||
"inside_outer_contour": true,
|
||||
"even_odd_filled": false,
|
||||
"nonzero_filled": false,
|
||||
"outer_contour_ring_index": 0
|
||||
},
|
||||
"subpath_index": null
|
||||
}
|
||||
],
|
||||
"ink_candidates_found": 27069,
|
||||
"background_candidates_found": 18066,
|
||||
"ink_spacing_relaxed": false,
|
||||
"background_spacing_relaxed": false,
|
||||
"ink_satisfied": true,
|
||||
"background_required": true,
|
||||
"background_satisfied": true,
|
||||
"subpath_coverage_required": false,
|
||||
"subpath_coverage_satisfied": true,
|
||||
"satisfied": true,
|
||||
"hole_diagnostic": {
|
||||
"raw_hole_grid_hits": 30028,
|
||||
"best_hole_clearance_device_px": 32.23617311771167
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "timeSig8",
|
||||
"requirement": "BoundedHole",
|
||||
"subpath_count": 3,
|
||||
"expected_subpath_count": 3,
|
||||
"transform": {
|
||||
"scale": 100.0,
|
||||
"tx": 872.7999983727932,
|
||||
"ty": 540.0,
|
||||
"target_width": 1920.0,
|
||||
"target_height": 1080.0
|
||||
},
|
||||
"bbox_staff": [
|
||||
0.07999999821186066,
|
||||
-1.0360000133514404,
|
||||
1.6640000343322754,
|
||||
1.0360000133514404
|
||||
],
|
||||
"outer_contour_ring_index": 0,
|
||||
"ring_signed_areas": [
|
||||
2.6743829783025608,
|
||||
-0.43524448349075884,
|
||||
-0.5148498430682749
|
||||
],
|
||||
"points": [
|
||||
{
|
||||
"staff": [
|
||||
1.3299999982118615,
|
||||
-0.19600001335143968
|
||||
],
|
||||
"device": [
|
||||
1005.7999981939794,
|
||||
559.6000013351439
|
||||
],
|
||||
"class": "Ink",
|
||||
"clearance_device_px": 28.64675883470137,
|
||||
"hole_evidence": null,
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
1.1199999982118614,
|
||||
-0.04600001335143965
|
||||
],
|
||||
"device": [
|
||||
984.7999981939794,
|
||||
544.6000013351439
|
||||
],
|
||||
"class": "Ink",
|
||||
"clearance_device_px": 28.21134677772842,
|
||||
"hole_evidence": null,
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
0.9599999982118613,
|
||||
0.013999986648560359
|
||||
],
|
||||
"device": [
|
||||
968.7999981939794,
|
||||
538.6000013351439
|
||||
],
|
||||
"class": "Ink",
|
||||
"clearance_device_px": 26.412730159918755,
|
||||
"hole_evidence": null,
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
0.6899999982118611,
|
||||
-0.53600001335144
|
||||
],
|
||||
"device": [
|
||||
941.7999981939793,
|
||||
593.600001335144
|
||||
],
|
||||
"class": "Background",
|
||||
"clearance_device_px": 34.51401272338884,
|
||||
"hole_evidence": {
|
||||
"inside_outer_contour": true,
|
||||
"even_odd_filled": false,
|
||||
"nonzero_filled": false,
|
||||
"outer_contour_ring_index": 0
|
||||
},
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
0.8399999982118612,
|
||||
-0.58600001335144
|
||||
],
|
||||
"device": [
|
||||
956.7999981939793,
|
||||
598.600001335144
|
||||
],
|
||||
"class": "Background",
|
||||
"clearance_device_px": 31.74536201663279,
|
||||
"hole_evidence": {
|
||||
"inside_outer_contour": true,
|
||||
"even_odd_filled": false,
|
||||
"nonzero_filled": false,
|
||||
"outer_contour_ring_index": 0
|
||||
},
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
0.9999999982118614,
|
||||
0.5939999866485607
|
||||
],
|
||||
"device": [
|
||||
972.7999981939794,
|
||||
480.60000133514393
|
||||
],
|
||||
"class": "Background",
|
||||
"clearance_device_px": 29.916370370369677,
|
||||
"hole_evidence": {
|
||||
"inside_outer_contour": true,
|
||||
"even_odd_filled": false,
|
||||
"nonzero_filled": false,
|
||||
"outer_contour_ring_index": 0
|
||||
},
|
||||
"subpath_index": null
|
||||
}
|
||||
],
|
||||
"ink_candidates_found": 8030,
|
||||
"background_candidates_found": 5882,
|
||||
"ink_spacing_relaxed": false,
|
||||
"background_spacing_relaxed": false,
|
||||
"ink_satisfied": true,
|
||||
"background_required": true,
|
||||
"background_satisfied": true,
|
||||
"subpath_coverage_required": false,
|
||||
"subpath_coverage_satisfied": true,
|
||||
"satisfied": true,
|
||||
"hole_diagnostic": {
|
||||
"raw_hole_grid_hits": 9504,
|
||||
"best_hole_clearance_device_px": 34.51401272338884
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "accidentalFlat",
|
||||
"requirement": "BoundedHole",
|
||||
"subpath_count": 2,
|
||||
"expected_subpath_count": 2,
|
||||
"transform": {
|
||||
"scale": 100.0,
|
||||
"tx": 914.8000007867813,
|
||||
"ty": 592.80000269413,
|
||||
"target_width": 1920.0,
|
||||
"target_height": 1080.0
|
||||
},
|
||||
"bbox_staff": [
|
||||
0.0,
|
||||
-0.699999988079071,
|
||||
0.9039999842643738,
|
||||
1.75600004196167
|
||||
],
|
||||
"outer_contour_ring_index": 0,
|
||||
"ring_signed_areas": [
|
||||
1.0400791647491505,
|
||||
-0.2566232782507076
|
||||
],
|
||||
"points": [
|
||||
{
|
||||
"staff": [
|
||||
0.7500000000000004,
|
||||
0.3100000119209296
|
||||
],
|
||||
"device": [
|
||||
989.8000007867813,
|
||||
561.8000015020369
|
||||
],
|
||||
"class": "Ink",
|
||||
"clearance_device_px": 14.123904362928288,
|
||||
"hole_evidence": null,
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
0.7600000000000005,
|
||||
0.15000001192092943
|
||||
],
|
||||
"device": [
|
||||
990.8000007867813,
|
||||
577.800001502037
|
||||
],
|
||||
"class": "Ink",
|
||||
"clearance_device_px": 13.242801200968785,
|
||||
"hole_evidence": null,
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
0.15,
|
||||
0.38000001192092964
|
||||
],
|
||||
"device": [
|
||||
929.8000007867813,
|
||||
554.8000015020369
|
||||
],
|
||||
"class": "Ink",
|
||||
"clearance_device_px": 12.984644164814336,
|
||||
"hole_evidence": null,
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
0.4000000000000002,
|
||||
0.12000001192092942
|
||||
],
|
||||
"device": [
|
||||
954.8000007867813,
|
||||
580.800001502037
|
||||
],
|
||||
"class": "Background",
|
||||
"clearance_device_px": 22.16023587801027,
|
||||
"hole_evidence": {
|
||||
"inside_outer_contour": true,
|
||||
"even_odd_filled": false,
|
||||
"nonzero_filled": false,
|
||||
"outer_contour_ring_index": 0
|
||||
},
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
0.37000000000000016,
|
||||
-0.02999998807907056
|
||||
],
|
||||
"device": [
|
||||
951.8000007867813,
|
||||
595.800001502037
|
||||
],
|
||||
"class": "Background",
|
||||
"clearance_device_px": 18.26919422193897,
|
||||
"hole_evidence": {
|
||||
"inside_outer_contour": true,
|
||||
"even_odd_filled": false,
|
||||
"nonzero_filled": false,
|
||||
"outer_contour_ring_index": 0
|
||||
},
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
0.46000000000000024,
|
||||
0.26000001192092953
|
||||
],
|
||||
"device": [
|
||||
960.8000007867813,
|
||||
566.800001502037
|
||||
],
|
||||
"class": "Background",
|
||||
"clearance_device_px": 13.87396974116893,
|
||||
"hole_evidence": {
|
||||
"inside_outer_contour": true,
|
||||
"even_odd_filled": false,
|
||||
"nonzero_filled": false,
|
||||
"outer_contour_ring_index": 0
|
||||
},
|
||||
"subpath_index": null
|
||||
}
|
||||
],
|
||||
"ink_candidates_found": 1381,
|
||||
"background_candidates_found": 1175,
|
||||
"ink_spacing_relaxed": false,
|
||||
"background_spacing_relaxed": false,
|
||||
"ink_satisfied": true,
|
||||
"background_required": true,
|
||||
"background_satisfied": true,
|
||||
"subpath_coverage_required": false,
|
||||
"subpath_coverage_satisfied": true,
|
||||
"satisfied": true,
|
||||
"hole_diagnostic": {
|
||||
"raw_hole_grid_hits": 2571,
|
||||
"best_hole_clearance_device_px": 22.16023587801027
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "noteheadHalf",
|
||||
"requirement": "BoundedHole",
|
||||
"subpath_count": 2,
|
||||
"expected_subpath_count": 2,
|
||||
"transform": {
|
||||
"scale": 100.0,
|
||||
"tx": 901.0000026226044,
|
||||
"ty": 540.0,
|
||||
"target_width": 1920.0,
|
||||
"target_height": 1080.0
|
||||
},
|
||||
"bbox_staff": [
|
||||
0.0,
|
||||
-0.5,
|
||||
1.1799999475479126,
|
||||
0.5
|
||||
],
|
||||
"outer_contour_ring_index": 0,
|
||||
"ring_signed_areas": [
|
||||
0.9031883633805334,
|
||||
-0.36761223428111944
|
||||
],
|
||||
"points": [
|
||||
{
|
||||
"staff": [
|
||||
0.4000000000000002,
|
||||
0.2800000000000004
|
||||
],
|
||||
"device": [
|
||||
941.0000026226044,
|
||||
511.99999999999994
|
||||
],
|
||||
"class": "Ink",
|
||||
"clearance_device_px": 12.347781160235112,
|
||||
"hole_evidence": null,
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
0.7600000000000005,
|
||||
-0.2899999999999998
|
||||
],
|
||||
"device": [
|
||||
977.0000026226044,
|
||||
569.0
|
||||
],
|
||||
"class": "Ink",
|
||||
"clearance_device_px": 12.307512116945116,
|
||||
"hole_evidence": null,
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
0.8900000000000006,
|
||||
-0.19999999999999973
|
||||
],
|
||||
"device": [
|
||||
990.0000026226044,
|
||||
560.0
|
||||
],
|
||||
"class": "Ink",
|
||||
"clearance_device_px": 11.678047475017337,
|
||||
"hole_evidence": null,
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
0.5900000000000003,
|
||||
3.0878077872387166e-16
|
||||
],
|
||||
"device": [
|
||||
960.0000026226044,
|
||||
540.0
|
||||
],
|
||||
"class": "Background",
|
||||
"clearance_device_px": 21.285841339339235,
|
||||
"hole_evidence": {
|
||||
"inside_outer_contour": true,
|
||||
"even_odd_filled": false,
|
||||
"nonzero_filled": false,
|
||||
"outer_contour_ring_index": 0
|
||||
},
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
0.46000000000000024,
|
||||
-0.0799999999999997
|
||||
],
|
||||
"device": [
|
||||
947.0000026226044,
|
||||
548.0
|
||||
],
|
||||
"class": "Background",
|
||||
"clearance_device_px": 20.68272256548643,
|
||||
"hole_evidence": {
|
||||
"inside_outer_contour": true,
|
||||
"even_odd_filled": false,
|
||||
"nonzero_filled": false,
|
||||
"outer_contour_ring_index": 0
|
||||
},
|
||||
"subpath_index": null
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
0.7200000000000004,
|
||||
0.08000000000000031
|
||||
],
|
||||
"device": [
|
||||
973.0000026226044,
|
||||
532.0
|
||||
],
|
||||
"class": "Background",
|
||||
"clearance_device_px": 20.1077663347467,
|
||||
"hole_evidence": {
|
||||
"inside_outer_contour": true,
|
||||
"even_odd_filled": false,
|
||||
"nonzero_filled": false,
|
||||
"outer_contour_ring_index": 0
|
||||
},
|
||||
"subpath_index": null
|
||||
}
|
||||
],
|
||||
"ink_candidates_found": 949,
|
||||
"background_candidates_found": 1919,
|
||||
"ink_spacing_relaxed": false,
|
||||
"background_spacing_relaxed": false,
|
||||
"ink_satisfied": true,
|
||||
"background_required": true,
|
||||
"background_satisfied": true,
|
||||
"subpath_coverage_required": false,
|
||||
"subpath_coverage_satisfied": true,
|
||||
"satisfied": true,
|
||||
"hole_diagnostic": {
|
||||
"raw_hole_grid_hits": 3681,
|
||||
"best_hole_clearance_device_px": 21.285841339339235
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fClef",
|
||||
"requirement": "DisjointComponents",
|
||||
"subpath_count": 3,
|
||||
"expected_subpath_count": 3,
|
||||
"transform": {
|
||||
"scale": 100.0,
|
||||
"tx": 824.1999969258904,
|
||||
"ty": 465.40000081062317,
|
||||
"target_width": 1920.0,
|
||||
"target_height": 1080.0
|
||||
},
|
||||
"bbox_staff": [
|
||||
-0.019999999552965164,
|
||||
-2.5399999618530273,
|
||||
2.7360000610351562,
|
||||
1.0479999780654907
|
||||
],
|
||||
"outer_contour_ring_index": 0,
|
||||
"ring_signed_areas": [
|
||||
2.5338203400622494,
|
||||
0.15280048146661862,
|
||||
0.1481298437718624
|
||||
],
|
||||
"points": [
|
||||
{
|
||||
"staff": [
|
||||
0.5000000004470351,
|
||||
-0.019999961853036992
|
||||
],
|
||||
"device": [
|
||||
874.1999969705939,
|
||||
467.39999699592687
|
||||
],
|
||||
"class": "Ink",
|
||||
"clearance_device_px": 41.559502835282714,
|
||||
"hole_evidence": null,
|
||||
"subpath_index": 0
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
2.5160000038146926,
|
||||
0.5000000011920931
|
||||
],
|
||||
"device": [
|
||||
1075.7999973073597,
|
||||
415.4000006914139
|
||||
],
|
||||
"class": "Ink",
|
||||
"clearance_device_px": 21.97428696274997,
|
||||
"hole_evidence": null,
|
||||
"subpath_index": 1
|
||||
},
|
||||
{
|
||||
"staff": [
|
||||
2.5239999008178664,
|
||||
-0.4960000205039976
|
||||
],
|
||||
"device": [
|
||||
1076.599987007677,
|
||||
515.000002861023
|
||||
],
|
||||
"class": "Ink",
|
||||
"clearance_device_px": 21.14908118002104,
|
||||
"hole_evidence": null,
|
||||
"subpath_index": 2
|
||||
}
|
||||
],
|
||||
"ink_candidates_found": 3,
|
||||
"background_candidates_found": 0,
|
||||
"ink_spacing_relaxed": false,
|
||||
"background_spacing_relaxed": false,
|
||||
"ink_satisfied": true,
|
||||
"background_required": false,
|
||||
"background_satisfied": true,
|
||||
"subpath_coverage_required": true,
|
||||
"subpath_coverage_satisfied": true,
|
||||
"satisfied": true,
|
||||
"hole_diagnostic": {
|
||||
"raw_hole_grid_hits": 0,
|
||||
"best_hole_clearance_device_px": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,314 @@
|
|||
//! Generates the committed Round-1 oracle: `oracle.json` (machine-readable,
|
||||
//! consumed by candidate runs in a later packet) and `ORACLE_SUMMARY.md`
|
||||
//! (human-readable, for the user's pin-13 review). Run with `cargo run -p
|
||||
//! round1-oracle` from `spikes/editor-toolkit/round1-oracle/`.
|
||||
|
||||
use epiphany_glyphs::BravuraGlyphCatalog;
|
||||
use epiphany_layout_ir::GlyphCatalog;
|
||||
use round1_oracle::{derive_glyph_oracle, GlyphOracle, Requirement, SampleClass};
|
||||
use serde::Serialize;
|
||||
use std::fmt::Write as _;
|
||||
use std::fs;
|
||||
|
||||
/// Round 1's bounded-hole check class: "≥3 must-be-ink and ≥3 must-be-
|
||||
/// background points, each inside a bounded hole".
|
||||
const HOLE_GLYPHS: &[(&str, usize)] = &[
|
||||
("gClef", 4),
|
||||
("timeSig8", 3),
|
||||
("accidentalFlat", 2),
|
||||
("noteheadHalf", 2),
|
||||
];
|
||||
|
||||
/// Round 1's disjoint-component check class: "no bounded hole ... ≥1 ink
|
||||
/// point inside each of its three filled subpaths".
|
||||
const DISJOINT_GLYPHS: &[(&str, usize)] = &[("fClef", 3)];
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct OracleFile {
|
||||
contract: &'static str,
|
||||
round: &'static str,
|
||||
render_transform_rule: &'static str,
|
||||
flatten_tolerance_staff_space: f64,
|
||||
clearance_floor_device_px: f64,
|
||||
glyphs: Vec<GlyphOracle>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let catalog = BravuraGlyphCatalog;
|
||||
let mut glyphs = Vec::new();
|
||||
let mut findings = Vec::new();
|
||||
|
||||
for (name, expected_subpaths) in HOLE_GLYPHS {
|
||||
let data = catalog
|
||||
.render_data(name)
|
||||
.unwrap_or_else(|| panic!("{name}: BravuraGlyphCatalog has no render data"));
|
||||
match derive_glyph_oracle(
|
||||
name,
|
||||
&data.outline,
|
||||
Some(*expected_subpaths),
|
||||
Requirement::BoundedHole,
|
||||
) {
|
||||
Ok(oracle) => {
|
||||
if oracle.subpath_count != *expected_subpaths {
|
||||
findings.push(format!(
|
||||
"{name}: parsed {} subpaths, contract's verified-starting-point section \
|
||||
records {}",
|
||||
oracle.subpath_count, expected_subpaths
|
||||
));
|
||||
}
|
||||
if !oracle.satisfied {
|
||||
let d = &oracle.hole_diagnostic;
|
||||
let detail = if d.raw_hole_grid_hits == 0 {
|
||||
"this glyph has NO bounded hole at all under even-odd fill — every \
|
||||
subpath is either the outer silhouette or a disjoint ink island, none \
|
||||
nested inside another"
|
||||
.to_string()
|
||||
} else {
|
||||
format!(
|
||||
"this glyph has a bounded hole ({} grid hits) but the best \
|
||||
achievable clearance in it is {:.3} device px, below the \
|
||||
{}px floor",
|
||||
d.raw_hole_grid_hits,
|
||||
d.best_hole_clearance_device_px.unwrap_or(f64::NAN),
|
||||
round1_oracle::CLEARANCE_MIN_DEVICE_PX
|
||||
)
|
||||
};
|
||||
findings.push(format!(
|
||||
"{name}: fewer than {} must-be-background sample points could be \
|
||||
derived — {detail}. Per Round 1's hole-check clause ('each inside a \
|
||||
bounded hole ... asserted by the derivation, not assumed'), no fallback \
|
||||
point was substituted; {name}'s oracle carries ink points only and is \
|
||||
NOT satisfied.",
|
||||
round1_oracle::POINTS_PER_CLASS
|
||||
));
|
||||
}
|
||||
glyphs.push(oracle);
|
||||
}
|
||||
Err(e) => findings.push(e),
|
||||
}
|
||||
}
|
||||
|
||||
for (name, expected_subpaths) in DISJOINT_GLYPHS {
|
||||
let data = catalog
|
||||
.render_data(name)
|
||||
.unwrap_or_else(|| panic!("{name}: BravuraGlyphCatalog has no render data"));
|
||||
match derive_glyph_oracle(
|
||||
name,
|
||||
&data.outline,
|
||||
Some(*expected_subpaths),
|
||||
Requirement::DisjointComponents,
|
||||
) {
|
||||
Ok(oracle) => {
|
||||
if oracle.subpath_count != *expected_subpaths {
|
||||
findings.push(format!(
|
||||
"{name}: parsed {} subpaths, contract's verified-starting-point section \
|
||||
records {}",
|
||||
oracle.subpath_count, expected_subpaths
|
||||
));
|
||||
}
|
||||
// `oracle.satisfied` is expected true here (no bounded-hole
|
||||
// shortfall is possible for this class — see
|
||||
// `derive_glyph_oracle`'s DisjointComponents arm, which only
|
||||
// returns Ok once every subpath is covered), but this is
|
||||
// reported as a finding rather than assumed, matching every
|
||||
// other status check in this file.
|
||||
if !oracle.satisfied {
|
||||
findings.push(format!(
|
||||
"{name}: disjoint-component coverage requirement not met — see \
|
||||
subpath_coverage_satisfied in oracle.json"
|
||||
));
|
||||
}
|
||||
glyphs.push(oracle);
|
||||
}
|
||||
Err(e) => findings.push(e),
|
||||
}
|
||||
}
|
||||
|
||||
let oracle_file = OracleFile {
|
||||
contract: "spec/CONTRACT_EDITOR_T4_SPIKE.md Round 1 (criterion 1, compound-path fill \
|
||||
correctness)",
|
||||
round: "round1",
|
||||
render_transform_rule: "device = (staff.x * scale + tx, ty - staff.y * scale); scale = \
|
||||
100 device px per staff space; tx/ty center the glyph's own flattened bounding box \
|
||||
in a 1920x1080 target (pin 4); staff-space is y-up, device space is y-down",
|
||||
flatten_tolerance_staff_space: round1_oracle::FLATTEN_TOLERANCE,
|
||||
clearance_floor_device_px: round1_oracle::CLEARANCE_MIN_DEVICE_PX,
|
||||
glyphs,
|
||||
};
|
||||
|
||||
let json = serde_json::to_string_pretty(&oracle_file).expect("oracle serializes");
|
||||
fs::write("oracle.json", &json).expect("write oracle.json");
|
||||
|
||||
let summary = render_summary(&oracle_file, &findings);
|
||||
fs::write("ORACLE_SUMMARY.md", &summary).expect("write ORACLE_SUMMARY.md");
|
||||
|
||||
println!("wrote oracle.json ({} bytes)", json.len());
|
||||
println!("wrote ORACLE_SUMMARY.md");
|
||||
if !findings.is_empty() {
|
||||
println!("\nFINDINGS:");
|
||||
for f in &findings {
|
||||
println!(" - {f}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_summary(oracle: &OracleFile, findings: &[String]) -> String {
|
||||
let mut s = String::new();
|
||||
writeln!(
|
||||
s,
|
||||
"# Round 1 precommitted oracle — compound-path fill correctness\n"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
s,
|
||||
"Generated by `round1-oracle` (`spikes/editor-toolkit/round1-oracle`). Governed by \
|
||||
`spec/CONTRACT_EDITOR_T4_SPIKE.md` Revision 6, Round 1 (\"criterion 1, compound-path \
|
||||
fill correctness\"). No rendering crate was used to produce this file — every point \
|
||||
below is derived from the typed `PathCommand` outline alone.\n\n\
|
||||
Two check classes, testing two different properties: **bounded-hole** (`gClef`, \
|
||||
`timeSig8`, `accidentalFlat`, `noteheadHalf`) requires ≥3 ink and ≥3 must-be-background \
|
||||
points inside a bounded hole; **disjoint-component** (`fClef`) has no bounded hole by \
|
||||
design and instead requires ≥1 ink point inside each of its three filled subpaths, \
|
||||
tagged with `subpath_index`.\n"
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
writeln!(s, "## Render transform (pinned)\n").unwrap();
|
||||
writeln!(
|
||||
s,
|
||||
"`{}`\n\n- Flatten tolerance: `{}` staff-space units (Bezier chord deviation).\n\
|
||||
- Clearance floor: `{}` device px.\n",
|
||||
oracle.render_transform_rule,
|
||||
oracle.flatten_tolerance_staff_space,
|
||||
oracle.clearance_floor_device_px
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
writeln!(s, "## Overall status\n").unwrap();
|
||||
writeln!(s, "| glyph | requirement | satisfied | ring_signed_areas |").unwrap();
|
||||
writeln!(s, "|---|---|---|---|").unwrap();
|
||||
for g in &oracle.glyphs {
|
||||
let requirement = match g.requirement {
|
||||
Requirement::BoundedHole => "BoundedHole",
|
||||
Requirement::DisjointComponents => "DisjointComponents",
|
||||
};
|
||||
let areas = g
|
||||
.ring_signed_areas
|
||||
.iter()
|
||||
.map(|a| format!("{a:.3}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
writeln!(
|
||||
s,
|
||||
"| `{}` | {} | **{}** | [{}] |",
|
||||
g.name, requirement, g.satisfied, areas
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
writeln!(
|
||||
s,
|
||||
"\n`fClef` is `satisfied = true` with zero background points — that is the correct, \
|
||||
designed-for outcome for its `DisjointComponents` requirement class, not a shortfall. \
|
||||
The `satisfied` column above is the field to read; `background_satisfied` alone would \
|
||||
make that correct outcome indistinguishable from a bounded-hole glyph's genuine failure.\n"
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if !findings.is_empty() {
|
||||
writeln!(s, "## Findings\n").unwrap();
|
||||
for f in findings {
|
||||
writeln!(s, "- **{f}**").unwrap();
|
||||
}
|
||||
writeln!(s).unwrap();
|
||||
}
|
||||
|
||||
for g in &oracle.glyphs {
|
||||
writeln!(s, "## `{}`\n", g.name).unwrap();
|
||||
let requirement = match g.requirement {
|
||||
Requirement::BoundedHole => "BoundedHole (≥3 ink, ≥3 background inside a bounded hole)",
|
||||
Requirement::DisjointComponents => {
|
||||
"DisjointComponents (no background requirement; ≥1 ink point per filled subpath)"
|
||||
}
|
||||
};
|
||||
let subpath_note = match g.expected_subpath_count {
|
||||
Some(exp) if exp == g.subpath_count => format!("matches recorded {exp}"),
|
||||
Some(exp) => format!("**MISMATCH** — recorded {exp}, parsed {}", g.subpath_count),
|
||||
None => "no recorded expectation".to_string(),
|
||||
};
|
||||
let areas = g
|
||||
.ring_signed_areas
|
||||
.iter()
|
||||
.map(|a| format!("{a:.3}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
writeln!(
|
||||
s,
|
||||
"- Requirement class: {requirement}\n\
|
||||
- Overall status: **satisfied = {}**\n\
|
||||
- Subpaths: {} ({subpath_note})\n\
|
||||
- Ring signed areas (staff-space², shoelace, ring order): `[{areas}]`\n\
|
||||
- Bounding box (staff space): `{:?}`\n\
|
||||
- Outer contour ring index: {}\n\
|
||||
- Transform: scale={}, tx={:.3}, ty={:.3}, target={}x{}\n\
|
||||
- background_required={}, background_satisfied={}\n\
|
||||
- subpath_coverage_required={}, subpath_coverage_satisfied={}\n\
|
||||
- Ink candidates found (>= {}px clearance): {}\n\
|
||||
- Background/hole candidates found (>= {}px clearance): {}\n\
|
||||
- Ink point spacing relaxed: {}\n\
|
||||
- Background point spacing relaxed: {}\n",
|
||||
g.satisfied,
|
||||
g.subpath_count,
|
||||
g.bbox_staff,
|
||||
g.outer_contour_ring_index,
|
||||
g.transform.scale,
|
||||
g.transform.tx,
|
||||
g.transform.ty,
|
||||
g.transform.target_width as i32,
|
||||
g.transform.target_height as i32,
|
||||
g.background_required,
|
||||
g.background_satisfied,
|
||||
g.subpath_coverage_required,
|
||||
g.subpath_coverage_satisfied,
|
||||
oracle.clearance_floor_device_px,
|
||||
g.ink_candidates_found,
|
||||
oracle.clearance_floor_device_px,
|
||||
g.background_candidates_found,
|
||||
g.ink_spacing_relaxed,
|
||||
g.background_spacing_relaxed,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
writeln!(
|
||||
s,
|
||||
"| class | subpath_index | staff (x, y) | device (x, y) | clearance (device px) | hole evidence |"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(s, "|---|---|---|---|---|---|").unwrap();
|
||||
for p in &g.points {
|
||||
let class = match p.class {
|
||||
SampleClass::Ink => "Ink",
|
||||
SampleClass::Background => "Background",
|
||||
};
|
||||
let subpath = match p.subpath_index {
|
||||
Some(i) => i.to_string(),
|
||||
None => "-".to_string(),
|
||||
};
|
||||
let evidence = match &p.hole_evidence {
|
||||
Some(ev) => format!(
|
||||
"inside_outer_contour={}, even_odd_filled={}, nonzero_filled={} (outer ring {})",
|
||||
ev.inside_outer_contour, ev.even_odd_filled, ev.nonzero_filled, ev.outer_contour_ring_index
|
||||
),
|
||||
None => "-".to_string(),
|
||||
};
|
||||
writeln!(
|
||||
s,
|
||||
"| {class} | {subpath} | ({:.4}, {:.4}) | ({:.2}, {:.2}) | {:.3} | {evidence} |",
|
||||
p.staff.0, p.staff.1, p.device.0, p.device.1, p.clearance_device_px
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
writeln!(s).unwrap();
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
Loading…
Reference in New Issue