Blockbuster — Approaches & Algorithms

A guide to the mathematical models and techniques behind Blockbuster's route-planning engine.

1. What Blockbuster Does

Blockbuster is a route-planning tool for traversing landscapes where different regions carry different kinds of danger. Given a terrain map, a set of waypoints, and an analyst's risk tolerances, it solves a variant of the shortest-path problem — except that "shortest" means lowest total risk-adjusted cost, not merely shortest distance — where here "risk" means the analyst's encoded judgement, made explicit and repeatable, not an objective measurement of danger.

The system produces three ranked Courses of Action (COAs), each taking a distinct corridor through the terrain. This lets the analyst compare trade-offs quantitatively: one route might minimise heat exposure at the expense of higher bandit risk; another might be physically longer but avoids all elevated-risk cells.

The pipeline has four core stages: terrain generation → hex grid overlay → risk assessment → route optimisation. Over that static map sits a time dimension — a departure time and travel speed, a day/night cycle, drifting hazards and a rotating cyclone — so the danger of a cell depends on when you reach it and which way you are heading. Each stage is deterministic — the same inputs always yield the same outputs. Everything runs client-side in the browser; no data leaves the machine.

2. Procedural Terrain Generation

The landscape is generated procedurally from a single seed integer. The seed initialises a deterministic pseudo-random number generator (mulberry32, a fast 32-bit PRNG), guaranteeing that the same seed always produces an identical map — useful for reproducibility and scenario sharing.

The underlying technique is Perlin noise, a gradient-based noise function that produces smooth, continuous variation across a 2D plane. At each integer lattice point, a pseudo-random gradient vector is assigned (via a deterministic hash — no sine functions, avoiding floating-point artefacts). The noise value at any point (x, y) is computed by interpolating the dot products of these gradients with the offset vectors, using Perlin's smootherstep function:

smootherstep(t) = 6t5 − 15t4 + 10t3

This gives C² continuity (both the value and its first derivative are smooth), producing natural-looking contours rather than blocky artefacts.

To create terrain with features at multiple scales — large mountain ranges and small rocky outcrops — multiple octaves of Perlin noise are summed in a technique called Fractal Brownian Motion (FBM). Each successive octave doubles the frequency and halves the amplitude:

FBM(x, y) = Σi=0n−1   0.5i · perlin(2ix,  2iy)

The result is normalised to [0, 1]. Separate FBM fields (with different seed offsets) drive elevation, vegetation density, temperature, and other terrain attributes. The biome at each point — woodland, savannah, mountains, etc. — is classified from these continuous fields.

Try it — change the seed to generate a different landscape
42

Each pixel is computed from multi-octave Perlin noise (FBM). The same seed always produces the identical terrain field.

3. Hex Grid Tessellation

The terrain is discretised into a hexagonal grid using axial coordinates (q, r) — a two-axis system where the implicit third coordinate is always s = −q − r (the "cube constraint"). This is mathematically elegant because hex distance reduces to a simple formula:

d(a, b) = max(|Δq|, |Δr|, |Δs|)   where   Δs = −Δq − Δr

The key advantage over a square grid is isotropy: all six neighbours of a hex cell are equidistant from its centre. In a square grid, the four diagonal neighbours are √2 ≈ 1.41 times further than the four cardinal neighbours, which introduces directional bias into any pathfinding algorithm. Hexagons eliminate this.

The grid uses pointy-top orientation with a configurable circumradius (centre-to-vertex distance). The default of 2.4 km yields approximately 100 cells over the standard 50 km × 30 km map extent — as specified in the project brief. The circumradius is adjustable live — down to 0.6 km for a much finer mesh — with the grid re-tessellating immediately and only the re-plan it triggers debounced.

Isotropy comparison — neighbour distances in square vs. hex grids

Square grid — diagonals are √2 ≈ 1.41× further

Hex grid — all neighbours equidistant

