epiphany/spec/CONTRACT_P13S6_PROMOTION.md

252 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Contract: P13-S6 promotion — the tuning constructions become normative
Repo root `/home/jeans/Repos/active/epiphany`. Read this file in full before
editing anything. The rulings below are **granted**; they are law for this task,
not proposals.
You are promoting reviewed, ratified content into the specification. This is a
transcription task with three small additions. The hard part is not deciding
anything — it is not silently changing anything.
## Blast radius
You edit exactly two files, plus one rebuilt artifact:
* `spec/core_spec.tex`
* `spec/core_spec.pdf` (rebuilt, not hand-edited)
* `crates/epiphany-testkit/tests/requirement_labels.rs` — **three integer
constants only**, at lines 1214. Nothing else in that file.
You write no other Rust. You touch no other document, no other companion
`.tex`, and no other agent's file.
## Source of truth, and the clause that matters most
`spec/DRAFT_P13S6_TEMPERAMENTS.md` is the reviewed draft. Its head carries a
block titled **"RATIFIED 2026-07-22"**. Read that block first; it supersedes
the draft's own §1113 wherever they disagree.
**The draft's arithmetic is settled. Every closure sum, wolf size, and ratio in
Section A has been independently recomputed by a reviewer and confirmed.**
If you believe you have found an error in it: **report it, do not fix it.**
Stop and say so in your report. A silent "correction" to a verified
construction is the single most damaging thing you can do here, because it
would arrive wearing the same clothes as the verification. The draft already
survived one round in which three properly-cited entries turned out to be
arithmetically impossible; the arithmetic is what caught them, and the
arithmetic has now been run. Your disagreement is a finding, not an edit.
## Part 1 — the ten pinned constructions
`\subsection{Built-in Tuning Systems}` (`core_spec.tex:3593`) holds a
`longtable` of 20 identifiers with one-line descriptions, followed by
`req:tuning:builtin-tuning-catalog`, which already says *"The semantics
specified above are normative."* Ten of the fourteen non-`tet-*` rows are bare
names; Section A of the draft specifies them.
Add a new `\subsection`**between** `req:tuning:builtin-tuning-catalog` and
`\subsection{Default Score Configuration}` (`core_spec.tex:3633`) — carrying the
normative construction for each of: `pythagorean`; `meantone-1/4-comma`,
`meantone-1/5-comma`, `meantone-1/6-comma`; `werckmeister-iii`,
`werckmeister-iv`; `vallotti`; `kirnberger-ii`, `kirnberger-iii`; `young-ii`.
For each, carry over from the draft:
* the generative rule — which fifths are tempered, by what fraction of **which**
comma (the draft names syntonic vs Pythagorean explicitly for every entry;
preserve that distinction exactly, it is the classic error);
* the wolf or chain placement where the construction does not force it —
`pythagorean` in particular states its cut as a *choice*, and it must read as
a choice in the spec too;
* the exact ratios where the construction yields rationals, and the derived
cents alongside, marked as derived;
* the closure statement — the twelve-fifths sum for the circulating
temperaments, and the wolf residue for the non-circulating ones.
Keep the closure sums. They are not decoration: they let a reader check the
construction without leaving the page, and they are what makes this section
self-verifying.
**Do not carry over:** the `verified`/`recalled` confidence tags, the URLs, the
"corrected this round" revision commentary, or any bibliography. The
temperament's *name* is its attribution, and the closure sum is its warrant.
This is deliberate — transcribing a citation you did not check is how a
fabricated source enters a normative document, and there is no reason to run
that risk when the arithmetic stands on its own.
**Do not add a `\begin{requirement}` block for any of the ten.** The existing
`req:tuning:builtin-tuning-catalog` already makes this text normative.
## Part 2 — the 5-limit construction (one new requirement)
Add `req:tuning:ji-static-construction`. It states:
The static 5-limit just-intonation systems are constructed from the contiguous
lattice block $\{3^a 5^b \mid a \in [-1,2],\ b \in [-1,1]\}$ — twelve cells,
octave-reduced, assigned in ascending order to the twelve chromatic positions
of `cmn-12` starting from the anchor, which takes the role of $1/1$. The block
is generated by its bounds; nothing is selected and nothing is discarded.
The twelve, anchor-relative (position 0 is the anchor):
| step | cell | ratio | cents (derived) |
|---|---|---|---|
| 0 | $3^{0}5^{0}$ | 1/1 | 0.000 |
| 1 | $3^{-1}5^{-1}$ | 16/15 | 111.731 |
| 2 | $3^{2}5^{0}$ | 9/8 | 203.910 |
| 3 | $3^{1}5^{-1}$ | 6/5 | 315.641 |
| 4 | $3^{0}5^{1}$ | 5/4 | 386.314 |
| 5 | $3^{-1}5^{0}$ | 4/3 | 498.045 |
| 6 | $3^{2}5^{1}$ | 45/32 | 590.224 |
| 7 | $3^{1}5^{0}$ | 3/2 | 701.955 |
| 8 | $3^{0}5^{-1}$ | 8/5 | 813.686 |
| 9 | $3^{-1}5^{1}$ | 5/3 | 884.359 |
| 10 | $3^{2}5^{-1}$ | 9/5 | 1017.596 |
| 11 | $3^{1}5^{1}$ | 15/8 | 1088.269 |
`ji-static-5limit-C`, `ji-static-5limit-G`, and `ji-static-5limit-D` are this
one construction at three anchors. The catalog keeps exactly those three;
additional anchors are an implementation extension and **MUST NOT** be read as
a conformance obligation.
Normalize the three catalog descriptions (`core_spec.tex:3617-3619`) to the same
form — *"anchored to X tonic"* — since today only the C row says "tonic".
**Banned vocabulary, throughout.** Never write "asymmetric scale", "symmetric
scale 1", or "symmetric scale 2". Those names come from a framing this ruling
rejects (a 5×3 grid with three cells discarded), and the word "symmetric" in
that framing describes the shape of the *discarded cells*, not any property of
the resulting scale. Do not claim any of these scales is inversionally
symmetric: none is, because $45/32$ is present and $64/45$ is not.
## Part 3 — adaptive tuning (two new requirements, plus two amendments)
### 3a. `req:tuning:adaptive-default-version`
`ji-adaptive-5limit` resolves to `TuningResolution::Adaptive` with function
identity **`"default-v1"`** — the version is part of the machine-visible
identifier string, not prose beside it.
Version 1 is a pure function of (position, anchor pitch class):
* resolve the position through `req:tuning:ji-static-construction` transposed so
the anchor is $1/1$;
* the anchor is the tonal centre supplied by the harmonic context; **C (chromatic
position 0) when none is supplied**;
* `concurrent`, `recent`, `hints`, `parameters`, and mode are **ignored** in
version 1 — state this explicitly, as `req:pitch:spelling-algorithm` does for
its own version 1, so that consuming them later is a new version rather than a
silent behaviour change;
* every result derives from the anchor and the reference pitch. No adjustment is
ever carried forward from a previous resolution, so the construction is
comma-drift-free by shape rather than by a correction step.
An unregistered or unknown `AdaptiveTuningFunctionId` **MUST** be a hard error.
There is no silent fallback. `AdaptiveTuningFunctionId` remains an extension
point and other functions may be registered — but the built-in
`ji-adaptive-5limit` is bound to `"default-v1"` and that binding cannot be
overridden.
### 3b. `req:tuning:adaptive-anchor-derivation`
Where a harmonic context is constructed from a score graph, the anchor is
derived from the prevailing key signature:
```
anchor_pc = (7 × fifths) mod 12
```
reading `fifths` as the major tonic — mode is not consulted, so a signature of
0 anchors at C whether the music is C major or A minor. `KeySignature` is
already defined (`core_spec.tex:4892`) with `fifths` validated to 7..=+7.
State the two things that rule leaves open, because `key_sequence`
(`core_spec.tex:4845`) is **per-staff and time-anchored**: the signature in
effect is the one on the staff containing the pitch, at the latest
`KeySignatureChange` whose anchor is at or before that pitch's onset. Without
both, a polytonal or modulating score resolves differently in two conforming
implementations, which `req:tuning:tuning-resolution-determinism` forbids.
### 3c. Amend the Adaptive Tuning prose
`core_spec.tex:3364-3368` promises that "the frequency of a given E depends on
whether it is the major third of a C chord, the perfect fifth of an A chord, or
a passing tone". That describes what an adaptive function **may** do. It is not
what the built-in does at version 1, and left as-is it reads as a promise the
normative text does not keep.
Rewrite it so per-sonority interpretation is described as within the scope of
registered adaptive functions generally, and note that `ji-adaptive-5limit`
version 1 consumes only the tonal centre. Do **not** weaken
`req:tuning:adaptive-tuning-purity` — it already permits the narrower algorithm,
since requiring a pure function of position and context never required consuming
every context field.
### 3d. Correct the forward reference
`core_spec.tex:3649-3653` says `KeyContext` is *"partially defined in
Chapter~\ref{ch:graph} and completed in the audio engine specification (out of
scope for this document)"*. `KeyContext` is not defined anywhere in this
specification, and no type of that name exists in the implementation.
Keep it out of scope — but replace the vague claim with the interface obligation
that Part 3 actually depends on: whatever completes `KeyContext` **MUST** expose
a tonal-centre chromatic pitch class consistent with
`req:tuning:adaptive-anchor-derivation`. Chapter 10 defines `KeySignature`, not
`KeyContext`; say that accurately.
`ContextHint` and `AdaptiveTuningParameters` stay undefined. Say so explicitly
rather than leaving the silence ambiguous. Version 1 ignores both, and inventing
them now would freeze a type surface on a chapter that has no consumer.
## Counts and verification
Three new requirements, so three constants move in
`crates/epiphany-testkit/tests/requirement_labels.rs`:
* `CORE_REQUIREMENT_COUNT` 209 → **212**
* `SUITE_REQUIREMENT_COUNT` 279 → **282**
* `SUITE_LABEL_COUNT` 279 → **282**
If your build produces different numbers, **stop and report** — do not tune a
constant until a test passes. That inverts what the constant is for.
Requirement numbering is automatic: the box steps a plain counter via
`code={\refstepcounter{requirement}}` (`core_spec.tex:245`), so inserting
requirements mid-document renumbers later ones in the rendered PDF while every
`\label` keeps resolving correctly. You do not maintain numbers by hand, and
you **MUST NOT** rename or move any existing `\label` — they are cited by code,
tests, and decision records.
Do not add code citations for the three new requirements. Nothing implements
them yet; that lands in a later Push 4b step.
Then:
1. `cargo test -p epiphany-testkit --test requirement_labels` → 6 passed.
2. Rebuild with `latexmk -xelatex` **twice**. Grep the log for `^! `,
`Undefined control sequence`, and `Reference .* undefined`, and report the
actual counts. All must be zero.
3. Full workspace gate: `cargo fmt --all --check`; `cargo clippy --workspace
--all-targets` → 0 warnings; `cargo test --workspace`;
`cargo run -q -p epiphany-testkit --example conformance_suite` → 8/8. Zero
golden churn is expected — report it if not.
Report the actual commands and their actual output. On this project an agent
once reported "verification passes" while errors pointed into its own file, and
another silently deleted a struct field while rewriting the comment above it —
so **re-read every listing and table you edit, in full, after editing it**, and
confirm nothing adjacent vanished.
## Do not
* Decide anything. All four ratifications are made; if you find a fifth
question, report it unanswered.
* Edit any construction in Section A of the draft, or "improve" its arithmetic.
* Carry a URL, a confidence tag, or a bibliography into `core_spec.tex`.
* Use the words "asymmetric scale", "symmetric scale 1", or "symmetric scale 2".
* Add a registry field, a `Codec` impl, or any Rust type. Part 3 is spec-only.
* Edit `binary_format.tex`, `operation_catalog.tex`, or any other companion.
* Run `cargo fmt --all`.