docs/dgp/02-timeline.md

# Exogenous timeline

The timeline lives on `TimelineParams` and is built once per simulation by
`build_timeline`. Length is `T+1` to include both `t=0` and the landfall hour.

## Forecast intensity

Piecewise-constant baseline plus i.i.d. Gaussian noise:

```
F̄_t  =  1   for   0 ≤ t < 48
        2   for  48 ≤ t < 72
        3   for  72 ≤ t < 96
        4   for  96 ≤ t ≤ T
F_t   =  F̄_t + ε_t,   ε_t ~ N(0, 0.15²)
```

The breakpoints `(0, 48, 72, 96)` and levels `(1, 2, 3, 4)` are tuples on
`TimelineParams`. They must be the same length, and the first breakpoint must
be 0; both invariants are checked at construction.

## Warning orders

Two indicators step from 0 to 1 once and stay on:

```
vol_t   =  1{t ≥ voluntary_hour}
mand_t  =  1{t ≥ mandatory_hour}
```

Defaults: voluntary at t=60, mandatory at t=84. These are the primary
scenario-tunable knobs; the `early-warning` scenario pulls them to 48 and 72.

## Time since last order

`time_since_order[t]` is the number of hours since the most recent escalation
in `(vol, mand)`. Before either order has fired we use a sentinel `T+1` so
downstream features treat early time steps as "no order has happened yet".
The current build does not consume this field directly, but it is exported in
`timeline.parquet` for use by later builds and figures.

## Local risk

`local_risk_at(forecast_t, distance, zone_multiplier)` is computed on-the-fly
each step (rather than materializing an N×(T+1) array), saving memory at no
runtime cost:

```
ρ_{i,t}  =  F_t · exp(−d_i / 10)
```

Optionally multiplied per-household by a zone multiplier (used by the
targeted-messaging scenario).