4. Risk Assessment

At each cell's centre, the terrain field is sampled to obtain continuous attributes — elevation, temperature, vegetation density, water proximity, and bandit activity. These are then mapped to five independent risk channels, each normalised to the range [0, 1]:

Together these form the cell's risk profile — a five-dimensional vector. Analysts can override any channel if they possess better local intelligence; overridden values take precedence during route planning and are visually highlighted in the UI.

Interactive — terrain attributes mapped to risk channels

Terrain properties

0.70
0.20
0.60
0.30
0.10

Resulting risk profile

Drag the terrain sliders to see how landscape features drive each risk channel.

5. The Cost Function and Risk Appetite

The cost of traversing a cell is a weighted sum across all five risk channels. The analyst controls a risk appetite per channel — a value in [0, 1] where 0 means "intolerant" and 1 means "fully tolerant." The sensitivity is simply the complement:

sensitivity = 1 − appetite

The per-channel cost contribution is then:

costchannel = riskchannel × sensitivitychannel × wrisk

where wrisk is a global risk weight (default 10). The total risk cost for the cell is the sum across all five channels. A separate movement cost — proportional to the physical distance in kilometres — is added to prevent routes from taking excessively long detours to avoid minor risks. The total step cost is therefore:

step cost = Σchannels costchannel + distance(km) × wdistance

Crucially, this cost function is shared between the routing engine and the UI charts — both call the same code, ensuring that what the analyst sees in the stacked bar charts is exactly what the optimiser minimised.

Interactive — adjust appetites to see their effect on per-channel and total cost
0.50
0.50
0.50
0.50
0.50

Fixed risk profile for this cell: Animals 0.6, Cold 0.3, Heat 0.8, Water 0.5, Humans 0.2 (wrisk = 10).
Faded bars show the raw risk × wrisk; solid bars show the effective cost after appetite scaling.

6. The Journey Clock & Travel Speed

The sections above describe a static map: one fixed risk profile per cell. But a real journey happens in time — it departs at a chosen hour and moves at a chosen speed, and the danger of a place depends on when you are there. Blockbuster layers this on as a modifier chain: each cell's base profile is reshaped, in order, by travel speed, the day/night cycle, any moving hazards, and the wind, before the cost function (Section 5) scores it. The planner re-evaluates the whole chain for every cell at the time the group would actually arrive.

The risk modifier chain — recomputed for every cell at its arrival time
Base profile Travel speed Day / night Moving hazards Wind Cost

Each link multiplies or offsets one or more of the five channels, clamped back into [0, 1]. The charts and the planner share this exact chain, so the costs you see are the costs that were optimised.

The analyst sets a departure time and a speed mode. Speed is bounded to a realistic 5–30 km/h band, and the mode decides how it is chosen:

Speed matters because it is coupled to risk, not just to travel time. Moving fast helps you slip past predators and bandits — animal and human risk fall to half at top speed, because you are harder to catch — but it bites with wind-chill, so cold risk doubles. Heat and thirst are indifferent to pace. Faster also means you reach each cell sooner, which shifts where you land in the day/night cycle and the path of any moving hazard.

Interactive — how speed trades animal & human risk against cold
15 km/h

Speed multiplier per channel (baseline = ×1)

Resulting cell cost (faded = at walking pace)

A fixed sample cell (animals 0.7, cold 0.5, heat 0.3, water 0.4, humans 0.6) at a neutral 0.5 appetite. Notice the sweet spot: pushing the pace sheds animal and human cost, until the rising cold term cancels the gain.

7. Day and Night

With day/night variation switched on, the clock reshapes two channels. Night runs from 20:00 to 06:00 (wrapping midnight). After dark, wildlife settles — animal risk drops to ×0.5 — but human (bandit) risk climbs to ×1.5, the classic ambush window.

