# Fitting hierarchical pupil time-course models

## Approved family

The first direct pupil model family is Gaussian with an identity link. It supports a governed temporal trajectory, optional condition-specific trajectory, participant hierarchy, optional item hierarchy, declared numeric nuisance covariates, and optional AR(1) dependence for sufficiently regular within-trial sampling.

    sim <- simulate_pupil_timecourse(
      n_participants = 5,
      trials_per_participant = 4,
      sampling_frequency = 20,
      time_window = c(-0.4, 1.2),
      baseline_window = c(-0.4, 0),
      blink_trial_probability = 0,
      seed = 2026
    )
    contract <- create_pupil_contract(
      outcome_col = "pupil_mm",
      participant_col = "participant_id",
      trial_col = "trial_id",
      item_col = "item_id",
      condition_col = "condition",
      time_col = "event_time",
      pupil_unit = "millimetres",
      sampling_frequency = 20,
      eye = "combined"
    )
    prepared <- prepare_pupil_timecourse(sim$data, contract)
    spec <- specify_pupil_timecourse_model(
      prepared,
      temporal_structure = "smooth",
      smooth_basis_dimension = 5,
      condition_trajectory = TRUE,
      autocorrelation = "none"
    )
    spec
    #> <gp3bayes_pupil_model_specification>
    #>   Family: Gaussian pupil time-course
    #>   Formula: .pupil_model ~ .condition + s(.event_time, by = .condition, k = 5) +      (1 | .participant) + (1 | .item)
    #>   Temporal structure: smooth
    #>   Condition trajectory: TRUE
    #>   Autocorrelation: none
    #>   Outcome unit: millimetres
    #>   Baseline: none
    #>   Unrestricted formula: FALSE
    #>   Fit performed: FALSE

## Translation without fitting

    translation <- translate_pupil_model_to_brms(spec)
    translation$formula
    translation$prior

Translation is restricted. The user does not provide an arbitrary formula, family, Stan program, algorithm, or open-ended backend argument list.

## Prior-predictive gate

Prior-predictive execution is governed separately from posterior fitting. The default call records the approved prior-only plan and does not compile Stan.

    prior_plan <- check_pupil_prior_predictive(
      spec,
      execute = FALSE,
      draws = 100,
      chains = 2,
      iter = 200,
      warmup = 100
    )
    as.data.frame(prior_plan)
    #>          field       value
    #> 1       family    Gaussian
    #> 2      backend       rstan
    #> 3        draws         100
    #> 4       chains           2
    #> 5         iter         200
    #> 6       warmup         100
    #> 7        cores           2
    #> 8 outcome_unit millimetres
    #> 9      execute       FALSE

A researcher can set `execute = TRUE` with either approved backend during manual analysis. The operation never changes priors automatically and its evidence does not certify model adequacy.

## Full-MCMC backends

Real fitting is optional and requires `brms` plus one approved backend.

    fit_rstan <- fit_pupil_model_backend(
      spec,
      backend = "rstan",
      chains = 2,
      iter = 1000,
      warmup = 500,
      cores = 2,
      seed = 20260814
    )

    fit_cmdstanr <- fit_pupil_model_backend(
      spec,
      backend = "cmdstanr",
      chains = 2,
      iter = 1000,
      warmup = 500,
      cores = 2,
      seed = 20260814
    )

The wrappers preserve a common gp3bayes object shape. A fitted object does not by itself establish convergence, adequacy, measurement validity, or a causal interpretation.