| Concern | Choice | Rationale |
|---|---|---|
| Language/runtime | Vanilla HTML + CSS + JS, single .html file |
Hard requirement: offline, no build step, runs from
file:// |
| Modules | One inline <script>, no ES module imports |
ES modules over file:// hit CORS restrictions; a single
inline script sidesteps it entirely |
| Rendering | DOM grid per panel, incremental cell updates | See "Rendering decision" below |
| Animation | requestAnimationFrame + time accumulator |
Smooth, frame-rate-independent lockstep stepping |
| Styling | Inline <style>, system font stack, CSS variables
for the shared palette |
No external fonts/CSS; CSS vars give one source of truth for the shared visual language |
| Dependencies | None | Reimplementing the four algorithms ourselves (see Brainstorm) |
Zero external dependencies, so
npm audit / pip audit are N/A — there is no
supply chain. This is the most secure posture available and follows
directly from the single-file/offline goal. The only self-imposed
disciplines (carried from Brainstorm):
textContent;
never innerHTML.eval, no new Function, no dynamic
<script>. Nothing to inject into.A standalone Node prototype of the search core was written and executed before committing. Test grid: a short route blocked by "mud" (weight 9) vs. a longer all-cheap (weight 1) detour.
| Algorithm | Steps | Path cost | Cells visited | Teaching point confirmed |
|---|---|---|---|---|
| BFS | 4 | 28 | 14 | Fewest steps, ignores weight → costly; explores widely |
| Dijkstra | 6 | 6 (optimal) | 7 | Finds genuinely cheapest path |
| Greedy | 4 | 28 | 5 | Fastest (fewest visits) but suboptimal |
| A* | 6 | 6 (optimal) | 7 | Optimal cost like Dijkstra, directed search |
Confirmed properties:
Cost model. Each cell has a weight = cost to
enter it (normal = 1, "mud" = a higher constant, e.g. 5–10). Walls
are impassable. g(n) = sum of weights of entered cells.
g.h = Manhattan distance (steps) to
goal.g + h.Data structures.
Array.shift() O(n).cameFrom map, gScore
map, closed set — keyed by "r,c".Shared maze, four views. One maze model (walls/weights/start/end) is the single source of truth. The four panels are renderings of that shared maze plus their own per-search overlay (open / closed / path). Painting on any panel mutates the shared model and re-renders all four.
Rendering decision: DOM over Canvas. Each panel is a
CSS-grid of cell <div>s with
data-r/data-c. Per tick we mutate only the
changed cells' CSS classes (typically a handful), not the whole
grid — so cost scales with work done, not grid size. DOM also makes
painting trivial via event delegation
(mousedown/mouseenter with a drag flag) and
gives crisp, CSS-variable-themed styling for the shared legend. Canvas
would force full or dirty-rect redraws and pixel→cell math for painting
— more code, no benefit at this scale.
Lockstep race. A single
requestAnimationFrame loop with a time accumulator. Each
"tick" advances every not-yet-finished search by one node
expansion, keeping the race fair and watchable. A speed slider
maps to ms-per-tick (and at max speed, multiple expansions per frame).
Play / Step / Reset control the loop.
Tie-breaking. Heap ties broken by insertion order;
for A*/Greedy this yields the classic clean "directed" frontier.
(Optional later polish: break ties toward lower h for
prettier A* fronts — noted, not required for iteration 1.)
Browser APIs used (all standard, offline-safe, no
flags): DOM, requestAnimationFrame, mouse events, CSS Grid,
CSS custom properties. No fetch, no storage, no
workers.