diff --git a/TODO.md b/TODO.md index b102042..52d22c5 100644 --- a/TODO.md +++ b/TODO.md @@ -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 pass has now been checked against a live Monastic query; most needed correction (confirmed Roman-rite sourced), a handful already matched. -- **All ~52 temporal entries: done** (see the section above for method and - the one deliberate exception, `christmas-octave-sunday`). +- **All ~52 temporal entries: done, including `christmas-octave-sunday`** + (see the section above for method and its own correction). - **94 `propers: null` saints: not started** — need a collect authored (a 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 `hours/resolve-common.ts`'s `splitNamedAntiphon` always marks its output diff --git a/src/data/calendar/temporal-categories.yml b/src/data/calendar/temporal-categories.yml index 295faf0..946a0a7 100644 --- a/src/data/calendar/temporal-categories.yml +++ b/src/data/calendar/temporal-categories.yml @@ -52,7 +52,21 @@ bySeason: # Canterbury, Semiduplex, wins outright within the Octave (Dec 29) in the # real Monastic 1617 engine, which `privileged-feria`'s Duplex+ threshold # 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 } septuagesima: { sunday: privileged-sunday, feria: ordinary-feria } lent: { sunday: privileged-sunday, feria: ordinary-feria } diff --git a/tests/calendar/december-sanctoral.test.ts b/tests/calendar/december-sanctoral.test.ts index f965b37..756dcd1 100644 --- a/tests/calendar/december-sanctoral.test.ts +++ b/tests/calendar/december-sanctoral.test.ts @@ -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', () => { // Same clean year (2033) as above, walking the whole week to show the // accumulation directly -- this is the concrete shape of "commemorations