# Exact frame-clock arithmetic lab

An original, offline Python standard-library illustration of animation time and
capture sampling. No dependencies, downloads, or network access are needed.

## Files

- [frame_clock.py](frame_clock.py): original clock and sampling exercise
- [test_frame_clock.py](test_frame_clock.py): 11 offline regression tests
- [report.json](report.json): calculated exact timing summary
- [samples.csv](samples.csv): frame-by-frame values for the three authored schedules

## Reproduce

From this directory, run:

```sh
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest -v
PYTHONDONTWRITEBYTECODE=1 python3 frame_clock.py
```

The first command runs 11 tests. The second regenerates `report.json` and
`samples.csv` beside the script. It overwrites these two generated files only.
The CSV contains a header and 360 data rows (120 frames for each of three
synthetic callback schedules). Rational numbers are written as exact strings,
such as `119/60`, rather than rounded decimal approximations. The same script
and inputs produce the same output bytes.

## Definitions and results

- Capture uses 60 frames per second for a 2-second, half-open interval [0, 2).
  There are 120 frame indices, 0 through 119. Frame n samples n/60 seconds;
  the last sample is 119/60 seconds. There is no capture sample at 2 seconds.
- Ideal motion is x(t) = 120t, starting at x=0. Its last capture position is 238
  arbitrary distance units. Time comes directly from the frame index.
- Callback schedules are synthetic: regular 30 Hz, regular 60 Hz, and alternating
  intervals of 1/120 and 3/120 seconds. The irregular schedule averages 60 updates
  per second over complete interval pairs; it is not a measured display trace.
- A callback at zero initializes the state without moving it. Subsequent
  callbacks either add 120 times their elapsed interval, or naively add 2 units.
- All three callback schedules happen to include 2 seconds. At that separate
  endpoint, elapsed-time motion reaches 240 units in all three. Naive motion
  reaches 120 units at regular 30 Hz, versus 240 at regular 60 Hz and in the
  irregular schedule. This endpoint comparison is not the final captured frame.
- The CSV additionally samples callback-updated state with a zero-order hold:
  use the last callback at or before each capture time, without interpolation
  or extrapolation. At the last capture, elapsed-time held positions are 236,
  238, and 237 for regular 30 Hz, regular 60 Hz, and irregular respectively.
  Maximum lag on these capture samples is respectively 2, 0, and 1 units.
  These maxima describe these particular sampled schedules, not a general
  upper bound on display lag or browser behavior.

Elapsed-time integration is exact here because the velocity is constant and
all arithmetic uses `fractions.Fraction`. It does not make a stale callback
state current at an arbitrary capture time. Directly evaluating ideal motion
at the capture timestamp avoids that particular sampling discrepancy in this
mathematical fixture.

## Boundaries

This is a synthetic math-only test, not a browser, requestAnimationFrame, CDP,
renderer, model, video encoder, screenshot, frame pacing, or performance test.
No browser animation was captured. The outputs do not establish image equality,
GPU determinism, dropped-frame behavior, real display cadence, browser timing
precision, or end-to-end reproducibility. They illustrate clock bookkeeping and
sampling distinctions only. Real browsers use finite-precision clocks; complex
simulations can remain timestep-sensitive even when they use elapsed time.

The helper functions use rational inputs. Prefer integers, Fraction values, or
fraction strings; passing binary floats preserves their binary approximation.
`held_value` expects sorted callback times; the supplied schedules satisfy this
precondition. It intentionally keeps the final value after the last callback.
