# Posterior pupil trajectories and declared estimands

## Lightweight posterior-draw contract

The estimand layer operates on posterior prediction draws. For documentation and tests, a deterministic draw matrix can be used without compiling Stan.

    grid <- expand.grid(
      .event_time = seq(0, 1, length.out = 21),
      .condition = factor(c("control", "treatment")),
      KEEP.OUT.ATTRS = FALSE
    )
    set.seed(2026)
    mu <- ifelse(grid$.condition == "treatment", 0.15, 0) +
      0.25 * sin(pi * grid$.event_time)
    draws <- matrix(
      rnorm(300 * nrow(grid), rep(mu, each = 300), 0.06),
      nrow = 300
    )
    pred <- as_pupil_prediction_draws(draws, grid, "millimetres")

## Trajectory uncertainty

    trajectory <- estimate_pupil_trajectory(pred, probability = 0.95)
    head(pupil_trajectory_table(trajectory))
    #>   .event_time .condition    estimate        median        lower     upper
    #> 1        0.00    control 0.002499136 -0.0003379517 -0.109771080 0.1237874
    #> 2        0.05    control 0.043415282  0.0501940460 -0.081746697 0.1660529
    #> 3        0.10    control 0.074564976  0.0717499708 -0.031731909 0.1971629
    #> 4        0.15    control 0.114372026  0.1152661166 -0.002348101 0.2363749
    #> 5        0.20    control 0.142101058  0.1402868252  0.028388224 0.2545414
    #> 6        0.25    control 0.180003590  0.1786603138  0.068083824 0.2910737

Pointwise intervals describe uncertainty at each grid value. A finite-grid simultaneous band can be requested explicitly; it is qualified as a grid-based posterior band rather than a universal continuous-time guarantee.

## Declared contrasts and windows

    contrast <- pupil_condition_contrast(
      pred,
      contrast = c("treatment", "control"),
      threshold = 0.10
    )
    window <- estimate_pupil_window(pred, window = c(0.3, 0.8))
    auc <- estimate_pupil_auc(pred, window = c(0.3, 0.8))
    peak <- estimate_pupil_peak(pred, window = c(0.3, 0.8))
    latency <- estimate_pupil_peak_latency(pred, window = c(0.3, 0.8))

    head(as.data.frame(contrast))
    #>    .event_time            contrast  estimate    median        lower     upper
    #> 22        0.00 treatment - control 0.1512366 0.1480115 -0.002193476 0.3195893
    #> 23        0.05 treatment - control 0.1456269 0.1386465 -0.002451938 0.3172826
    #> 24        0.10 treatment - control 0.1554272 0.1563937 -0.017678839 0.3005029
    #> 25        0.15 treatment - control 0.1461007 0.1519917 -0.018632237 0.3023661
    #> 26        0.20 treatment - control 0.1611109 0.1606923 -0.011704219 0.3428709
    #> 27        0.25 treatment - control 0.1463584 0.1494409 -0.019417686 0.3219812
    #>    threshold probability_gt_threshold
    #> 22       0.1                0.7533333
    #> 23       0.1                0.7066667
    #> 24       0.1                0.7500000
    #> 25       0.1                0.7033333
    #> 26       0.1                0.7266667
    #> 27       0.1                0.7200000
    as.data.frame(window)
    #>   .condition    estimand  estimate    median     lower     upper window_start
    #> 1    control window_mean 0.2164691 0.2171261 0.1804861 0.2549529          0.3
    #> 2  treatment window_mean 0.3680499 0.3680008 0.3300174 0.4051905          0.3
    #>   window_end
    #> 1        0.8
    #> 2        0.8
    as.data.frame(auc)
    #>   .condition estimand  estimate    median      lower     upper window_start
    #> 1    control      auc 0.1105039 0.1107491 0.09204704 0.1303858          0.3
    #> 2  treatment      auc 0.1861384 0.1863730 0.16620597 0.2057661          0.3
    #>   window_end
    #> 1        0.8
    #> 2        0.8
    as.data.frame(peak)
    #>   .condition estimand  estimate    median     lower     upper window_start
    #> 1    control     peak 0.3231959 0.3239621 0.2594610 0.3998760          0.3
    #> 2  treatment     peak 0.4753597 0.4671045 0.4115866 0.5624902          0.3
    #>   window_end
    #> 1        0.8
    #> 2        0.8
    as.data.frame(latency)
    #>   .condition     estimand  estimate median     lower upper window_start
    #> 1    control peak_latency 0.5131667    0.5 0.3420833  0.75          0.3
    #> 2  treatment peak_latency 0.4960000    0.5 0.3000000  0.70          0.3
    #>   window_end
    #> 1        0.8
    #> 2        0.8

Windows are supplied by the analyst. The package does not search across time for the most favourable interval and relabel it confirmatory. Peak and peak-latency summaries propagate posterior-draw uncertainty within the declared evaluation grid.