There is one important exception. Inside a town the human threat is the settlement, and a settlement is at its safest in the dead of night: during the deep-sleep window of 01:00–05:00 a town's human risk instead collapses to ×0.5. Out in open country the bandit risk stays elevated all night. The other three channels — cold, heat, thirst — are left to the speed and weather factors.

Interactive — risk multipliers across the 24-hour clock
08:00
Animals × Humans — open country × Humans — in town × Night Deep sleep

Scrub the clock: the cursor tracks the three step-curves. Night shades the 20:00–06:00 band; the darker inset is the 01:00–05:00 deep-sleep window where towns go quiet.

8. Moving & Time-Bounded Hazards

Analysts can paint extra-risk zones straight onto the map (rectangle, circle or polygon) from the Extra factors tab. Each zone nudges one channel by a signed offset (−0.5 … +0.5); a cell's share is area-weighted — the fraction of the hex the zone covers, times the offset — and offsets from overlapping zones add. A purely static zone is folded into the cell profiles once and forgotten.

Zones get interesting when they gain a time window or motion. A zone with a start/end time counts only while the clock is inside it. A storm band goes further: a cold-risk parallelogram that sweeps across the map — east to west — over its active window. Its position is never stored; the planner recomputes the band's footprint on demand for the exact moment the group would reach each cell. So whether a storm hurts you turns on the race between your route and the front.

Interactive — a cold storm band sweeping its active window
10:00 · sweeping
5 cells

Active 08:00–16:00. The band enters from the east at 08:00 and clears the west edge by 16:00; outside that window it is gone entirely. Cells whose centre falls inside the band pick up the extra cold the moment you are there.

9. The Cyclone — a Directional Wind Field

The richest temporal factor is the cyclone: a rotating wind field whose eye drifts across the world over an active window. Unlike a zone — a flat scalar offset — wind is directional. Its effect depends on how your heading relates to the local air, so it is computed in two halves.

First, the wind vector at a point and time. Strength follows a radial profile around the eye — a calm eye, rising to a peak at the eyewall, then decaying to nothing by the outer radius. Direction is the anticlockwise tangent: the air circles the eye, running 90° to the line out from the centre.

Second, the effect of travelling a heading through that wind, set by the alignment between the two:

In between, the impact builds up exponentially as your heading lines up with the wind, so only a near-dead head- or tail-wind feels the full force. Because a headwind both costs more and takes longer, the planner will happily bend a route to take the wind on the beam or the back. A tailwind, by contrast, lowers a cell's cost below the straight-line floor — which is precisely why the distance heuristic the planner relies on (Section 10) is no longer strictly admissible once wind is active, so under wind A* is a high-quality heuristic rather than a guarantee.

Interactive — the rotating field and its pull on a traveller

Travelling through the wind here

12:00
E · 0°
Eye (calm) Eyewall (peak) Outer edge Wind

Click the map to move the traveller (the white dot); spin the heading to feel the wind shift from head to cross to tail. Scrub time to drift the eye along its track. The arrows are the wind field — their length and depth show its strength.

10. Route Optimisation (A* Search)

Given a set of waypoints, the system finds the lowest-cost path through the hex graph using the A* algorithm. A* is a best-first graph search that maintains a priority queue of candidate cells, ordered by f(n) = g(n) + h(n), where:

On a static map, risk and the diversity penalty only ever add cost on top of that straight-line floor, so this heuristic is admissible and A* returns the optimal leg. With temporal modifiers active, cost depends on arrival time, so the search is best understood as a high-quality time-aware heuristic rather than a proof of optimality — the state space it would need to guarantee that is (cell, time), not cell alone. As it expands, A* threads the running arrival time through each cell, so day/night, moving hazards and wind are all evaluated for the moment the group would actually be there. On a hex grid of ~100 cells and degree 6, the search runs in well under 50 ms.

