Terrain Scoring
Scores DEM-derived terrain features (slope, aspect, TPI, elevation position, canopy fit) per candidate.
src/lib/scoring/terrain.ts
Classic heuristic terrain scoring. Given a candidate location extracted from the LiDAR pipeline (saddle, funnel, ridge, bench, field edge, or bedding area), computes a 0–100 score based on how well the local terrain matches the weapon-specific canopy range and general deer movement heuristics (preference for moderate slopes, south/southeast aspects, concave TPI for saddles, etc.).
Inputs
- Candidate feature type (saddle, funnel, ridge, bench, field edge, bedding)
- Slope, aspect, TPI, elevation position from USGS 3DEP DEM
- Canopy percentage from NLCD 2021 or Esri Sentinel-2
- Weapon type (bow / crossbow / rifle)
Output
0–100 terrain score with per-factor breakdown for the Score Breakdown UI.
Note: This is the heuristic layer that RSF scoring replaces when real GPS-collar data coefficients are available.
Resource Selection Function (RSF) Scoring
Replaces heuristic terrain scoring with published Resource Selection Function beta coefficients applied to local covariates.
src/lib/scoring/rsf-scoring.ts
The RSF surface is pre-computed per geography by pipeline/compute_rsf_surface.py using published seasonal beta coefficients from Darlington et al. 2022 (Scientific Reports) applied to DEM slope/aspect, NLCD land cover, NHD distance-to-water, and computed distance-to-edge. At request time, rsf-scoring.ts reads the candidate's pre-enriched RSF probability, rescales it to 0–100, adds feature-type bonuses (funnel +25, saddle +20, etc.), and applies weapon-specific canopy modifiers. When RSF data is unavailable for a geography (pipeline not yet run), it falls back gracefully to the heuristic terrain score.
Inputs
- Pre-enriched rsf_score from the pipeline (if available)
- Feature type (funnel / saddle / ridge / bench / field edge / bedding)
- Weapon type
- Canopy percentage
Output
Blended terrain score, feature bonus, weapon modifier, and a scoring_mode flag (rsf | heuristic | both).
HMM-Inspired Behavior States
Hour-by-hour prediction of deer behavior states (BEDDING / FORAGING / TRAVELING) from real GPS collar activity budgets.
src/lib/scoring/behavior-states.ts
Implements a 3-state hidden-Markov-inspired model using real GPS collar activity budgets loaded from src/lib/scoring/config/behavior-matrices.json. The matrices encode 24-hour activity percentages from MSU Deer Lab 15-minute GPS fix data (Strickland et al. 2023) plus the counterintuitive midday-buck-peak finding from the Penn State Deer-Forest Study. Photoperiod-based season detection auto-adjusts the rut phase by latitude and date. Moon phase is explicitly excluded per Webb et al. 2010's seven-year, 32-deer finding of zero measurable effect.
Inputs
- Hunt date and latitude (photoperiod calculation)
- Hour of day
- Sex (buck or doe)
- Current rut phase
Output
Per-hour movement probability and dominant behavior state for the timing chart.
Note: Paterson 2023 is cited for HMM methodology only — that paper studied pronghorn and mule deer, not white-tailed deer. WTD-specific numbers come from Strickland 2023 and Webb 2010.
Rut Phase Tracker
Photoperiod-based rut phase detection with daily distance and home range predictions.
src/lib/scoring/rut-tracker.ts
Walks forward and backward from the given date to detect phase boundaries using photoperiod (day length), then looks up phase-specific daily movement distance (yards), home range expansion (acres), and hunting strategy from the MSU P3927 publication. The 1.7× rut-movement claim in EPIC-009 was reconciled against this source: the actual published numbers are 4,800 yd/day pre-rut → 7,500+ yd/day peak rut (1.56× daily total), with NET DISPLACEMENT growing 1.7× (see STORY-065 for details).
Inputs
- Hunt date
- Latitude (drives photoperiod)
Output
Rut phase, days into phase, phase progress, buck daily distance, home range acres, hunting strategy text, day length.
Hunting Pressure Response
Multi-factor hunting pressure model combining season-long decay, stand-specific learning, and the Oklahoma hunt-day effect.
src/lib/scoring/pressure.ts
Composite 0–1 pressure score that multiplies the behavior score. Four factors: (1) temporal decay via exp(−0.025 × daysSinceOpener) calibrated to Penn State's ~25% of opener by late November finding; (2) stand-specific sigmoid avoidance via 1 / (1 + exp(2.0 × (effectiveVisits − 2.5))) calibrated to Auburn's 4+ visit abandonment finding (Sullivan et al. 2018); (3) calendar weekend/weekday modifier; (4) land-type multiplier (0.67 public / 1.0 private). STORY-063 added the explicit Oklahoma hunt-day effect from Marantz et al. 2016: weekends during hunting season get an additional multiplier (−17% daytime / −31% nighttime movement) applied via applyHuntDayEffect. STORY-040 also exposes getCoverPreferenceShift() for the Penn State 240% cover-use escalation used by the terrain path.
Inputs
- Hunt date, latitude (for season opener estimation)
- Day of week
- Stand visit count and last visit date (per candidate)
- Public vs private land (from PAD-US)
Output
Composite pressure score 0–1, per-factor breakdown, human-readable label, days-to-recovery estimate, cover-shift multiplier.
Weather-Movement Correlation
Rate-of-change weather modifier (temperature delta, pressure trend, cold fronts) applied to the base weather score.
src/lib/scoring/weather-movement.ts
GPS collar research consistently shows that RATE OF CHANGE is a stronger movement predictor than absolute values. A 15°F+ temperature drop with rising barometric pressure (a cold front) produces the single strongest movement response in the literature. This module computes a composite multiplier (0.3–2.0) from temp delta, pressure trend, wind impact, and precipitation impact, then applies it to the raw weather score from the Open-Meteo HRRR forecast. Moon phase is explicitly excluded.
Inputs
- Current and previous-day high temperature
- Barometric pressure and trend
- Wind speed and direction
- Precipitation probability and inches
Output
Weather-movement modifier 0.3–2.0, per-factor breakdown, cold-front-detected flag.
Food Plot Arrival Timing
Predicts deer arrival time at food sources with pressure-adjusted shift from early to late season.
src/lib/scoring/food-plot-timing.ts
For candidates near food sources (NLCD land cover 71/81/82 or field_edge feature), predicts the estimated arrival time relative to sunset. Opening-week arrivals average 30–60 min before sunset; by gun-season week 2 arrivals shift to at-or-after dark. Includes a rut-disruption flag for bucks during peak rut (when food-plot patterns break down in favor of doe-seeking). Pressure level is derived from the pressure model's spatial factor.
Inputs
- Hunt date and location
- Days since opener
- Pressure level (derived from pressure.spatial factor)
- Bedding distance (approximate)
- Sex and rut phase
Output
Estimated arrival HH:MM, minutes relative to sunset, legal-hours flag, recommended setup time, rut-disrupted flag, explanation text.
Movement Corridor Detection
A* least-cost-path corridors between predicted bedding and feeding areas with funnel detection.
src/lib/corridor/auto-corridors.ts
Pre-computes a cost surface per geography from DEM slope + NLCD land cover + water barriers (pipeline/compute_cost_surface.py). At recommendation time, detects bedding and feeding clusters from PostGIS terrain features, runs A* with a MinHeap across the cost grid to find least-cost paths between each bedding-feeding pair, and identifies topographic funnels (pinch points) along the paths. Candidates near a corridor or funnel get a terrain score boost of 0–15 points.
Inputs
- Cost grid (pre-computed per geography)
- Terrain features from PostGIS (bedding and feeding classifications)
- Candidate locations for proximity scoring
Output
CorridorResult[] with paths, travel times, funnels detected, and per-candidate corridor proximity boost.
Hunter Approach Route Planner
A* walking route from the property edge to each recommended stand, with wind, bedding, and water cost layers.
src/lib/routing/approach-planner.ts
For every top-N recommendation, plans the lowest-cost walking route from a polygon edge entry point to the stand. The ~10m grid composes four cost layers: (1) base distance, (2) downwind scent exposure — a wind cone projected from each other predicted deer position toward the hunter so the route avoids carrying scent into the rest of the herd, (3) bedding-zone avoidance — a gradient around predicted bedding sites so the hunter doesn't bump deer on the approach, and (4) NHD HR water polygons — a hard barrier inside the polygon plus a soft shoreline buffer within ~30m. The auto-detected entry point is biased toward crosswind/downwind approach bearings, or the user can pin an explicit entry point. The static layers (polygon mask, bedding cost, water cost) are built once per request via buildSharedRouteContext and reused across all candidate routes; only the per-candidate scent overlay is recomputed. A* uses a binary min-heap open set, and the path is smoothed via Chaikin's corner-cutting after pathfinding.
Inputs
- User-drawn polygon (defines walkable area)
- Predicted bedding zones from PostGIS terrain features
- NHD HR water polygons intersecting the polygon bbox
- Wind direction + speed from Open-Meteo HRRR forecast
- Other top-N recommendations as predicted deer positions (scent avoidance)
- Optional user-specified entry point (overrides auto-detection)
Output
GeoJSON LineString attached to each recommendation as approach_route, with distance_miles, time_minutes, wind_exposure_score (0–100), and a natural-language start_description.
Note: Water avoidance was added to fix routes that drew across reservoirs and large lakes. The hard-barrier polygon mask plus soft shoreline buffer guarantee no path segment crosses standing water, even at the cost of longer walks. The shared static grid + binary heap optimization in STORY-068 took the per-request planner cost from 25,778 ms to 40 ms.
RSF Validation Harness (STORY-060)
Validates predicted RSF probabilities against real Dryad GPS collar fixes using the continuous Boyce Index and AUC.
pipeline/validation/validate_rsf_against_dryad.py
Standard wildlife-ecology validation methodology. Loads real used-points from the collar_tracks PostGIS table (with a hard safeguard that REFUSES to validate against synthetic data), generates 10× random available-background points within the study minimum convex polygon, samples the RSF GeoTIFF at both sets of points, and computes the continuous Boyce Index (Hirzel et al. 2006) plus Mann-Whitney U AUC plus per-season calibration bins. Outputs a markdown report and machine-readable JSON artifact the UI can display.
Inputs
- Real Dryad GPS fixes from collar_tracks (study_source=dryad_nyc by default)
- Seasonal RSF GeoTIFF per geography
- Target season (early / pre_rut / rut / post_rut / late)
Output
Boyce Index, AUC, calibration bin table, research/validation/rsf_validation_<season>.md + .json
Note: This is the story that lets a client ask "how do you know the RSF model actually works?" and get a real number back instead of hand-waving.