Add a clickable cuckoo clock and the 90-second talk to the landing page
The clock strikes the current hour. Two things in it are mechanism rather than decoration: - The pendulum TICKS. steps(2) over 2s = two discrete positions, one per second. That is what an escapement does — it chops continuous energy into countable ticks. A swept pendulum would contradict the entire site. - The call is SYNTHESIZED, not sampled. A real cuckoo clock is two wooden pipes a minor third apart (6:5, so 700 Hz -> 583 Hz), pushed by a bellows. That's twenty lines of Web Audio: triangle pipes with a slight pitch sag as the bellows empties, plus a puff of bandpassed noise for the breath. The sound is the mechanism rebuilt, and the hero ships zero bytes of audio. The talk is regenerated for a public audience — the finding, not the procurement case. Transcript ships with it: audio is the alternative to reading, never a replacement for the text. preload=none so the 800 KB only downloads for readers who actually press play.
This commit is contained in:
parent
6881489bf6
commit
63902f095a
BIN
docs-site/public/audio/lightning-talk.mp3
Normal file
BIN
docs-site/public/audio/lightning-talk.mp3
Normal file
Binary file not shown.
298
docs-site/src/components/CuckooClock.astro
Normal file
298
docs-site/src/components/CuckooClock.astro
Normal file
@ -0,0 +1,298 @@
|
||||
---
|
||||
// A cuckoo clock you can click. It strikes the current hour.
|
||||
//
|
||||
// Two things here are deliberate rather than decorative:
|
||||
//
|
||||
// 1. THE PENDULUM TICKS, it does not sweep. `steps(2)` over 2s means it lands
|
||||
// in exactly two discrete positions per period — one per second. That IS an
|
||||
// escapement: the mechanism that takes continuous energy (a falling weight,
|
||||
// a crystal oscillator, a satellite) and chops it into countable ticks. The
|
||||
// whole site is an argument about what happens when you put a scheduler in
|
||||
// front of that chop, so the hero ought to do it correctly.
|
||||
//
|
||||
// 2. THE CALL IS SYNTHESIZED, not a sample. A real cuckoo clock is two wooden
|
||||
// pipes pushed by a bellows, tuned a MINOR THIRD apart, high note first.
|
||||
// That's ~700 Hz down to ~590 Hz — about twenty lines of Web Audio. So the
|
||||
// sound isn't an asset we shipped; it's the mechanism, rebuilt. Also it
|
||||
// means the hero costs zero bytes of audio and works offline.
|
||||
//
|
||||
// Audio only ever starts from a click, which is both good manners and the only
|
||||
// thing browsers permit — an AudioContext can't be resumed without a gesture.
|
||||
---
|
||||
|
||||
<div class="cuckoo">
|
||||
<button
|
||||
class="cuckoo__btn"
|
||||
id="cuckoo-clock"
|
||||
type="button"
|
||||
aria-label="Cuckoo clock — click to strike the hour"
|
||||
title="Strike the hour"
|
||||
>
|
||||
<svg viewBox="0 0 200 272" class="cuckoo__svg" aria-hidden="true">
|
||||
<defs>
|
||||
<linearGradient id="ck-brass" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#f2d79b" />
|
||||
<stop offset="55%" stop-color="#d9a441" />
|
||||
<stop offset="100%" stop-color="#8a6417" />
|
||||
</linearGradient>
|
||||
<linearGradient id="ck-wood" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#241f19" />
|
||||
<stop offset="100%" stop-color="#14110e" />
|
||||
</linearGradient>
|
||||
<!-- The doorway. Anything drawn inside this clip can only be seen
|
||||
through the opening — which is how the bird stays hidden until it
|
||||
rides up into it. -->
|
||||
<clipPath id="ck-door-clip">
|
||||
<path d="M78 96V74a22 22 0 0 1 44 0v22Z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
<!-- Case -->
|
||||
<path
|
||||
d="M100 20 172 58 172 202 28 202 28 58Z"
|
||||
fill="url(#ck-wood)" stroke="url(#ck-brass)" stroke-width="2.5" stroke-linejoin="round"
|
||||
/>
|
||||
<!-- Roof, with the deep Black Forest overhang -->
|
||||
<path
|
||||
d="M100 8 192 58 176 66 100 24 24 66 8 58Z"
|
||||
fill="#2b241c" stroke="url(#ck-brass)" stroke-width="2" stroke-linejoin="round"
|
||||
/>
|
||||
|
||||
<!-- Bird, behind the doorway. Starts pushed down out of the opening. -->
|
||||
<g clip-path="url(#ck-door-clip)">
|
||||
<g class="ck-bird" transform="translate(0, 30)">
|
||||
<circle cx="96" cy="84" r="11" fill="#e8e4dc" />
|
||||
<path d="M105 80 124 85 105 90Z" fill="#d9a441" />
|
||||
<circle cx="99" cy="80" r="2" fill="#161310" />
|
||||
<path d="M90 74c-3-5 0-9 4-9-2 3-1 6 1 7Z" fill="#e8e4dc" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Door frame + the two leaves, hinged at the outer jambs -->
|
||||
<path d="M78 96V74a22 22 0 0 1 44 0v22" fill="none"
|
||||
stroke="url(#ck-brass)" stroke-width="2" />
|
||||
<g class="ck-doors">
|
||||
<path class="ck-door ck-door--l" d="M78 96V74a22 22 0 0 1 22-22v44Z"
|
||||
fill="#1d1915" stroke="url(#ck-brass)" stroke-width="1.4" />
|
||||
<path class="ck-door ck-door--r" d="M122 96V74a22 22 0 0 0-22-22v44Z"
|
||||
fill="#1d1915" stroke="url(#ck-brass)" stroke-width="1.4" />
|
||||
</g>
|
||||
|
||||
<!-- Dial -->
|
||||
<circle cx="100" cy="146" r="36" fill="#0d0b09" stroke="url(#ck-brass)" stroke-width="2.5" />
|
||||
<g stroke="#d9a441" stroke-linecap="round" opacity="0.8">
|
||||
<!-- 12 hour ticks; the quarters are longer, as on a real dial -->
|
||||
<line x1="100" y1="114" x2="100" y2="121" stroke-width="1.8" />
|
||||
<line x1="132" y1="146" x2="125" y2="146" stroke-width="1.8" />
|
||||
<line x1="100" y1="178" x2="100" y2="171" stroke-width="1.8" />
|
||||
<line x1="68" y1="146" x2="75" y2="146" stroke-width="1.8" />
|
||||
<line x1="116" y1="118" x2="114" y2="122" stroke-width="1.1" />
|
||||
<line x1="128" y1="130" x2="124" y2="132" stroke-width="1.1" />
|
||||
<line x1="128" y1="162" x2="124" y2="160" stroke-width="1.1" />
|
||||
<line x1="116" y1="174" x2="114" y2="170" stroke-width="1.1" />
|
||||
<line x1="84" y1="174" x2="86" y2="170" stroke-width="1.1" />
|
||||
<line x1="72" y1="162" x2="76" y2="160" stroke-width="1.1" />
|
||||
<line x1="72" y1="130" x2="76" y2="132" stroke-width="1.1" />
|
||||
<line x1="84" y1="118" x2="86" y2="122" stroke-width="1.1" />
|
||||
</g>
|
||||
<!-- Hands. Set to the real local time by the script below. -->
|
||||
<line class="ck-hand-h" x1="100" y1="146" x2="100" y2="126"
|
||||
stroke="#e8e4dc" stroke-width="3.2" stroke-linecap="round" />
|
||||
<line class="ck-hand-m" x1="100" y1="146" x2="100" y2="118"
|
||||
stroke="#e8e4dc" stroke-width="2" stroke-linecap="round" />
|
||||
<circle cx="100" cy="146" r="2.8" fill="#d9a441" />
|
||||
|
||||
<!-- Pendulum. It TICKS — see the header comment. -->
|
||||
<g class="ck-pendulum">
|
||||
<line x1="100" y1="202" x2="100" y2="242" stroke="#a97c1f" stroke-width="1.6" />
|
||||
<!-- Leaf-shaped bob, as on a Black Forest movement -->
|
||||
<ellipse cx="100" cy="250" rx="11" ry="9"
|
||||
fill="url(#ck-brass)" stroke="#8a6417" stroke-width="0.8" />
|
||||
<circle cx="100" cy="250" r="3" fill="#8a6417" opacity="0.6" />
|
||||
</g>
|
||||
|
||||
<!-- Pine-cone weights, on their chains -->
|
||||
<g>
|
||||
<line x1="60" y1="202" x2="60" y2="222" stroke="#8a6417" stroke-width="1" />
|
||||
<path d="M54 222h12l-2 7h-8Zm1 7h10l-2 7h-6Zm1 7h8l-3 8h-2Z" fill="url(#ck-brass)" />
|
||||
<line x1="140" y1="202" x2="140" y2="234" stroke="#8a6417" stroke-width="1" />
|
||||
<path d="M134 234h12l-2 7h-8Zm1 7h10l-2 7h-6Zm1 7h8l-3 8h-2Z" fill="url(#ck-brass)" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<span class="cuckoo__hint">Strike the hour</span>
|
||||
</button>
|
||||
|
||||
<p class="cuckoo__live" role="status" aria-live="polite" id="cuckoo-live"></p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const clock = document.getElementById("cuckoo-clock");
|
||||
const live = document.getElementById("cuckoo-live");
|
||||
|
||||
if (clock) {
|
||||
const svg = clock.querySelector("svg");
|
||||
|
||||
// --- Set the hands to the real local time. It's a clock; it should be right.
|
||||
const setHands = () => {
|
||||
const now = new Date();
|
||||
const m = now.getMinutes();
|
||||
const h = now.getHours() % 12 + m / 60;
|
||||
const hand = (sel, deg) =>
|
||||
svg?.querySelector(sel)?.setAttribute(
|
||||
"transform",
|
||||
`rotate(${deg} 100 146)`,
|
||||
);
|
||||
hand(".ck-hand-h", h * 30);
|
||||
hand(".ck-hand-m", m * 6);
|
||||
};
|
||||
setHands();
|
||||
setInterval(setHands, 30_000);
|
||||
|
||||
// --- The call. Two wooden pipes, a minor third apart, high note first.
|
||||
// A minor third is a frequency ratio of 6:5, so 700 Hz → 583 Hz. Triangle
|
||||
// waves for the woody pipe tone; a puff of filtered noise for the bellows.
|
||||
let ctx = null;
|
||||
let striking = false;
|
||||
|
||||
const pipe = (freq, at, dur) => {
|
||||
const osc = ctx.createOscillator();
|
||||
const gain = ctx.createGain();
|
||||
osc.type = "triangle";
|
||||
osc.frequency.setValueAtTime(freq, at);
|
||||
// Real pipes sag slightly as the bellows empties.
|
||||
osc.frequency.linearRampToValueAtTime(freq * 0.985, at + dur);
|
||||
|
||||
gain.gain.setValueAtTime(0, at);
|
||||
gain.gain.linearRampToValueAtTime(0.18, at + 0.02); // bellows push
|
||||
gain.gain.setValueAtTime(0.18, at + dur - 0.06);
|
||||
gain.gain.linearRampToValueAtTime(0, at + dur); // and release
|
||||
|
||||
osc.connect(gain).connect(ctx.destination);
|
||||
osc.start(at);
|
||||
osc.stop(at + dur + 0.02);
|
||||
|
||||
// The breath: a short hiss under the note's attack.
|
||||
const noise = ctx.createBufferSource();
|
||||
const buf = ctx.createBuffer(1, ctx.sampleRate * 0.06, ctx.sampleRate);
|
||||
const data = buf.getChannelData(0);
|
||||
for (let i = 0; i < data.length; i++) data[i] = (Math.random() * 2 - 1) * 0.5;
|
||||
noise.buffer = buf;
|
||||
const bp = ctx.createBiquadFilter();
|
||||
bp.type = "bandpass";
|
||||
bp.frequency.value = freq * 1.5;
|
||||
bp.Q.value = 0.7;
|
||||
const ng = ctx.createGain();
|
||||
ng.gain.setValueAtTime(0.05, at);
|
||||
ng.gain.exponentialRampToValueAtTime(0.001, at + 0.06);
|
||||
noise.connect(bp).connect(ng).connect(ctx.destination);
|
||||
noise.start(at);
|
||||
};
|
||||
|
||||
// One "cuck-oo": high note, then the minor third below it.
|
||||
const call = (at) => {
|
||||
pipe(700, at, 0.24);
|
||||
pipe(700 * 5 / 6, at + 0.30, 0.30);
|
||||
return 0.78; // seconds until the next call may begin
|
||||
};
|
||||
|
||||
clock.addEventListener("click", async () => {
|
||||
if (striking) return;
|
||||
striking = true;
|
||||
|
||||
// An AudioContext can only start from a user gesture — which a click is.
|
||||
ctx = ctx ?? new (window.AudioContext || window.webkitAudioContext)();
|
||||
if (ctx.state === "suspended") await ctx.resume();
|
||||
|
||||
const hour = new Date().getHours() % 12 || 12;
|
||||
let t = ctx.currentTime + 0.05;
|
||||
for (let i = 0; i < hour; i++) t += call(t);
|
||||
|
||||
clock.classList.add("is-striking");
|
||||
if (live) live.textContent = `Cuckoo ×${hour}`;
|
||||
|
||||
const total = (t - ctx.currentTime) * 1000;
|
||||
setTimeout(() => {
|
||||
clock.classList.remove("is-striking");
|
||||
striking = false;
|
||||
}, total);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.cuckoo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
margin: 0 0 2rem;
|
||||
}
|
||||
|
||||
.cuckoo__btn {
|
||||
background: none;
|
||||
border: 0;
|
||||
padding: 0.5rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.cuckoo__btn:focus-visible {
|
||||
outline: 2px solid var(--sl-color-accent);
|
||||
outline-offset: 4px;
|
||||
}
|
||||
|
||||
.cuckoo__svg {
|
||||
width: 168px;
|
||||
height: auto;
|
||||
filter: drop-shadow(0 18px 30px rgb(0 0 0 / 0.5));
|
||||
}
|
||||
|
||||
.cuckoo__hint {
|
||||
font-family: var(--sl-font-system-mono);
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.14em;
|
||||
color: var(--sl-color-gray-3);
|
||||
transition: color 0.2s;
|
||||
}
|
||||
.cuckoo__btn:hover .cuckoo__hint { color: var(--sl-color-accent); }
|
||||
|
||||
.cuckoo__live {
|
||||
min-height: 1.2em;
|
||||
margin: 0;
|
||||
font-family: var(--sl-font-system-mono);
|
||||
font-size: 0.75rem;
|
||||
color: var(--sl-color-accent);
|
||||
}
|
||||
|
||||
/* The escapement. steps(2) over 2s = two discrete positions, one per second.
|
||||
A swept pendulum would be a lie: an escapement's whole job is to make the
|
||||
motion DISCRETE. */
|
||||
.ck-pendulum {
|
||||
transform-origin: 100px 202px;
|
||||
animation: ck-tick 2s steps(2, jump-none) infinite;
|
||||
}
|
||||
@keyframes ck-tick {
|
||||
from { transform: rotate(-9deg); }
|
||||
to { transform: rotate(9deg); }
|
||||
}
|
||||
|
||||
/* Doors swing outward on their hinges; the bird rides out behind them. */
|
||||
.ck-door { transition: transform 0.35s cubic-bezier(0.2, 0.8, 0.3, 1); }
|
||||
.ck-door--l { transform-origin: 78px 96px; }
|
||||
.ck-door--r { transform-origin: 122px 96px; }
|
||||
.is-striking .ck-door--l { transform: rotate(-62deg); }
|
||||
.is-striking .ck-door--r { transform: rotate(62deg); }
|
||||
|
||||
.ck-bird { transition: transform 0.3s cubic-bezier(0.3, 1.4, 0.5, 1); }
|
||||
.is-striking .ck-bird { transform: translate(0, 0); }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.ck-pendulum { animation: none; }
|
||||
.ck-door, .ck-bird { transition: none; }
|
||||
}
|
||||
</style>
|
||||
136
docs-site/src/components/ListenToTalk.astro
Normal file
136
docs-site/src/components/ListenToTalk.astro
Normal file
@ -0,0 +1,136 @@
|
||||
---
|
||||
// "Listen to the talk" — the 90-second version of this site.
|
||||
//
|
||||
// preload="none" matters: without it every reader's browser downloads the whole
|
||||
// MP3 on page load whether or not they ever press play. On a docs page that's
|
||||
// ~800 KB of bandwidth for a feature most people skip. The cost is a ~200 ms
|
||||
// delay before audio starts, which nobody notices on a 90-second talk.
|
||||
//
|
||||
// The transcript below is not an accessibility afterthought — it's the reason
|
||||
// the page is searchable and the reason someone in an open-plan office can
|
||||
// still get the argument. Audio is the alternative to reading, never a
|
||||
// replacement for the text.
|
||||
|
||||
interface Props {
|
||||
src?: string;
|
||||
seconds?: number;
|
||||
}
|
||||
const { src = "/audio/lightning-talk.mp3", seconds = 89 } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="talk">
|
||||
<button class="talk__btn" id="talk-btn" type="button">
|
||||
<svg class="talk__icon talk__icon--play" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M6 4l14 8-14 8Z" fill="currentColor" stroke="none" />
|
||||
</svg>
|
||||
<svg class="talk__icon talk__icon--pause is-hidden" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M7 4h4v16H7ZM13 4h4v16h-4Z" fill="currentColor" stroke="none" />
|
||||
</svg>
|
||||
<span class="talk__label">Listen to the talk</span>
|
||||
<span class="talk__dur">{seconds}s</span>
|
||||
<span class="talk__progress" id="talk-progress"></span>
|
||||
</button>
|
||||
|
||||
<audio id="talk-audio" preload="none" src={src}></audio>
|
||||
|
||||
<details class="talk__transcript">
|
||||
<summary>Transcript</summary>
|
||||
<slot />
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const btn = document.getElementById("talk-btn");
|
||||
const audio = document.getElementById("talk-audio") as HTMLAudioElement | null;
|
||||
const progress = document.getElementById("talk-progress");
|
||||
|
||||
if (btn && audio) {
|
||||
const play = btn.querySelector(".talk__icon--play");
|
||||
const pause = btn.querySelector(".talk__icon--pause");
|
||||
|
||||
// Toggle a class, not `.hidden`. These are SVGElements, which inherit from
|
||||
// Element rather than HTMLElement — so the `hidden` reflected property
|
||||
// doesn't exist on them and assigning it silently does nothing.
|
||||
const showPlaying = (playing: boolean) => {
|
||||
play?.classList.toggle("is-hidden", playing);
|
||||
pause?.classList.toggle("is-hidden", !playing);
|
||||
};
|
||||
|
||||
btn.addEventListener("click", () => {
|
||||
audio.paused ? audio.play() : audio.pause();
|
||||
});
|
||||
|
||||
audio.addEventListener("play", () => showPlaying(true));
|
||||
audio.addEventListener("pause", () => showPlaying(false));
|
||||
audio.addEventListener("ended", () => {
|
||||
showPlaying(false);
|
||||
if (progress) progress.style.width = "0";
|
||||
});
|
||||
audio.addEventListener("timeupdate", () => {
|
||||
if (progress && audio.duration) {
|
||||
progress.style.width = `${(audio.currentTime / audio.duration) * 100}%`;
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.talk { margin: 0 0 2rem; }
|
||||
|
||||
.talk__btn {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
padding: 0.65rem 1.1rem;
|
||||
border: 1px solid var(--sl-color-hairline-light);
|
||||
border-radius: 6px;
|
||||
background: var(--sl-color-bg-nav);
|
||||
color: var(--sl-color-gray-2);
|
||||
font: inherit;
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s, border-color 0.2s;
|
||||
}
|
||||
.talk__btn:hover {
|
||||
color: var(--sl-color-white);
|
||||
border-color: var(--sl-color-accent-low);
|
||||
}
|
||||
|
||||
.talk__icon { width: 16px; height: 16px; color: var(--sl-color-accent); }
|
||||
.talk__icon.is-hidden { display: none; }
|
||||
.talk__label { font-weight: 600; }
|
||||
.talk__dur {
|
||||
font-family: var(--sl-font-system-mono);
|
||||
font-size: 0.75rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--sl-color-gray-3);
|
||||
}
|
||||
|
||||
/* Fills along the bottom edge as it plays. */
|
||||
.talk__progress {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
width: 0;
|
||||
background: var(--sl-color-accent);
|
||||
transition: width 0.15s linear;
|
||||
}
|
||||
|
||||
.talk__transcript {
|
||||
margin-top: 0.9rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.talk__transcript summary {
|
||||
cursor: pointer;
|
||||
font-family: var(--sl-font-system-mono);
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--sl-color-gray-3);
|
||||
}
|
||||
.talk__transcript summary:hover { color: var(--sl-color-accent); }
|
||||
.talk__transcript[open] summary { margin-bottom: 0.6rem; }
|
||||
</style>
|
||||
@ -4,6 +4,41 @@ description: A field report on GPS Stratum 1 timekeeping on a Raspberry Pi 4 —
|
||||
---
|
||||
|
||||
import { Aside, CardGrid, Card } from '@astrojs/starlight/components';
|
||||
import CuckooClock from '../../components/CuckooClock.astro';
|
||||
import ListenToTalk from '../../components/ListenToTalk.astro';
|
||||
|
||||
<CuckooClock />
|
||||
|
||||
<ListenToTalk>
|
||||
Here's a thing that cost us a week. It might be costing you right now, and you'd
|
||||
have no way to know.
|
||||
|
||||
We built a GPS-disciplined time server on a Raspberry Pi 4. Stratum 1 — a
|
||||
satellite pulse, once a second, straight into a GPIO pin. Then we did what every
|
||||
guide says to do next. Install a realtime kernel. Isolate the interrupt. Pin it
|
||||
to a quiet core.
|
||||
|
||||
**The realtime kernel made it three times worse.** Not slightly worse. Three
|
||||
times. 2134 ns of jitter became 6947 ns.
|
||||
|
||||
Here is why. PREEMPT_RT buys its determinism by turning interrupt handlers into
|
||||
schedulable threads. For almost every driver that's a good trade. But the PPS
|
||||
driver takes its timestamp *inside* its handler. So threading it doesn't defer
|
||||
some work — it defers **the act of looking at the clock**. We didn't make the
|
||||
system more predictable. We put a scheduler between the pulse and the clock.
|
||||
|
||||
The fix is four lines. One flag: `IRQF_NO_THREAD`. Keep that one handler in
|
||||
hard-IRQ context, and let everything else stay preemptible. Our error went from
|
||||
2468 ns to **199 ns**.
|
||||
|
||||
As far as we can tell, nobody has applied this. Which means anyone running GPS
|
||||
timing on a realtime kernel is quietly eating microseconds of error and has no
|
||||
reason to suspect it — because nothing looks broken. chrony still says Stratum 1.
|
||||
The dashboard still says locked. The number is just silently worse.
|
||||
|
||||
So that's the talk. Measure your clock. Don't trust the guide. And if you take
|
||||
one thing from us, [take the patch](/reference/the-patch/).
|
||||
</ListenToTalk>
|
||||
|
||||
**This is not a build guide.**
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user