# Handel *Dixit Dominus* HWV 232 — Stage-1 direct-PDF parser
## Canonical parser v4

**Status:** canonical parser infrastructure from Movement 9 onward  
**Adopted:** 8 Oct 2026  
**Executable:** `dixit_stage1_parser_v4_CANONICAL.py`  
**Regression suite:** `test_dixit_stage1_parser_v4_regression.py`

This document is authoritative for the **literal score-extraction / Stage-1
parser layer**. It does not define vocal renderer phonemes, performing-edition
overrides, piano reduction, or player balance.

The parser's job is to establish what the selected score source actually prints
before any downstream interpretation is added.

---

## 1. Canonical ordering

The fundamental v4 rule is that movement/system ownership is resolved **before**
clef, key, accidental, lyric or note state is interpreted.

```text
PDF page
  -> identify score systems
  -> retain only systems belonging to selected movement
  -> bind named vocal staves within retained system
  -> bind clef/key glyphs to staff + retained-system geometry
  -> decode printed opening key signature
  -> require exact vocal-staff consensus
  -> parse pitches/rhythm/ties/lyrics
```

A page is not a musical-state boundary. Two movements can occupy different
systems on the same PDF page.

---

## 2. Printed key signature: never inherit, never fall back

The movement profile deliberately contains **no key signature**.

For every named vocal staff independently:

1. locate that staff's printed clef;
2. identify the key-signature corridor between clef and time signature / first
   musical event;
3. read accidental glyphs in that corridor;
4. convert vertical staff position to a note letter using the active clef;
5. compare the resulting semantic letter→accidental map across all named vocal
   staves.

The parse continues only if all named vocal staves agree exactly.

If the signature is absent, ambiguous, conflicting, or contaminated by another
system, **abort**. There is no configured or inherited fallback.

---

## 3. Clef/key glyph ownership is two-dimensional

Nearest-staff assignment alone is insufficient on a page containing multiple
score systems.

A candidate clef/key glyph belongs to a vocal staff only when:

```text
nearest staff == candidate staff
AND
vertical distance from candidate staff <= canonical system-proximity limit
```

This rule must also be used conceptually by instrumental extraction: a clef on
another score system cannot own notes merely because it is the closest clef on
the page in one dimension.

---

## 4. Shared-page regression: PDF page 51

Philip Legge's score gives the project a particularly strong regression case:
Movement 8 ends in the upper system of PDF page 51 and Movement 9 begins in the
lower system of the same page.

### Movement 8 — *Conquassabit*

```text
profile pages:        48-51
p51 selection:        TOP system only
vocal topology:       Soprano I / Soprano II / Alto / Tenor / Bass
metre:                3/4
last bar:             50
printed key:          B-flat only
named-staff consensus: 5/5
systems:              7
vocal events:         821
lyric fragments:      362 / 362 assigned
continuation notes:   310
classified ties:      0
core issues:          0
unmapped glyphs:      0
```

### Movement 9 — *De torrente in via*

```text
profile pages:        51-54
p51 selection:        LOWER system only
vocal topology:       Soprano I / Soprano II / Tenor / Bass
metre:                4/4
last bar:             36
printed key:          B-flat + E-flat
named-staff consensus: 4/4
systems:              7
vocal events:         414
lyric fragments:      233 / 233 assigned
continuation notes:   66 (16 tied, 50 melismatic)
classified ties:      17
cross-system ties:    2
core issues:          0
unmapped glyphs:      0
```

The two cross-system Movement-9 ties are Soprano II bar 5→6 G4 and bar 26→27
C5.

---

## 5. Frozen byte-level regression hashes

The v4 regression suite requires exact SHA-256 identity for the principal
extraction CSVs. These hashes establish that the infrastructure promotion does
not alter already-validated musical extraction.

### Movement 8

```text
2c9674dae77674d0509c8da2ee0d2c584e199dca963b27081f8a7286b3f50b42  dixit-mvt8-vocals-v4.csv
f8d915b0266984d98e2716979115a669ae6ce509f924d73d2c09ee1ac4663504  dixit-mvt8-lyrics-v4.csv
092ffe3ade647cb685be7279771d235840a12e751aac55303b147c9de28a4e1e  dixit-mvt8-syllables-v4.csv
c5ba9a4c52cfe96fe601257064fd10a00584aa9567f38e0ddc62257afacc17cc  mvt8-key-signature-audit.csv
```

The first three files are byte-identical to the validated M8-v3 extraction
counterparts after filename/schema promotion.

### Movement 9

```text
dd404fc078a6145288c91fcfd9a91e1c5caf4085010c47c03632956abeb2ad88  dixit-mvt9-vocals-v4.csv
a5cac5f09d700af9a8daf2e771ece56e92a2193dca251cd0dd7d5f03a4cacec7  dixit-mvt9-lyrics-v4.csv
7dda1cab37dec2d00253eb3c457cf959b55720777bec5c9cd01184c155a5b27c  dixit-mvt9-syllables-v4.csv
cb080446a54644f27838ea4c55104bdf1b7c3243acda96396bba76ceac6c2035  mvt9-key-signature-audit.csv
```

The first three files are byte-identical to the validated M9-v1 Stage-1
extraction counterparts.

---

## 6. Canonical profiles are explicit, but musical state is still read from score

A profile may contain structural facts required to delimit the parse:

```text
movement number/title
PDF page span
shared-page system slice
named vocal topology
metre / expected final bar
```

A profile must **not** contain a guessed key signature used to rescue parsing.
The key is score-derived state.

The current executable freezes M8 and M9 as profiles because they form the
shared-page regression pair. Future movement profiles should be added only after
movement boundaries/topology are established and then frozen by regression.

---

## 7. Rhythm closure is necessary, not sufficient

Zero bar-closure errors validate durations/topology only. They do not validate:

- key signature;
- clef ownership;
- movement/system ownership;
- lyric assignment;
- edition correctness.

The canonical QC layers remain separate.

---

## 8. Running the parser

```bash
python3 dixit_stage1_parser_v4_CANONICAL.py \
  IMSLP13037-232dixit-philip-legge.pdf \
  --movement 9 \
  --no-render
```

For the frozen M8 regression:

```bash
python3 dixit_stage1_parser_v4_CANONICAL.py \
  IMSLP13037-232dixit-philip-legge.pdf \
  --movement 8 \
  --no-render
```

Preview tempo defaults to 100 BPM and can be overridden with `--tempo`. The
literal score extraction is not changed by preview tempo.

---

## 9. Running the regression suite

```bash
python3 test_dixit_stage1_parser_v4_regression.py \
  dixit_stage1_parser_v4_CANONICAL.py \
  IMSLP13037-232dixit-philip-legge.pdf
```

A successful run must report both movement passes and the explicit shared-page
boundary pass.

---

## 10. Promotion rule

`dixit_stage1_parser_v4_CANONICAL.py` supersedes the M8-specific v3 parser as
the **infrastructure starting point for subsequent movements**.

The older movement-specific parsers remain historical/frozen reconstruction
artifacts. Do not rewrite locked Movement-8 production data merely because the
shared parser implementation has been promoted.