The widget below makes that search visible. A* explores outward from the start, always opening the most promising cell next — the one with the lowest f = g + h. Step through it, or press Play, and watch the two sets grow: the frontier (gold outlines — cells discovered but not yet committed) and the explored set (navy — cells whose best cost is now settled). When the frontier reaches the goal, the path is traced back through the recorded parent links.

The most instructive control is the heuristic weight. Slide it to 0 and A* degenerates into Dijkstra's algorithm — with no sense of direction it explores in an expanding blob, equally in all directions. At weight 1 it is true A*: the same optimal path, but the explored set is visibly pulled toward the goal, so far fewer cells are touched. Push it above 1 and it becomes greedy / weighted — it rushes at the goal and finishes even faster, but may settle for a slightly costlier route. This is the central trade-off in heuristic search, the same one the Red Blob Games tutorials illustrate.

Interactive — watch A* explore the cost-weighted hex grid
A* (optimal)
16/s
7
Start Goal Explored Frontier Path Wall cell cost (low→high)

Each hexagon is shaded by its traversal cost — green is cheap, red is costly — exactly the risk-weighted cost the planner minimises. The two dark clusters are pre-placed blocked cells — impassable terrain the planner must route around. Click any cell to add or remove blocked cells and watch A* adapt. Drag the New map slider to regenerate the cost field (blocking clusters reset with the map).

Multi-waypoint routes are computed segment by segment: A* is run independently between each consecutive pair of waypoints, in the order given, and the sub-paths are concatenated. The analyst can optionally let the planner reorder the intermediate waypoints to cut total cost — a greedy nearest-neighbour pass (a lightweight travelling-salesman heuristic) that keeps the start fixed. This is a lightweight heuristic, not a TSP solver: legs are routed independently, so the combined route carries no global visit-once guarantee and may revisit cells across legs. Cells flagged impassable are hard-blocked and routed around; a waypoint stranded behind a block stays reachable, because the leg that needs it falls back to a passable path.

Generating three diverse COAs

A single optimal path is rarely enough for a decision. To produce three genuinely distinct Courses of Action, the planner combines strategy biases with an overlap-penalised fan-out. It first runs the search under three deliberately different cost balances, then keeps fanning out — each time inflating the cost of cells an accepted route already used — and rejects any candidate that retraces too much of an earlier one. Every survivor is finally re-scored under the analyst's real settings, so the ranking is honest no matter which bias discovered the route.

How the planner fans out into three distinct corridors
Strategy biases
Search three ways — Direct, Balanced, Cautious — to surface naturally different corridors
Penalise reuse
Multiply the cost of every cell an accepted COA already crossed
Fan out
Re-run A* under the real settings — it now steers around the penalised cells
Reject near-duplicates
Drop a candidate that overlaps an accepted route by more than 80%
Score & rank
Re-cost every survivor under the analyst's real appetites; keep the three cheapest

The reuse penalty is a soft constraint — used cells aren't forbidden, just made expensive — which fans each successive COA onto fresh ground. (Overlap is measured as the Jaccard ratio of shared to total cells.) Re-scoring under the real cost function keeps the three totals directly comparable.

The three COAs are ranked by total cost. Each records a per-cell breakdown of risk contributions, enabling the detailed visual comparison described in the next section. COA discovery is therefore heuristic by design — the strategy biases, reuse penalties and overlap rejection above decide which corridors surface — but COA ranking is exact: every survivor is re-scored under the analyst's real settings, so the three totals are directly comparable.

11. Comparing Courses of Action

The three COAs are visualised as stacked bar charts. Each bar represents one cell along the route; each coloured segment shows the contribution of one risk channel to that cell's cost. The total cost of each COA is displayed above its chart. When the journey has timing, the bars are laid along a shared time axis (departure → arrival) and each COA is annotated with its clock and travel speed, so you can read off when each risk is met as well as how much it costs.

