calendar: fix transferred candidates blindly winning outright

Found via real data (the May sanctoral pull): a transfer landing on a day
whose own native winner was temporal blindly took over as winner,
regardless of that day's own precedence category — so a transferred-in
Simplex saint (St. Felix I, impeded by Pentecost's Ember Saturday) was
overwriting Trinity Sunday itself instead of being merely commemorated,
the way a native Simplex candidate on an ordinary Sunday already
correctly is.

Fixes applyIncomingTransfer to run the arriving candidate through
decideOccurrence using the receiving day's own temporalCategory, the same
rules a native occurrence there would use, instead of assuming an empty
temporal slot means automatic victory.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 17:25:27 -04:00
parent 88074cdfe6
commit f0e32ae0c1
3 changed files with 38 additions and 2 deletions
+11 -1
View File
@@ -59,7 +59,17 @@ function applyIncomingTransfer(
return winner; return winner;
} }
if (winner.kind === 'temporal') { if (winner.kind === 'temporal') {
return { kind: 'sanctoral', id: candidate.id, name: candidate.name, rank: candidate.rank }; // Found via real data: a transferred-in Simplex saint was blindly
// winning outright even when it landed on a privileged Sunday (Trinity
// Sunday, specifically) — the arriving candidate needs to go through
// the *same* precedence rules a native occurrence would have used,
// not just take over because nothing else was assigned here.
const decided = decideOccurrence(temporalCategory, winner.id, candidate);
commemorations.push(...decided.commemorations);
// If the candidate fails here too (decided.transfer set), that's a
// transfer chain — not modeled, same "not delivered rather than
// guessed at" stance as the privileged-feria-major case above.
return decided.winner;
} }
const collision = resolveCollision(candidate, winner); const collision = resolveCollision(candidate, winner);
commemorations.push(...collision.commemorations); commemorations.push(...collision.commemorations);
+5 -1
View File
@@ -11,7 +11,11 @@ describe('getDayLabel — ordinal temporal label', () => {
}); });
it('names the anchor day itself, not "day after itself"', () => { it('names the anchor day itself, not "day after itself"', () => {
expect(getDayLabel(resolveDay('2026-05-31'))).toBe('Trinity Sunday'); // St. Felix I (May 30, Simplex) is impeded by that date's own Pentecost
// Ember Saturday and transfers forward into Trinity Sunday, where he's
// commemorated (not displacing the Sunday) — real sanctoral content
// now populates this date, so the label reflects both, feast name first.
expect(getDayLabel(resolveDay('2026-05-31'))).toBe('St. Felix I, Pope and Martyr — Trinity Sunday');
expect(getDayLabel(resolveDay('2026-04-05'))).toBe('Easter'); expect(getDayLabel(resolveDay('2026-04-05'))).toBe('Easter');
expect(getDayLabel(resolveDay('2026-02-18'))).toBe('Ash Wednesday'); expect(getDayLabel(resolveDay('2026-02-18'))).toBe('Ash Wednesday');
}); });
+22
View File
@@ -31,4 +31,26 @@ describe('transfer mechanism (resolveDay integration)', () => {
rank: 'duplex-2-classis', rank: 'duplex-2-classis',
}); });
}); });
// Found via real data (the May sanctoral pull): a transferred-in
// candidate was blindly winning outright whenever the receiving day's
// own native winner was temporal, regardless of that day's own
// precedence category — so a transferred-in Simplex was overwriting
// Trinity Sunday itself instead of going through the same ordinary-
// Sunday rule a native candidate would have.
it('a simplex saint impeded by a privileged feria transfers forward and is merely commemorated, not made to win, on the ordinary Sunday it lands on', () => {
// St. Felix I (May 30, Simplex) is impeded by that date's own
// Pentecost Ember Saturday (privileged-feria-major) and transfers
// forward into Trinity Sunday 2026 (May 31, an ordinary Sunday).
const saturday = resolveDay('2026-05-30');
const sunday = resolveDay('2026-05-31');
expect(saturday.winner).toEqual({ kind: 'temporal', id: 'pentecost-sunday' });
expect(saturday.commemorations).toEqual([]);
expect(sunday.winner).toEqual({ kind: 'temporal', id: 'post-pentecost-01' });
expect(sunday.commemorations).toEqual([
{ kind: 'sanctoral', id: 'st-felix-i', name: 'St. Felix I, Pope and Martyr', rank: 'simplex' },
]);
});
}); });