Compare commits

..

2 Commits

Author SHA1 Message Date
Alexander Payne
304c7697be feat: Tempus Caeleste celestial timestamps (#1705)
Some checks failed
CI / test (pull_request) Failing after 1m2s
CI / validate (pull_request) Failing after 1m20s
- Add shared tempus-caeleste.js formatter with solar/lunar phases
- Replace UTC display with Latin celestial labels (Hora Aurorae, etc.)
- Add test coverage for dawn/noon/dusk/night + moon phases
- Preserve internal UTC timestamps for audit
2026-04-29 08:07:15 -04:00
d9594d4f29 Merge pull request 'perf: Three.js LOD and texture audit for local hardware (#873)' (#1704) from fix/873 into main
Some checks failed
Deploy Nexus / deploy (push) Failing after 3s
Staging Verification Gate / verify-staging (push) Failing after 3s
Weekly Privacy Audit / privacy-audit (push) Successful in 9s
PR Backlog Monitor / pr-backlog-check (push) Failing after 7s
2026-04-26 01:31:06 +00:00
3 changed files with 219 additions and 3 deletions

6
app.js
View File

@@ -10,6 +10,7 @@ import { MemoryOptimizer } from './nexus/components/memory-optimizer.js';
import { MemoryInspect } from './nexus/components/memory-inspect.js';
import { MemoryPulse } from './nexus/components/memory-pulse.js';
import { ReasoningTrace } from './nexus/components/reasoning-trace.js';
import { formatTempusCaeleste, formatTempusBrevis, formatTempusPlenus } from './tempus-caeleste.js';
// ═══════════════════════════════════════════
// NEXUS v1.1 — Portal System Update
@@ -2546,9 +2547,8 @@ function renderEvenniaRoomPanel() {
const roomKeyEl = document.getElementById('erp-footer-room');
if (tsEl) {
try {
const d = new Date(evenniaRoom.timestamp);
tsEl.textContent = d.toISOString().replace('T', ' ').substring(0, 19) + ' UTC';
} catch(e) { tsEl.textContent = '—'; }
tsEl.textContent = formatTempusPlenus(evenniaRoom.timestamp);
} catch(e) { tsEl.textContent = 'Tempus Incertum'; }
}
if (roomKeyEl) roomKeyEl.textContent = evenniaRoom.roomKey;
}

106
tempus-caeleste.js Normal file
View File

@@ -0,0 +1,106 @@
/**
* Tempus Caeleste — Celestial Timestamp Formatter
* Replaces machine time with celestial/Latin display strings.
* Internal UTC/ISO timestamps are preserved for audit; this is display-only.
*/
const TEMPUS_REF_NEW_MOON = new Date('2023-01-21T20:53:00Z');
const TEMPUS_MOON_CYCLE_DAYS = 29.53058867;
// Solar phase definitions (local time)
const SOLAR_PHASES = [
{ id: 'noctis', label: 'Hora Noctis', start: 21, end: 24 },
{ id: 'noctis2', label: 'Hora Noctis', start: 0, end: 5 },
{ id: 'aurorae', label: 'Hora Aurorae', start: 6, end: 8 },
{ id: 'meridiana', label: 'Hora Meridiana', start: 9, end: 16 },
{ id: 'vesperi', label: 'Hora Vesperi', start: 17, end: 20 },
];
// Moon phase definitions
const MOON_PHASES = [
{ id: 'nova', label: 'Sub luna nova', min: 0, max: 1.84 },
{ id: 'crescente', label: 'Sub luna crescente', min: 1.84, max: 7.38 },
{ id: 'dimidiata-prima', label: 'Sub luna dimidiata prima', min: 7.38, max: 10.69 },
{ id: 'gibbosa-crescens', label: 'Sub luna gibbosa crescens', min: 10.69, max: 18.22 },
{ id: 'plena', label: 'Sub luna plena', min: 18.22, max: 22.53 },
{ id: 'gibbosa-decrescens', label: 'Sub luna gibbosa decrescens', min: 22.53, max: 25.84 },
{ id: 'dimidiata-ultima', label: 'Sub luna dimidiata ultima', min: 25.84, max: 27.38 },
{ id: 'decrescens', label: 'Sub luna decrescens', min: 27.38, max: 29.53 },
];
function getSolarPhase(date) {
const localHours = date.getHours();
const phase = SOLAR_PHASES.find(p =>
(p.start <= p.end && localHours >= p.start && localHours <= p.end) ||
(p.start > p.end && (localHours >= p.start || localHours <= p.end))
);
return phase ? phase.label : 'Hora Incerta';
}
function getMoonPhase(date) {
const msPerDay = 86400000;
const diffMs = date.getTime() - TEMPUS_REF_NEW_MOON.getTime();
const diffDays = diffMs / msPerDay;
const phaseDay = ((diffDays % TEMPUS_MOON_CYCLE_DAYS) + TEMPUS_MOON_CYCLE_DAYS) % TEMPUS_MOON_CYCLE_DAYS;
const phase = MOON_PHASES.find(p => phaseDay >= p.min && phaseDay < p.max);
return phase ? phase.label : 'Luna Incerta';
}
/**
* Format a canonical UTC/ISO timestamp into Tempus Caeleste display string.
* @param {string} isoString - UTC/ISO timestamp (e.g., "2026-04-29T12:34:56Z")
* @param {object} [options]
* @param {boolean} [options.includeMoon=true] - Include moon phase
* @param {boolean} [options.includeSolar=true] - Include solar phase
* @param {boolean} [options.completed=false] - Append "Actum est" for completed events
* @returns {string} Celestial/Latin formatted timestamp
*/
export function formatTempusCaeleste(isoString, options = {}) {
const { includeMoon = true, includeSolar = true, completed = false } = options;
try {
const date = new Date(isoString);
if (isNaN(date.getTime())) return 'Tempus Incertum';
const parts = [];
// Add solar phase
if (includeSolar) {
parts.push(getSolarPhase(date));
}
// Add moon phase
if (includeMoon) {
parts.push(getMoonPhase(date));
}
// Add raw local time as fallback (optional, for debug)
// parts.push(date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }));
let result = parts.join(' · ');
// Add completion marker
if (completed) {
result += ' · Actum est';
}
return result || 'Tempus Caeleste';
} catch (e) {
return 'Tempus Incertum';
}
}
/**
* Format a short celestial timestamp (solar phase only, no moon)
*/
export function formatTempusBrevis(isoString) {
return formatTempusCaeleste(isoString, { includeMoon: false });
}
/**
* Format a full celestial timestamp (solar + moon + completion if needed)
*/
export function formatTempusPlenus(isoString, completed = false) {
return formatTempusCaeleste(isoString, { completed });
}

View File

@@ -0,0 +1,110 @@
/**
* Tempus Caeleste Formatter Tests
* Covers solar phases, moon phases, and edge cases.
*/
import { formatTempusCaeleste, formatTempusBrevis, formatTempusPlenus } from '../tempus-caeleste.js';
// Helper to create ISO string from date components (local time)
function makeISO(year, month, day, hour, minute) {
const d = new Date(year, month - 1, day, hour, minute);
return d.toISOString();
}
// ─── Solar Phase Tests ──────────────────────────────────
export function testDawnPhase() {
// 6:30 AM = Hora Aurorae
const iso = makeISO(2026, 4, 29, 6, 30);
const result = formatTempusBrevis(iso);
console.assert(result.includes('Hora Aurorae'), `Expected Hora Aurorae, got: ${result}`);
console.log('✓ Dawn phase test passed');
}
export function testNoonPhase() {
// 12:30 PM = Hora Meridiana
const iso = makeISO(2026, 4, 29, 12, 30);
const result = formatTempusBrevis(iso);
console.assert(result.includes('Hora Meridiana'), `Expected Hora Meridiana, got: ${result}`);
console.log('✓ Noon phase test passed');
}
export function testDuskPhase() {
// 6:30 PM = Hora Vesperi
const iso = makeISO(2026, 4, 29, 18, 30);
const result = formatTempusBrevis(iso);
console.assert(result.includes('Hora Vesperi'), `Expected Hora Vesperi, got: ${result}`);
console.log('✓ Dusk phase test passed');
}
export function testNightPhase() {
// 11:30 PM = Hora Noctis
const iso = makeISO(2026, 4, 29, 23, 30);
const result = formatTempusBrevis(iso);
console.assert(result.includes('Hora Noctis'), `Expected Hora Noctis, got: ${result}`);
console.log('✓ Night phase test passed');
}
// ─── Moon Phase Tests ──────────────────────────────────
export function testFullMoonPhase() {
// 2026-05-05 12:00 is full moon (phase day ~18.57)
const iso = makeISO(2026, 5, 5, 12, 0);
const result = formatTempusPlenus(iso);
console.assert(result.includes('Sub luna plena'), `Expected Sub luna plena, got: ${result}`);
console.log('✓ Full moon phase test passed');
}
export function testNewMoonPhase() {
// 2026-04-17 is new moon (40 cycles after 2023-01-21: 2023-01-21 + 29.53*40 ≈ 2026-04-17)
const iso = makeISO(2026, 4, 17, 12, 0);
const result = formatTempusPlenus(iso);
console.assert(result.includes('Sub luna nova'), `Expected Sub luna nova, got: ${result}`);
console.log('✓ New moon phase test passed');
}
// ─── Edge Cases ───────────────────────────────────────
export function testInvalidTimestamp() {
const result = formatTempusCaeleste('invalid-date');
console.assert(result === 'Tempus Incertum', `Expected Tempus Incertum, got: ${result}`);
console.log('✓ Invalid timestamp test passed');
}
export function testCompletedEvent() {
const iso = makeISO(2026, 4, 29, 12, 0);
const result = formatTempusPlenus(iso, true);
console.assert(result.includes('Actum est'), `Expected Actum est, got: ${result}`);
console.log('✓ Completed event test passed');
}
// ─── Run All Tests ────────────────────────────────────
if (import.meta.url === `file://${process.argv[1]}`) {
const tests = [
testDawnPhase,
testNoonPhase,
testDuskPhase,
testNightPhase,
testFullMoonPhase,
testNewMoonPhase,
testInvalidTimestamp,
testCompletedEvent,
];
let passed = 0;
let failed = 0;
for (const test of tests) {
try {
test();
passed++;
} catch (e) {
console.error(`${test.name} failed:`, e.message);
failed++;
}
}
console.log(`\nResults: ${passed} passed, ${failed} failed`);
process.exit(failed > 0 ? 1 : 0);
}