Fix Christmas Octave Sunday's occurrence precedence (was backwards)
Deploy / deploy (push) Successful in 51s

christmastide's Sunday was coded privileged-sunday (Advent's own tier),
which live-checking against the reference engine shows is wrong: on
every one of the three real collision dates (St. Stephen/John/Holy
Innocents, all Duplex-II-classis, falling on the Sunday within the
Octave) the saint wins outright and the Sunday is merely commemorated --
the reverse of what privileged-sunday would produce (Sunday stays
primary, saint just gets a nod). St. Silvester (plain Duplex, Dec 31)
confirms the same live. Reclassified to ordinary-sunday, which matches
all four checked cases: Duplex+ wins outright (Stephen/John/Innocents/
Silvester), Semiduplex still transfers off the Sunday rather than
winning (St. Thomas Becket, unaffected by the fix either way), and a day
with no saint at all resolves to the Sunday cleanly.

Found while pulling christmas-octave-sunday's own Benedictus antiphon
from the reference engine's dedicated Tempora/Nat1-0 proper and noticing
this app's own occurrence resolution didn't match what the live engine
showed for the Sunday's real collision dates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 06:30:46 -04:00
parent 73672853ef
commit ff81a6fe52
3 changed files with 90 additions and 3 deletions
+19 -2
View File
@@ -213,11 +213,28 @@ Urban I/Eleutherius/John I do have content -- they just needed a very
late test year). Every pre-existing antiphon file that predated this late test year). Every pre-existing antiphon file that predated this
pass has now been checked against a live Monastic query; most needed pass has now been checked against a live Monastic query; most needed
correction (confirmed Roman-rite sourced), a handful already matched. correction (confirmed Roman-rite sourced), a handful already matched.
- **All ~52 temporal entries: done** (see the section above for method and - **All ~52 temporal entries: done, including `christmas-octave-sunday`**
the one deliberate exception, `christmas-octave-sunday`). (see the section above for method and its own correction).
- **94 `propers: null` saints: not started** — need a collect authored (a - **94 `propers: null` saints: not started** — need a collect authored (a
bigger, separate task) before an antiphon id even has anywhere to attach. bigger, separate task) before an antiphon id even has anywhere to attach.
### Bug found and fixed while pulling christmas-octave-sunday's antiphon
`data/calendar/temporal-categories.yml` had christmastide's own Sunday
coded `privileged-sunday` (Advent's tier — never displaced short of
Duplex-majus+, and even then only commemorated in return). Live-checking
all three Duplex-II-classis "Comites Christi" collision dates (St.
Stephen/John/Holy Innocents falling on the Sunday, plus St. Silvester,
plain Duplex) showed the exact reverse: the saint wins outright every
time, the Sunday only commemorated — `ordinary-sunday`'s rule, not
`privileged-sunday`'s. Fixed by reclassifying christmastide's `sunday` to
`ordinary-sunday` (the `feria` side, `privileged-feria-minor`, was
already correct and untouched). New tests in
`tests/calendar/december-sanctoral.test.ts` cover both directions: the
saint winning on the three verified collision dates, and St. Thomas
Becket (Semiduplex) still transferring off the Sunday rather than
winning, unchanged by the fix.
### Bug found while checking February's results, not yet fixed ### Bug found while checking February's results, not yet fixed
`hours/resolve-common.ts`'s `splitNamedAntiphon` always marks its output `hours/resolve-common.ts`'s `splitNamedAntiphon` always marks its output
+15 -1
View File
@@ -52,7 +52,21 @@ bySeason:
# Canterbury, Semiduplex, wins outright within the Octave (Dec 29) in the # Canterbury, Semiduplex, wins outright within the Octave (Dec 29) in the
# real Monastic 1617 engine, which `privileged-feria`'s Duplex+ threshold # real Monastic 1617 engine, which `privileged-feria`'s Duplex+ threshold
# would wrongly forbid. # would wrongly forbid.
christmastide: { sunday: privileged-sunday, feria: privileged-feria-minor } #
# The Sunday itself is `ordinary-sunday`, NOT `privileged-sunday` —
# originally miscoded as privileged-sunday (Advent's own Sundays' tier),
# which was backwards: live-verified against all six possible collision
# dates (Dec 26 St. Stephen, 27 St. John, 28 Holy Innocents — all
# Duplex-II-classis — plus 31 St. Silvester, plain Duplex), every one of
# them wins outright over the Sunday, which is merely commemorated in
# return — exactly ordinary-sunday's Duplex+ rule, and the reverse of
# privileged-sunday's (which would keep the Sunday primary and only nod
# to the saint). Dec 29 (St. Thomas Becket, Semiduplex) still transfers
# off the Sunday rather than winning, and a day with no saint at all
# (e.g. Dec 30) still resolves to the Sunday cleanly — both of those
# match ordinary-sunday too, so nothing here actually depended on the
# stronger tier.
christmastide: { sunday: ordinary-sunday, feria: privileged-feria-minor }
epiphanytide: { sunday: ordinary-sunday, feria: ordinary-feria } epiphanytide: { sunday: ordinary-sunday, feria: ordinary-feria }
septuagesima: { sunday: privileged-sunday, feria: ordinary-feria } septuagesima: { sunday: privileged-sunday, feria: ordinary-feria }
lent: { sunday: privileged-sunday, feria: ordinary-feria } lent: { sunday: privileged-sunday, feria: ordinary-feria }
+56
View File
@@ -108,6 +108,62 @@ describe('December sanctoral pull (first pass, 12/12)', () => {
]); ]);
}); });
it('the Christmas Octave Sunday loses outright to a Duplex+ Comites Christi feast when the two collide -- ordinary-sunday, not privileged-sunday', () => {
// Corrects a real bug: christmastide's Sunday was originally coded
// privileged-sunday (Advent's own tier), which would have kept the
// Sunday primary here with the saint merely commemorated -- backwards
// from the live Monastic 1617 engine, which was checked directly
// against all three Duplex-II-classis collision dates and shows the
// saint winning outright every time, the Sunday only commemorated.
const stephenSunday = resolveDay('2021-12-26');
expect(stephenSunday.winner).toEqual({
kind: 'sanctoral',
id: 'st-stephen-protomartyr',
name: 'St. Stephen, Protomartyr',
rank: 'duplex-2-classis',
});
expect(stephenSunday.commemorations).toContainEqual({ kind: 'temporal', id: 'christmas-octave-sunday' });
const innocentsSunday = resolveDay('2025-12-28');
expect(innocentsSunday.winner).toEqual({
kind: 'sanctoral',
id: 'holy-innocents',
name: 'The Holy Innocents, Martyrs',
rank: 'duplex-2-classis',
});
expect(innocentsSunday.commemorations).toContainEqual({ kind: 'temporal', id: 'christmas-octave-sunday' });
// A plain Duplex (not just Duplex-II-classis) is still enough: St.
// Silvester (Dec 31) wins outright the same way when it falls on the
// Sunday, also live-verified.
const silvesterSunday = resolveDay('2023-12-31');
expect(silvesterSunday.winner).toEqual({
kind: 'sanctoral',
id: 'st-silvester-i',
name: 'St. Silvester I, Pope and Confessor',
rank: 'duplex',
});
expect(silvesterSunday.commemorations).toContainEqual({ kind: 'temporal', id: 'christmas-octave-sunday' });
});
it('the Christmas Octave Sunday still keeps a lesser feast from winning, same as before the ordinary-sunday fix', () => {
// St. Thomas Becket (Semiduplex, Dec 29) transfers off the Sunday
// rather than winning it -- unaffected by the privileged->ordinary
// change, since both tiers transfer a Semiduplex here; this test
// guards against a regression the other direction.
const beckettSunday = resolveDay('2024-12-29');
expect(beckettSunday.winner.kind).toBe('temporal');
expect(beckettSunday.commemorations.some((c) => c.kind === 'sanctoral')).toBe(false);
// St. Thomas still gets his day -- pushed to the next open one.
const transferred = resolveDay('2024-12-30');
expect(transferred.winner).toEqual({
kind: 'sanctoral',
id: 'st-thomas-becket',
name: 'St. Thomas of Canterbury, Bishop and Martyr',
rank: 'semiduplex',
});
});
it('the Christmas Octave commemorations build up day by day as each Comites Christi feast starts its own octave', () => { it('the Christmas Octave commemorations build up day by day as each Comites Christi feast starts its own octave', () => {
// Same clean year (2033) as above, walking the whole week to show the // Same clean year (2033) as above, walking the whole week to show the
// accumulation directly -- this is the concrete shape of "commemorations // accumulation directly -- this is the concrete shape of "commemorations