This decomposition is possible because the cost function is additive across channels — the analyst can immediately see which risks dominate each route and make an informed choice. For example, one COA might show tall red (heat) segments while another shows tall purple (human) segments of similar overall height, representing a genuine trade-off.

Interactive — stacked bar charts for three COAs
1

Each bar is one cell on the route; colour indicates the per-channel risk cost. Slide to randomise the risk distributions and observe how the trade-offs change.

12. Responsiveness and Concurrency

Route optimisation — three A* searches across a ~100-cell graph — can take tens of milliseconds. To prevent the UI from freezing during computation, the routing engine runs in a Web Worker (a browser-native background thread). The main thread remains responsive to slider adjustments and map interactions throughout.

User actions are debounced with a ~150 ms window: rapid successive changes (e.g., dragging a slider) are coalesced into a single route-planning request. If the analyst modifies the inputs while a plan is in progress, the system applies stale-guarding — it checks whether the returned plan's waypoints still match the current state, and discards outdated results.

Data flow — from user action to updated COA display
User adjusts a slider or moves a waypoint
Debounce — wait 150 ms for more changes
Build route request with current settings
Send to Web Worker (background thread)
Worker runs A* × 3 and returns 3 COAs
Stale check — do the waypoints still match?
✅ Yes → display results   ❌ No → discard & re-plan

13. Assumptions & Limitations

Blockbuster is a model, not a measurement. The figures it optimises are encoded analyst judgement — made explicit and repeatable, but not objective readings of danger, and running them through arithmetic does not make them true. Read every total with that in mind:

What the architecture does earn is auditability and reproducibility: the planner and the charts share one cost function, and the same seed and inputs always reproduce the same plan. The payoff is not that the numbers are objectively right — it is that every assumption behind a route is explicit, inspectable and repeatable. Measurement makes the assumptions visible; it does not certify them.

14. Glossary

COACourse of Action — one of the three route options the system generates.
SeedA number that controls the random landscape generation. Same seed = same landscape.
Hex cellOne hexagonal tile on the grid. Each cell has its own risk profile.
Risk profileThe five danger scores (animals, cold, heat, water, humans) for a single cell.
Risk appetiteHow tolerant the analyst is of each risk type, set via sliders (0 = avoid, 1 = tolerate).
WaypointA cell the analyst selects as a destination — the route must pass through all waypoints.
A* algorithmA well-known shortest-path algorithm that finds the lowest-cost route through a graph.
Multi-leg routingConsecutive waypoint pairs solved independently and concatenated; cells may be revisited across legs (no global visit-once guarantee).
Perlin noiseA method for generating smooth, natural-looking random patterns (used for terrain).
Web WorkerA browser feature that runs heavy calculations in a background thread, keeping the UI responsive.
DebounceWaiting briefly before acting on rapid repeated changes, so only the final state is processed.
Modifier chainThe ordered set of adjustments — speed, day/night, moving hazards, wind — applied to a cell's base risk before it is costed, recomputed at the arrival time.
Speed modeHow travel speed is chosen: Fixed (one constant speed), Optimal (best of several constant speeds), or Dynamic (chosen per cell).
Day/night variationAn optional toggle that calms animals (×0.5) and emboldens bandits (×1.5) at night — except towns, which quieten in the deep-sleep hours.
Deep-sleep window01:00–05:00, when a town's human risk drops to ×0.5 because the settlement is asleep.
Extra-risk zoneAn analyst-drawn rectangle, circle or polygon that offsets one risk channel for the cells it covers (area-weighted).
Storm bandA cold-risk zone that sweeps across the map over a set window — a moving, time-bounded hazard.
CycloneA rotating wind field whose eye drifts across the world; its effect on speed and risk is directional (head/tail/cross wind).
Headwind / tailwindTravelling into the wind (slower, riskier) versus with it (faster, safer) — relative to the local wind direction.
Arrival windowAn optional earliest/latest arrival time on a waypoint; missing it adds a soft cost penalty rather than blocking the route.