Hmbown/CodeWhale · error
finite points
Error message
finite points
What it means
Panic from `.expect("finite points")` on `serde_json::to_string(&points)` inside the pet-watch QuickJS worker (crates/tui/src/tui/pet_watch/worker.rs:129). The points are parsed from the bundled `whale-points.tsv` as `f64`; `serde_json::to_string` errors when a value is NaN or infinite (JSON has no representation for them). This is a build-data invariant: the shipped TSV must contain only finite floats.
Solutions
- Audit `crates/tui/src/tui/ambient_life/whale-points.tsv` for NaN/Inf-producing values and regenerate it with finite numbers only.
- Add a build-time check that parses the TSV and asserts every cell is finite, so bad data fails the build instead of the worker.
- If the parser must tolerate malformed lines, drop them (`.filter(|v| v.is_finite())`) rather than substituting non-finite defaults.
- Replace the bare `.expect("finite points")` with an expect that names the offending cell/line for faster diagnosis.
Example fix
// before
serde_json::to_string(&points).expect("finite points")
// after
serde_json::to_string(&points)
.expect("whale-points.tsv must contain only finite floats (json serialize)") Defensive patterns
Strategy: validation
Validate before calling
let points: Vec<Vec<f64>> = /* parsed TSV */; assert!(points.iter().flatten().all(|v| v.is_finite()), "non-finite point in whale-points.tsv");
Type guard
fn all_finite(points: &[Vec<f64>]) -> bool { points.iter().flatten().all(|v| v.is_finite()) } Try / catch
let json = serde_json::to_string(&points)
.expect("finite points (check whale-points.tsv for NaN/Inf)"); Prevention
- Add a build/test assertion that every parsed TSV value is finite.
- Drop (not default) malformed TSV lines in the parser.
- Regenerate datasets with scripts that serialize finite f64 only.
When it happens
Trigger: `whale-points.tsv` (or any replacement points file) containing a token that parses to NaN/inf, or a code change altering the parser to emit non-finite defaults (e.g. `parse().unwrap_or(f64::NAN)`); the panic fires at worker `start()`/`run()` when the JS pet engine is initialized.
Common situations: Editing or regenerating the ambient-life points dataset with an export script that emits `NaN`/`Inf`; fuzzing or replacing the TSV in tests; hand-editing the resource with locale-formatted decimals (`1,5`) that a filter_map silently drops, followed by a later change substituting non-finite fallbacks.
Understand the failure class
Background: "JSON serialization failed", "not JSON serializable", "Failed to serialize": why JSON marshaling errors happen and how to fix them — this error's family across 46 libraries.
Related errors
- serialize runtime event envelope
- bounded to model output cap
- Clock lock failed
- command string
- deserialize
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/9998f68e2fa894eb.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tui/pet_watch/worker.rs:129
let deadline = Arc::new(Mutex::new(Instant::now() + Duration::from_secs(2)));
let check = Arc::clone(&deadline);
runtime.set_interrupt_handler(Some(Box::new(move || {
check.lock().map_or(true, |d| Instant::now() > *d)
})));
let context = Context::full(&runtime).map_err(|_| ())?;
context
.with(|ctx| -> rquickjs::Result<()> {
let points: Vec<Vec<f64>> = include_str!("../ambient_life/whale-points.tsv")
.lines()
.map(|line| {
line.split_whitespace()
.filter_map(|s| s.parse().ok())
.collect()
})
.collect();
ctx.globals().set(
"points",
serde_json::to_string(&points).expect("finite points"),
)?;
ctx.eval::<(), _>(include_bytes!("pet-native.js").as_slice())?;
ctx.eval::<(), _>("globalThis.pet = new PetNative(points, '', '[]', true)")?;
Ok(())
})
.map_err(|_| ())?;
let mut store = None;
let mut offset_ms = 0.0;
if let Some(session) = session {
// Hydrating a bounded recording validates its whole accepted history.
// It runs off the UI thread and can take longer than a frame command.
*deadline.lock().map_err(|_| ())? = Instant::now() + Duration::from_secs(10);
let loaded = (|| -> Result<Store, ()> {
let mut files = Store::open(session).map_err(|_| ())?;
if let Some(saved) = files.load().map_err(|_| ())? {
offset_ms = context
.with(|ctx| -> rquickjs::Result<f64> {
ctx.globals().set("savedHabitat", saved)?;View on GitHub (pinned to 73e0f67d83)