Let me plan this out. The user wants a single HTML file web game, top-down, quick-paced, themed "the hunter and the hunted", showing good A* pathfinding and AI decision-making. Placeholder graphics (simple canvas shapes). I should build something well designed. Let me think about the concept. Concept ideas: The player is the hunted? Or the hunter? Maybe player plays as a "prey" creature escaping hunters, or as a hunter chasing prey. To show A* well, maybe multiple AI agents: hunters with roles (flanker, blocker, chaser), and prey that flees using flocking + pathfinding. A good design: You play as a hunter (pack AI?) No — better: You play as the Prey, escaping a pack of AI hunters that coordinate: they call each other, flank, cut off escapes, use A* on a grid over a procedurally generated map with obstacles. There's also a "hunted becomes hunter" twist — maybe you can turn and fight when at full stamina, hunting smaller prey. Better twist to make it quick-paced: You are the last of the hunted. You must collect "scent tokens"/"signal relays" while being hunted. Hmm. Alternative stronger loop that displays both hunter and hunted AI: Waves. Wave-based: you're the hunter chasing "skitterers" (prey that flee with flee-AI and use A* to reach escape routes/holes), and then waves escalate where the prey becomes organized and starts hunting YOU (role reversal at threshold, e.g., when prey pack gets low, they turn and hunt you). This literally shows "the hunter and the hunted" and requires both pursuit (A* to target, interception, cornering) and flight (A* to safety, threat avoidance). I like this: single-screen arena(s), grid-based, procedurally generated obstacle field. Player = "Tracker". Prey use A* to reach nearest den/burrow, and they panic, scatter. When you corner them they die. As kills increase, remaining prey get "cornered/feral" state and turn aggressive, becoming hunters chasing you. Then more waves. Also new hunter types: "Stalker" that ambuses (goes to a location and hides), "Runner" that intercepts using velocity prediction, "Caller" that marks you (reveals your position to others = shared knowledge/scent marks). Also good: visualize the pathfinding — a debug overlay that shows the grid, open/closed sets of a selected agent's A* in real time (that's a very nice showcase). Plus show "scent trails" (last known player position fading over time), and knowledge decay: hunters chase last-known position, not true position — if you break line of sight they path to last known pos and then search. That's excellent AI decision-making showcase. Decision-making: utility-based scoring for state selection (Predators: Hunt, Flank, Intercept, Guard/Hold, Regroup, Flee when damaged/low count). Prey: Flee-to-burrow, Scatter, Mob/Feint, Hide. Show a small "brain" panel with selected-agent utility bars. That shows "good implementation" visibly. Scope: single HTML file, canvas 2D, no libraries. Should be quick-paced and fun: grid maybe 40x24 cells at 20px = 800x480. Camera fixed on one arena (no scroll) = simplest and readable. Waves with increasing enemy count and types. Player has: dash (stamina), a "pulse"/scan that reveals, and a melee burst (net/shock) to make kills. Objective each wave: survive + hunt down all prey / or the pack hunts you. Let me define the actual game loop concretely so it's FUN and quick: "You are a Warden in a series of sealed grids. Wave 1-2: hunt the skitterers (prey) and tag/destroy them before they reach the burrows... " Hmm. Need clear win condition per wave. Concrete: Grid Hunter. Each wave spawns N "prey" (dots/triangles). You must eliminate all of them. They flee from you using A* to furthest-from-you safe tile / nearest burrow (burrow = exit hole; if prey reaches a burrow it escapes and you lose a life / score). Prey become "feral" when their pack count >= 3 and they've been damaged, and they'll rally and chase. Actually simpler: prey have morale — they flee when healthy, but if surrounded/hurt they turn on you (cornered rats) — this is realistic and creates the hunter/hunted flip constantly. Then wave escalation: add "Hunter-class" enemies that are always hunters: Stalkers (ambush), a "Pack" that shares knowledge. Between waves, short intermission with a wave banner and maybe an upgrade choice (2 random perks) — that adds depth and is quick to implement (3-4 perks). Player ability kit: Move WASD/arrows. Left click / Space: Strike (short cone burst, short cooldown) — needs proximity → forces you to chase/corner. Shift/Right click: Dash (phase-step) with stamina. E: Pulse/Scan — reveals enemies through walls as skeletons + shows their intended paths briefly, and marks them (knowledge persists). Actually keeping controls simple: WASD move, mouse aim + click strike, Space dash, E pulse. Make kills satisfying: hit flash, particles, screen shake, kill popups, combo timer. Let's think about A* implementation quality: Grid with 4- or 8-directional movement, octile heuristic, tie-breaking on cross product, jump-point? JPS is overkill but "good implementation" — I can do binary-heap open list, octile heuristic, tie-breaker, blocked tiles with corner-cut prevention, path smoothing (line-of-sight string pulling / funnel-ish simplification), path repathing timer, re-target when target moves N cells. Flow-field could be better for many agents but A* requested. I'll do A* with path recalculation budget (max X node expansions per agent per frame? no—just recompute on interval, with node cap). Shared knowledge: agents store lastKnown {pos, age}. If !visible, move to lastKnown, then search pattern (spiral/nearest cover tile). Show "lost you!" state. Roles via utility AI: each agent evaluates actions: Chase, Intercept (predict lead tile), Flank (path around obstacles to a perpendicular point), Guard burrow/exit, Chase last-known, Search, Flee (prey), Burrow/escape, Regroup. Compute utility scores with weights, pick argmax, with hysteresis so they don't flip-flop. Show bars in inspector. Prey "herding" is a nice emergent thing: they flee in the direction away from you, so you can drive them into other hunters / into a wall. Visuals: crisp vector placeholder graphics on canvas: rounded squares for tiles? Placeholder graphics means simple shapes; I can still make it look tidy: dark grid, glowing circles/triangles with facing lines, path polylines faint, debug overlay toggleable with G (show grid, open/closed sets of selected agent, paths). Nice showcase: press G to toggle "Cortex overlay" showing the grid cost field, selected agent's open/closed sets expanding in real time (animated) — I can render the frontier of the currently computing search. I can even make search "animated" by spreading the A* per frame step? Simpler: store arrays of visited nodes and their g/h values, render colored cells for the selected agent's most recent search. That reads beautifully. Also a "memory/scent" visualization: last-known positions as fading diamond markers with age label. I'll pick a visual identity matching the subject: dark charcoal arena with amber/red accents? Or a cold slate with cyan prey and amber hunters. Since the player is the hunter, make the player a bright white/steel chevron; prey cyan-teal; hunters amber-orange. Background: dark warm gray with subtle checker. Use HUD panel top-left with score/wave, and right-side "CORTEX" inspector panel showing the selected agent (tab to cycle or click an agent) with its state, utility bars, knowledge age, path length, nodes expanded. Layout: canvas game area centered, HUD overlays inside canvas maybe, and a side panel in HTML? Single file; I'll draw HUD in DOM for crispness or in canvas. I'll do canvas-only rendering for the arena, and DOM for side panel + top HUD for text crispness. Hmm, mixed sizing/responsive. Simpler: entire UI in canvas except maybe nothing. Actually DOM side panels are easier to style and crisp. I'll do: page grid with left = canvas (fixed aspect, scaled with CSS transform or just responsive scaling via CSS width), right = DOM panel with stats. That is clean, and I can render everything text-y in DOM. Let me decide arena grid: 48 x 30 cells at 18px = 864x540. Hmm, or 44x28 at 20 = 880x560. Fine: COLS=44, ROWS=28, TILE=20 → 880x560 canvas, scaled responsively with CSS to fit viewport (canvas CSS width 100%). Mouse coords need scaling factor handling (use getBoundingClientRect ratio). Good. Now, the game loop details for a quick-paced feel: Waves: Wave N: spawn prey (Skitterers) count = 4 + N, plus Hunters from wave 2+: Stalkers (ambush) and one "Howler/Caller" from wave 4. All enemies must be destroyed. If prey reaches a "BURROW" (a marked exit cell) with... hmm, if prey flee and reach burrow they escape → they don't die but you lose the "catch". To keep the wave winnable (must kill all), escaping prey leaving is fine — but you want all destroyed → escape = wave fails? Better: escaped prey costs you "Integrity" (health/quota). Simplify: Each escaped prey = -1 life (lives = 5). Game over on 0. Prey always flee to nearest burrow, which is why you must corner them. And they turn cornered-feral when low HP. HP: player HP 100, prey damage on contact (bite DoT) etc. Let me be concrete: Prey: HP 2 hits from your strike (1 damage per strike hit, strike does 1 dmg, 3 strike charges?). Keep simple: player strike damage 1; prey HP 3 (fast weak), stalker HP 5, howler HP 4, and prey damage: touch deals 6 dmg w/ cooldown; feral prey deal more. Player dash: 1.5s cooldown, i-frames-ish, gives "tag" stacking? Keep simple. Weapon: "Arc Prod" — a cone burst in facing direction, range 3.5 tiles, damage 1, knockback, cooldown 0.35s. Combo/tag: tagging prey marks them (reveals, +damage). Fine. Extra layer for depth: pickups: "signal motes" dropped on kill, collect for score/energy. Energy used for Pulse/Scan and Dash? Two resources: Stamina (dash) and Charge (strike)... Keep: Dash uses stamina regen; Pulse uses charge gained from catching prey → rewards hunting. Good loop: kills charge your scanner. Upgrades between waves: choose 1 of 3: +1 strike damage (or +range), faster dash, wider cone, mark slows prey, prey knowledge decays faster for them (they lose your trail faster), +1 max HP, bigger scan radius, prey drop extra charge, dash damages. That's a solid game in ~1000 lines. Let's make sure pathfinding & AI shine, which is the explicit ask. I'll include: NavGrid: uniform-cost grid, blocked/blocked-since-updated. aStar(start, goal, opts) returning {path, opened, closedSet for viz, nodesExpanded, found}. With binary heap, octile, corner-cut rule, tie break, early-exit if goal unreachable → return closest node? That's a good detail: "partial path" when unreachable. Path smoothing: stringPull(path) removing waypoints with LOS. Steering: arrive + separation + obstacle avoidance for body radius. Agent knowledge model: knowledge = {x,y,age,quality} updated by LOS + cone? For hunter agents, they "see" you if LOS and within sight range; else knowledge decays. Utility AI: for hunters: Chase (champion/nearest pursuer): score ~ knowledge freshness, dist Intercept: lead target by (dist/speed) factor, pick tile Flank: perpendicular offset path point (they route around walls) — score high when prey is in corridor? Seal/Guard burrow: score when prey has high escape pressure. Search (when knowledge stale): sweep spiral. Flee/Recover: when HP low → retreat to a safe pocket (uses A* to furthest tile). For prey: Burrow (escape): score based on distance to nearest exit and threat distance. Scatter/Flee directly away: when threat very close and burrow far. Cornered/Rally (feral): when HP low or surrounded → chase player. Flee via A* to far tile. Roles assigned by the pack so they don't all do same thing — role assignment: the closest becomes "Chase", one gets "Flank", one "Seal" — I'll implement via a small "role auction": each agent scores per role and claims the best available role. That auction detail is a genuinely good AI showcase and easy: iterate roles sorted by demand; agents bid. Debug overlay: grid, per-agent path, open/closed viz for the inspected agent, knowledge diamonds, sight cones, A* animation. Also should I show the A* "expanding frontier" animated? I can implement A* with a step-by-step iterator: each frame, agents' active search advances some steps, but that complicates. Alternative: when an agent recomputes, store search.visited grid arrays; render them dimmed for the inspected agent, and I make recomputes frequent enough (every ~0.35s) that the overlay visibly pulses/expands. Good enough and honest: label it "last search expansion". Hmm, but a nice touch: when you press G, show the currently-computing search of the inspected agent in slow-motion replay? Simpler: overlay draws visited cells colored by (g vs h) with alpha decaying with age since expansion → gives an expanding-rings appearance naturally. Let me now also plan controls/UX polish: pause (P/Esc), restart, mute (M), speed? Show "next wave in Xs", combo meter, and a top HUD with wave, lives, score/combo, and a legend. Also a start screen with title, theme text, controls, and difficulty? Keep one difficulty but maybe 2 (Drill/Overwatch)? Skip difficulty to save complexity; maybe add "Tempo" selector (Cadence/Relentless) as difficulty toggle in HUD. Cheap to add: multiplier on enemy count/speed. Mouse aim: facing = direction to mouse. Click = strike. Also keyboard J? Fine. Let me write the code carefully. Structure: html ...<style>...</style></head> <body> <div id=wrap> <header>... title + wave/score strip? <canvas id=game> <aside id=panel>...</aside> </div> <script>...</script> Let me decide the layout: CSS grid: main column with canvas centered; right panel width ~320px. On small screens stack. Canvas logical size: W=880,H=560, TILE=20, COLS=44, ROWS=28. Device pixel ratio scaling for crispness: canvas.width = W*dpr, ctx.scale(dpr,dpr) and CSS width 100% max 880? Use CSS max-width. Core data: Grid map generation: rooms + pillars + crevices, seeded. Keep TILE=20, ensure connectivity via flood fill: carve, then ensure all floor reachable from center (remove isolated). Add 2-4 burrows (exits) placed on floor tiles near edges, marked. Also new maps each wave? Regenerate each wave with new seed = variety (fresh pathfinding demos). Yes: new arena each wave (with a nice wipe animation). Nice. Entities: player: pos, vel, radius 7, hp, speed 3.4 px/frame... use px/sec: speed 145 px/s (7.25 tiles/s) fast. Enemies ~100-125. prey (Skitterer): r6, speed 130-150 (they're fast but weak; you catch them by cornering since they're predictable? make them 118 so you can corner), hp 2. stalker: hp 5, speed 108, sight cone 7 tiles, damage 9, ambush: moves slowly when hidden? Implement: Stalkers use "Ambush" role: they path to a tile with no LOS to you and near your path, wait, then charge when you're within 3 tiles. howler (Caller): hp 6, slow 95, marks you on sight for all allies (knowledge quality 1) and gives allies +speed briefly; when it dies allies lose coordination (knowledge sharing off) — nice mechanic: kill the caller first → pack loses coordination. Enemy types per wave (progression): W1: 5 skitterers. W2: 4 skitterers + 2 stalkers. W3: 6 skitterers + 2 stalkers (they spawn in two groups). W4: +1 howler. etc. Formula-based. Scoring: tag/damage score, kill score*combo, escaped prey penalty, wave clear bonus, combo multiplier decaying in 3s. Resources: Stamina (dash cost 40, regen 25/s, max 100), Charge (pulse cost 50, gained from kills). Strike cooldown 0.3s; damage 1; cone half-angle 40°; range 3.6 tiles (72px); knock impulse. Pulse (E): reveals all within radius 200px through walls for 3s, sets your knowledge of them (you see them → they're "marked": you see their path preview + they know you see them?). Simplify: Pulse = scan: enemies in radius become revealed (drawn through walls) for 4s, and your "mark" debuff on prey increases damage taken by 15% and slows them 15% while revealed. Dash (Shift/Space): burst speed 620px/s for 0.14s, leaves trail, damages/collides? Let it just reposition with knockback through enemies; deals 1 damage to enemies passed through? That'd make dash a second weapon — fun. Make it a "Phase Step" that deals 0.5 dmg? Keep integer damage: deals 1 damage to enemies it passes, cooldown 1.2s. And strike does 1 dmg, small cone. Fine: strike is primary, dash = mobility + light damage. Hmm—maybe better: Strike = "Arc Prod" cone, dash = pure mobility (no damage) for clarity. Then kills come from strike while cornering. Actually for a "quick-paced" feel, having dash be a stun (0.6s stun) is neat: Phase Step dazes prey briefly. I'll do dash = mobility + brief stasis field that slows enemies passing? Keep it: dash deals 1 dmg on pass-through, cooldown 1.1s. Decided, no more dithering. Prey behavior detail (important for the theme + AI showcase): Prey goal = escape to a burrow. They compute A* to the nearest reachable burrow; if that path is blocked/too long or threat blocks the route, they switch to Flee: A* to a far corner tile. Prey morale: nerve. Each prey has nerve; when they see the player close by and they're damaged they may "RALLY" (turn cornered): they chase and bite. Feral prey move faster and give more score. This creates the hunter↔hunted flip mid-wave. Also howlers can force rally. Player knowledge of enemies (mirror of theirs): you always see through walls with pulse only; otherwise drawn only if LOS? That could be frustrating in a fast game — instead, show all enemies but as dim "unseen" silhouettes when out of LOS, plus their path previews when inspected. That keeps info clear while still visualizing visibility. Good compromise. Let's now write it. Key algorithms code: js // ---------- Nav grid ---------- const COLS=44, ROWS=28, TS=20; grid = Uint8Array(COLS*ROWL) // 0 floor, 1 wall cost = Float32Array (wall=Inf) A* implementation: js class Heap{ constructor(){this.a=[];} size(){...} push(f,idx){...} pop(){...} } function astar(sx,sy,gx,gy,opt){ // returns {path:[{x,y}...], expanded, found, visited:[], g:[], ...} } Store visited for viz in module-level arrays vizG, vizStamp (Uint16 counter) so we can color visited cells for inspected agent. For per-agent stored viz, only store for inspected agent to save memory: if agent===inspected → set a global flag so the search records visited cells into SEARCH_VIZ with timestamps. Implementation of A* with 8-dir and no corner cutting: neighbors check for diagonals that both orthogonal neighbors aren't both walls (or each of them? Standard: disallow diagonal if either cardinal is blocked → strict) plus midpoint pathing. Also cost field: some tiles are "rough" (cost 1.4) — adds interesting path selection. Let's add "rough" tiles? Maybe skip and keep binary for clarity, but a couple of hazard/boost tiles adds a showcase: "resonance" tiles where agents move slower (cost 1.35). I'll add "grit" tiles that slow prey slightly? Hmm, careful: if agents use cost field but movement doesn't reflect cost, it's a lie. I could make rough tiles actually slow everyone: implement per-entity speed multiplier from tile cost. Easy and honest: speedMul = 1/cost on rough tiles. And show cost in overlay. Good, adds depth + shows uniform-cost-aware A*. Also need: partial path when goal unreachable — A* returns best-effort with found=false and path to node nearest goal (min h). Great touch, and I'll surface it in the inspector as "PATH: partial (blocked)". Steering/movement: entity moves along path waypoints with arrival; keep entity within bounds via collision resolve (axis-separated AABB/circle vs tile grid resolution). Simple: move x, resolve x overlap, move y, resolve y overlap using tile checks. Separation: for each pair within r sum, push apart lightly. O(n²) with n≤20 agents → fine. Knowledge: js function senseTo(a, target){ // returns visible bool via Bresenham LOS on grid, dist, fov angle } Agent.knowledge = {x,y,age,conf}. On update: if visible → knowledge={target pos, age:0, conf:1}; else age += dt, conf = exp decay. Agents coordinate: if hasCall, when an agent sees player it broadcasts to pack (allies get knowledge with conf 0.85, age 0) with cooldown. Roles (hunters): role assignment each 0.4s via auction: roles: 'chase', 'flank', 'seal'(guard nearest burrow/prey exit), 'reserve'(wander/patrol → patrol waypoints). For prey groups: each pack (list) auctions roles. Hmm: prey are the ones fleeing; hunters are hunters chasing the player. Roles should be applied to whichever side is currently hunting. Let me unify: every agent has intent. I'll implement hunter role auction: chase: A* to knowledge point. intercept: target = predicted position (knowledge pos + knowledgeVel * t) — they set goal at that tile and A* there; recompute when predicted tile changes. flank: goal = knowledge point rotated ±60° at distance, but must route around walls (that's the point) → the "flanker" chooses the goal tile with the longest A* path relative to straight line (pick among 3-4 candidate tiles the one maximizing path length/straight ratio? that's the reverse—flank means take the long route). I'll choose: candidate tiles = knowledge + perpendicular*±2..4; pick the one that has LOS-to-knowledge... simpler: pick the perpendicular offset tile with higher "cover" (less LOS to your current position). Good. seal: goal = the burrow the prey... hmm the hunters hunt YOU. Sealer guards your likely exit? For hunters hunting you, "seal" doesn't apply since you're free to move. So roles for hunters: chase, flank, intercept, pounce(ambush for stalkers), escort (stay near howler). For prey fleeing you, roles apply to the pack: 'scatter' (flee to spread), 'burrow' (route to nearest exit), 'bait/feint'? Prey don't coordinate much. But feral prey packs could flank you too. I'll allow feral prey to use flank/intercept roles → literally "the hunted becomes the hunter", using the same role system. So single unified AI: each agent has mode from {HUNT, FLEE} (determined by faction state & morale) and a role from the auction. Nice and tidy. Inspector panel (right side): shows inspected agent: type, mode, role, HP, speed, state label, knowledge: conf% & age (of YOU), path cells + length, nodes expanded, last search time; utility bars (top 3 scored intents, e.g., Burrow 0.82 / Scatter 0.31). legend + controls list + toggles: [G] overlay, [T] trails, [Tab] cycle inspected, [Click] inspect. Right, also the "path" rendering: faint polyline of the agent's path + dots at waypoints; for the inspected agent, brighter, plus visited cell viz. Let me plan HUD DOM: Left of canvas top strip (inside canvas top-left, use canvas text? crisp with DPR, fine) — but I already have the DOM panel. I'll do: Top-left in canvas: wave + timer + score/combo overlay minimal (bars for HP/stamina/charge drawn in canvas bottom-left, compact). DOM panel on right: "CORTEX" — inspector + legend + controls + toggle buttons. DOM top bar in panel header: title, tempo selector, mute. Overlays (canvas): start screen (title, brief, controls, PRESS ENTER), wave banner ("WAVE 3 — THE PACK COORDINATES" style flavor lines themed), upgrade choice overlay (3 cards, clickable + keys 1/2/3), game over (stats + restart). Let me write the code in a single file. Let's go. Detailed numbers: TS=20, COLS=44, ROWS=28 → 880x560. Player: speed 168 px/s (8.4 tiles/s) — quite fast; enemies: skitterer 128, stalker 118, howler 96, feral bonus +18. Player faster than individual prey but they scatter so you need cornering; waves add numbers. Strike: cd 0.28, cone half-angle 0.62 rad (~35°... let's do 45°=0.785 for reliability), range 74px, dmg 1, knock 180. Player HP 5 (hearts) instead of 100? Use HP 100, contact damage 8 with 0.6s internal cooldown per enemy → damage readable via HP bar. Bites should feel quick. Dash: speed 620, dur 0.13s, cd 1.1s, dmg 1 to passed enemies, i-frames? give invuln during dash 0.18s. Pulse: cost 45 charge, radius 190, reveals 5s, marked prey slowed 18% and take +1 dmg? "+1 dmg" strong. Use "+0.5 dmg". Charge: max 100, gain per kill 18, gain per damage 1? Just per kill +14 and per tag +3. Enemies count scaling & tempo multiplier (Cadence 0.85x speed / Relentless 1.15x). Let's define tempo: 'steady' & 'swift' affecting enemy speed & count. Score: dmg +10, kill +80, escape -60 (and lose 1 life), wave clear +300*combo? Keep combo for kills only. Lives = 5 escapes → game over? Also HP → game over. Two fail states is fine: HP=0 → death; each escaped prey costs 1 "quench"? Let's simplify: escaped prey = -6% score & you lose nothing else; wave clears when no prey remain (escaped or dead). Game over only on HP 0. Fewer confusing rules. But then "escape" feels like failure without stakes — score penalty + combo break is enough stake. Yes: escaping prey breaks your combo and gives 0 score. Wave clear condition: all prey are gone (killed or escaped) AND all hunters dead. Hunters (stalker/howler) don't flee. Actually when only hunters remain, wave is "kill them all". Fine: wave ends when there are no prey (killed/escaped) and all hostile agents dead. So you must always kill hunters. Good. Actually wait: if prey escape, do they still need to be dead? No. Wave ends when prey.length==0 && hunters.length==0. Prey leave via burrow → removed with "ESCAPED" tag. Nice tension: you want to catch them before they reach burrows; so kill them fast. Let's add: prey at a burrow take 1.2s to "burrow" (channeling, shown by ring) — gives you a window to interrupt. Nice counterplay: they must channel, so interrupting resets. Adds fun and shows goal-choice: prey will switch to FLEE when interrupted. Upgrade cards (pick 1 of 3 each wave): "Overclock Prod": strike +1 damage... maybe too strong; use "+0.6 dmg". "Wide Arc": cone half-angle +18°. "Long Reach": range +14px. "Servos": dash cd -0.2s. "Deft": move speed +7%. "Cold Blood": prey lose your trail 30% faster (their knowledge decay +30%). "Resonance": kills release +8 charge. "Static Bloom": pulse radius +40 and reveals enemy paths. "Fang Jammer": feral rally chance reduced 20%. "Battery": max charge +25, start charged. Randomly show 3 of these; allow duplicates stacking with stack counts. OK, and I should ensure the "hunter and hunted" theme text and clarity. Now: does the player also use knowledge? Player just sees enemies dimly. Fine. Let me write the code. js // ============ util const clamp=(v,a,b)=>v<a?a:v>b?b:v; const dist=... function rngFrom(seed){ mulberry32 } Map gen: seeded. Approach: fill with floor; scatter wall blocks: random rect pillars of size 1x1..3x2 (~26 clusters), plus 2-4 long crevices (walls 1 wide, length 5-10) with gaps. Then carve nothing (since floor default). Then ensure connectivity from center: BFS flood; convert unreachable floor clusters to wall? That could delete pockets that hold prey; better: keep them but mark, and spawn enemies only in main component; but then unreachable pockets look messy. Alternative: make walls sparse enough that connectivity is nearly guaranteed and then verify + carve a connecting tunnel between components (BFS through walls cost). Easiest robust approach: generate, flood-fill from center, for each unreachable floor cluster, carve a straight L-shaped tunnel from it to the main component. Actually simplest: convert unreachable floor to wall (they're small pockets) — visually fine (looks like solid rock). I'll do that, then place burrows on floor tiles near edges in the main component. Add "grit" tiles: pick 2-3 blobs of rough cost 1.35 (visualized as dotted/darker tiles). Agents use cost; entities' speed multiplied by 1/cost of their tile → 74% speed on rough. Also shows in overlay as cost field. Burrows: 3 exits, drawn as hollow rounded squares with a dashed ring and a "vent" look. Label "EXIT". Rendering: background: fill dark #0e1116; tiles: floor subtle checker rgba(255,255,255,0.02); walls as rounded solid blocks with top highlight, color #232a33; grit: dotted pattern; burrows: amber dashed rings. Path preview: polylines; for inspected: brighter + visited cells with alpha by (1 - ageSinceExpansion), color per-agent hue, plus 'start' and 'goal' markers (goal = crosshair). Agents: circle body + facing wedge, small state icon: I'll draw state via color ring segments (e.g., ring = mode color: cyan=prey fleeing, orange=feral/hunt) plus letter label? Letters might be the clearest "placeholder graphics" and good for clarity: single letter in center (S skitterer, K stalker, H howler). Yes! Placeholder graphics = shapes + letters. That reads great. Player = chevron (triangle) white with a facing arc. HP pips as tiny arc segments. Knowledge viz: for inspected agent, draw its knowledge diamond + age text at its knowledge pos; for all agents when overlay on, small fading diamond. Trail: player ghost trail; dash trail; kill pops; damage numbers as floating text (small, monospace). Let me plan HUD DOM: Left of canvas top strip (inside canvas top-left, use canvas text? crisp with DPR, fine) — but I already have the DOM panel. I'll do: Top-left in canvas: wave + timer + score/combo overlay minimal (bars for HP/stamina/charge drawn in canvas bottom-left, compact). DOM panel on right: "CORTEX" — inspector + utilities + toggles. Overlays (canvas): start screen (title, brief, controls, PRESS ENTER), wave banner, upgrade choice overlay (3 cards, clickable + keys 1/2/3), game over (stats + restart). Let me write the code in a single file now. Let's go. Detailed numbers (final): TS=20, COLS=44, ROWS=28 → 880x560. Player: speed 168 px/s (8.4 tiles/s). Enemies: skitterer 124, stalker 116, howler 92. Strike: cd 0.30, cone 0.80 rad half-angle, range 72, dmg 1, knock 180. Hmm let me re-derive the pacing numbers now, because I want the catch loop to feel good. Prey faster than player would be unfun, but too slow and hunting is trivial. Since the catch mechanic works via escape-route cutoff (they run to burrow and you block), player speed slightly ABOVE prey base + dash 3.3x makes interception possible. And feral (rally) prey at 144 vs player 170 is fine. So: player 170, skitterer 138 (+wave), stalker 118, howler 96, feral bonus +6. With swift tempo ×1.12. Also mark slow -20%. Dash 520 for 0.13s covers ~68px. Recompute: player speed 170 (upgrades +8% each up to 2). skitterer speed 138 + wave*1.5 (cap 158), feral 144+... let me just do base 136, feral +8. tempo steady ×0.94, swift ×1.12 → steady 128, swift 152 (swift prey slightly faster than player base 170? no, 152 < 170 fine). strike dmg 1.0; skitter hp 2, stalker hp 5, howler hp 6. bite: skitter 7, stalker 9, howler 0, feral skitter 10. Player hp 100 → 14 bites. Fine. score: dmg 12, kill 90, escape penalty -50 & combo reset. Sticky target: PREY: target = knowledge pos. HUNT: target = predict(knowledge.pos, knowledge.vel, dist/speed*0.75). I'll also track stats: totalExpansions (sum of expanded for agent searches), peak frontier (max of openSize). Also keep a lastSearch object for viz including openSize peak & found flag. Now let me write code. Also note: prey escape → removed. Show floating "ESCAPED" text. Score display: score number with commas, combo xN. Upgrade pool: js const UPGRADES=[ {id:'arc', name:'Overclock', desc:'+0.5 prod damage', apply:p=>p.dmg+=0.5}, {name:'Wide Arc', desc:'prod cone +16°', apply:p=>p.arc+=0.28}, {name:'Long Reach', desc:'prod range +12px', ...}, {name:'Servos', desc:'dash cooldown -0.18s'}, {name:'Deft Frame', desc:'move speed +7%'}, {name:'Cold Blood', desc:'hosts lose your trail 30% faster'}, {name:'Resonant Kills', desc:'+8 charge per kill'}, {name:'Static Bloom', desc:'scan radius +34, reveals paths'}, {name:'Fang Jammer', desc:'rally chance -22%'}, {name:'Deep Cell', desc:'max charge +25, start +40'}, {name:'Tri-Volt', desc:'prod tags stack: each tag adds +0.2 dmg'}, // maybe skip, complexity ]; I'll pick 3 random distinct each wave, with stack counts shown in panel ("MODS" list). Wave composition: js function compose(w){ const skit = w<=1?5: Math.min(4+Math.floor(w*0.7), 9); ... } Let's define: skitters = Math.min(4 + Math.ceil(w*0.8), 10) stalkers = w>=2 ? Math.min(1+Math.floor((w-1)/2),4) : 0 howlers = w>=4 ? Math.min(1+Math.floor((w-3)/3),2) : 0 Spawn at tiles far from player, in the main component. Flavor lines per wave: array of themed strings; e.g. W1 "The prey scatters. Learn their routes.", W2 "Stalkers in the walls — they watch, then run you down." etc. Wave intro timer: 1.4s banner, enemies spawn immediately but "dormant" (idle, slow) for 0.8s with a "SCANNING" tag. Simpler: enemies spawn and act right away, but the banner overlays for 1.6s without pausing (quick pace). Add a brief 0.8s "spawn glow" where they can't act. Fine: enemies have spawnT 0.6s countdown, they don't move/damage until done. Game over screen: score, waves cleared, prey caught/escaped, nodes expanded (fun stat). Start screen: title "HUNTED / HOUND"? Name: “CORTEX GRID”? Theme name: "PREY/CLOCK"? Let me name it "SCENT & STATIC"? Hmm. Something themed around hunter and hunted + pathfinding: "SIXTH SENSE", "TRAILCRAFT", "COURT OF TEETH"... The twist: prey becomes hunter. Name: "TURN THE HUNT"? I'll call it "QUARRY//HOUND" or simply QUARRY. Title: QUARRY // HOUND. I'll use "QUARRY & HOUND" with subtitle "a hunt on a navigable grid — A* + utility AI". Good. Now, write out the code. Also don't forget: Tab cycles inspected among agents (excluding player). I maybe auto-select nearest. Click picks. Key handling: prevent scroll for space/arrows. Let me write everything now carefully. For the DOM panel HTML: html <div class="shell"> <aside class="panel" id="left"> <h1>QUARRY<span>//</span>HOUND</h1> <div class="tag">the hunter and the hunted, on a navigable grid</div> <div class="block" id="runStats">...</div> <div class="block mods">MODS: <span id="mods">—</span></div> <div class="block keys">...controls list...</div> </aside> <div class="stage"> <canvas id=game></canvas> <div id="hint">...</div> </div> <aside class="panel" id="right"> <h2>CORTEX INSPECTOR</h2> <div id=inspect> ... </div> <div class="legend">...</div> <div class="toggles">buttons for overlay/trails/paths</div> </aside> </div> Maybe simpler: single right panel containing everything (title, inspector, legend, keys) and canvas as main focus. Left panel with title + run stats + controls; right panel with inspector. Two panels makes it look like a nice dev-tool UI. I'll do it. For responsiveness: body { display:flex } on narrow → stack vertically with panel below. Let me now write out actual final code. I'll be systematic. For rendering entities I'll write drawAgent(a): if not seen and not revealed → draw dim (alpha .35, no hp?). I'll draw unseen as 0.4 alpha ghost, seen as full with hp arc. Draw path (if overlay.paths): polyline from a.pos to waypoints; waypoint dots; goal marker. inspected: brighter path + goal crosshair + knowledge diamond + sight cone + visited cells (global, drawn before entities). body shape: skitterer = triangle, stalker = square rotated, howler = hexagon, all with letter label. mode ring color: prey stable = teal (#2fd3c4), feral = amber (#ff9f43)? Hunters: stalker magenta #f0648c, howler violet #a78bfa. Hmm: color for mode vs type conflict. Simplify: fill = type color; ring = mode (flee = dashed teal, hunt = solid amber). Letter label white. Let me finalize type colors: skitter: teal #3fd0c9 (prey) — when feral draw ring amber. stalker: #ff8a4c (hunter). howler: #b48cff. player: #f3f6fa chevron with cyan glow. Fine. Write now. I'll also add spawnT glow ring. Also I want to show the prey's goal (burrow) with a line when inspected; already have goal marker. Let me write the final code. I'll try to keep it clean but complete. Given the size, I'll write carefully in one go, then mentally test. Also important: requestAnimationFrame loop with dt clamp 0.033. Let me write. js // ---------- search ---------- const G = new Uint8Array(COLS*ROWS); let rough = null; // Float32Array extra cost 0..1 function idx(x,y){return y*COLS+x;} function solid(x,y){ return x<0||y<0||x>=COLS||y>=ROWS ? true : G[idx(x,y)]===1; } function passable(x,y){ return x>=0&&y>=0&&x<COLS&&y<ROWS&&G[idx(x,y)]===0; } function costAt(x,y){ return passable(x,y)?1+rough[idx(x,y)]:Infinity; } A*: js function astar(sx,sy,gx,gy,rec){ if(!passable(sx,sy)) return {found:false,path:[],expanded:0,partial:false}; if(!passable(gx,gy)){ const n=nearestPassable(gx,gy); if(!n) return {found:false,path:[],expanded:0}; gx=n.x; gy=n.y; } const N=COLS*ROWS, came=new Int32Array(N).fill(-1), gs=new Float32Array(N).fill(Infinity), fs=new Float32Array(N).fill(Infinity), state=new Uint8Array(N); const st=SEARCH; st.active = !!rec; ... const h=(x,y)=>{const dx=Math.abs(x-gx),dy=Math.abs(y-gy); return (dx+dy)+ (SQ2-2)*Math.min(dx,dy);}; ... } rec boolean = record viz (when the searching agent is inspected or overlay.mode==='full'). Node index = yCOLS+x. Heap stores combined (f4096 + i)? Better: heap of indices with separate f array, compare fs[i]. Implement class PQueue{ constructor(f){this.a=[];this.f=f;} ... } using external f array. Standard. Tie-break in push order: if f equal, prefer higher g (closer to goal) → store ts counter and compare (f, -g). I'll compare f[a]!==f[b]? f[a]<f[b] : g[a]>g[b]. Good, deterministic. js function astar(sx,sy,gx,gy,rec){ const N=COLS*ROWS; const g=new Float32Array(N).fill(Infinity), f=new Float32Array(N).fill(Infinity), from=new Int32Array(N).fill(-1), st=new Uint8Array(N), ts=new Int32Array(N); const fArr = f; const heap = new PQueue(f, g); const h=(x,y)=>{const dx=Math.abs(x-gx),dy=Math.abs(y-gy);return (dx+dy)+(SQ2-2)*Math.min(dx,dy);}; const si=sy*COLS+sx, gi=gy*COLS+gx; g[si]=0; f[si]=h(sx,sy); heap.push(si); st[si]=1; if(rec){VIZ.s[si]=VIZ.stamp; VIZ.g[si]=0;} let found=false, best=si, bestH=h(sx,sy), expanded=0, peak=1; while(heap.size()){ peak=Math.max(peak,heap.size()); const i=heap.pop(); st[i]=2; if(i===gi){found=true;break;} const x=i%COLS,y=(i/COLS)|0; expanded++; const ng0=g[i]; for(const [dx,dy] of DIRS){ const nx=x+dx, ny=y+dy; if(!passable(nx,ny)) continue; if(dx&&dy && (solid(x+dx,y)||solid(x,y+dy))) continue; // no corner cutting const ni=ny*COLS+nx, t=(dx&&dy)?SQ2:1, ng=ng0 + t*(1+rough[ni]); if(ng < g[ni]-1e-6){ g[ni]=ng; f[ni]=ng+h(nx,ny); ts[ni]=expanded; if(st[ni]!==2) heap.push(ni); st[ni]= st[ni]===2?2:1; if(st[ni]===2){} // reopen disabled from[ni]=i; if(rec){VIZ.s[ni]=VIZ.stamp; VIZ.g[ni]=ng;} } } } Careful with the reopen logic: st=1 open, 2 closed. If ni closed and better path found — with consistent heuristic (octile × cost where cost≥1 → heuristic still admissible since min tile cost = 1) better path won't be found after closing, so skip. So: if(st[ni]===0){ st[ni]=1; heap.push(ni); g[ni]=ng; ... from[ni]=i; } and skip non-unvisited nodes. Cleaner: js if(st[ni]!==0) continue; g[ni]=ng; f[ni]=ng+h(nx,ny); from[ni]=i; st[ni]=1; heap.push(ni); if(rec){...} Good (no reopen needed with consistent heuristic). Track best partial: if !found, choose visited node with min (g+h at final?) → use best = i with min f among closed? Track: after loop, if !found choose node with min f among visited (track during pops). Then path build & pull. Return. VIZ: const VIZ={s:new Uint16Array(N), g:new Float32Array(N), stamp:0, t:0, found:false, expanded:0, peak:0, goal:{x:0,y:0}}; VIZ.stamp++ each recorded search (wrap at 65535 → reset array). Render visited for inspected agent when overlay on: loop all cells, if VIZ.s[i]===VIZ.stamp → alpha = clamp(1-age/1.1)*0.32, color teal, and if it's the goal cell draw crosshair. Note VIZ is global (only one recorded at a time) → only the inspected agent's search is recorded (or all agents but that mixes). I'll record only when searching agent is the inspected one, or if overlay.showAllSearch && agent is... let's record when rec = (a===inspected). Fine. If inspected not set but overlay on, nothing shows — instruct in panel. PQueue: js class PQueue{constructor(f,g){this.a=[];this.f=f;this.g=g;} size(){return this.a.length} push(i){const a=this.a; a.push(i); let c=a.length-1; while(c>0){const p=(c-1)>>1; if(this._lt(i,a[p])){a[c]=a[p];c=p;}else break;} a[c]=i;} pop(){const a=this.a,top=a[0],last=a.pop(); let c=0; const h=a.length>>1; while(c<h){let l=2*c+1,r=l+1,b=l; if(r<a.length&&this._lt(a[r],a[l]))b=r; if(!this._lt(a[b],last)){a[c]=last;break;} a[c]=a[b]; c=b;} if(a.length)a[c]=last; return top;} _lt(i,j){return this.f[i]!==this.f[j]? this.f[i]<this.f[j] : this.g[i]>this.g[j];} } Careful with empty-array pop edge (size 1 handled since a.length>>1 = 0 → loop skipped → a[0]=last which is the same popped element... if a was length 1, after pop a is empty, h=0, loop skipped, if(a.length) false → return top. ok. Also push sets a[c]=i at end but during loop a[c] may hold moved parent; final a[c]=i correct. Now stepMove: js function stepMove(a,dt,spd){ if(a.stun>0){a.stun-=dt; return;} let tp=a.tp; while(!tp.path.length){ // no path: pick a local passable neighbor tile to shuffle toward (keeps them alive) a.wanderT-=dt; if(a.wanderT<=0){a.wanderT=0.7; const t=randTileNear(a.cX(),a.cY(),3); if(t)a.setPath([t]);} if(!a.path.length) return; tp=a.tp; } let mx=0,my=0; while(spd*dt>0){ if(!tp.path.length){tp.done=true; return;} const w=tp.path[0]; const tx=w.x*TS+TS/2, ty=w.y*TS+TS/2; const dx=tx-a.x, dy=ty-a.y, d=Math.hypot(dx,dy); const step=spd*dt; if(d<=step){ a.x+=dx; a.y+=dy; tp.path.shift(); spd-=step/d*step... } Let me simplify: distance-based consumption: js let remain=spd*dt; while(remain>0 && tp.path.length){ const w=tp.path[0], tx=w.x*TS+TS/2, ty=w.y*TS+TS/2; const dx=tx-a.x,dy=ty-a.y,d=Math.hypot(dx,dy); if(d<=remain){ a.x+=dx;a.y+=dy; remain-=d; tp.path.shift(); } else { a.x+=dx/d*remain; a.y+=dy/d*remain; remain=0; } } if(!tp.path.length && !tp.done){ tp.done=true; tp.doneT=0; } Good; no collision resolve needed (paths never cut corners), but separation pushes could clip into walls → clamp with a small push-out routine that pushes along a wall-parallel axis, keeping them in floor. setPath(cells): sets a.tp {mode:current mode, path:stringPull(cells).map(c=>({x:c.x,y:c.y})), found, done:false}. Then in update: if(!tp.path.length && tp.done) tp.doneT+=dt; role recompute handled by ai(). Facing: if(hypot(dx,dy)>0.5) a.f=Math.atan2(dy,dx) — set during movement toward next waypoint (for player use input/mouse). Player movement uses raw input with tile collision (axis separate): js let vx=0,vy=0; if keys... normalize; move axis-sep collide Axis-sep collide: after x move, if overlaps wall tile → revert x. Use rect check of the player's circle bounding box. Player knockback: p.kx,p.ky velocity decayed 8/s added to movement. OK, damage numbers array pops=[]. Effects list: {type, x,y,t,life,...} for shot cones, rings, kills, dash trail. Now writing the code fully. Let me produce it. Note about path rendering for non-inspected agents: only when overlay on. I'll gate: overlay shows paths for all (faint), inspected bright. One more thing: the hint line at the bottom of the canvas showing live info: "wave 3 · prey 4 · hunters 2 · combo x3" plus "TAB: inspect · G: overlay". I'll draw a slim strip at bottom-left in canvas with key hints. Good. Write final answer now. Include a short summary after the code block? Keep the response mostly code with a brief intro and brief notes after. Keep prose short. Let me write the code out. Also: pause overlay with 'P'. Game state machine: 'title','play','choice','over','paused'(flag). I'll write start(), nextWave(), endWave(). endWave: when aliveHostiles()==0 → state='choice', clearBonus. choice: 3 cards; on pick → apply, then startWave(wave+1) after 0.4s. Game over when hp<=0 → state 'over'. Escapes: score -= 45 (min 0), combo reset, escaped++ counter. Now the number of prey to spawn per wave and difficulty. Also prey should spawn away from the player: choose spawn tiles with distance from player > 8 tiles. Write it! For the mods list display: p.mods = {} id→count. Cards: each has name, desc, tag; pick 3 random from POOL (avoid duplicates within a choice). Let me finalize upgrade pool with apply(p) and label. Let me write it all now, carefully, in one block. Also careful: prey "hunt" mode (feral) — their target is player. Their role auction uses hunter roles: chase/flank/pounce. I'll unify: role auction in assignRoles() for each faction group that is in hunt mode; prey in flee mode use single 'flee' intent (no roles needed) — but I want to show roles for prey-when-hunting, so when prey become feral they hunt and use roles: chase/flank/intercept. That happens naturally with the same code path if I group by faction. For role auction, group agents by faction; only groups in 'hunt' do the auction. Prey flee-mode agents get role 'flee'. Good. Now, code. js function assignRoles(){ const groups=new Map(); for(const a of agents){ if(a.dead)continue; const k=a.f; if(!groups.has(k))groups.set(k,[]); groups.get(k).push(a); } for(const [f,list] of groups){ const hunted = f==='player'? player : prey; // the quarry of that group Hmm: prey group hunting player → target = player. Player is the hunted but not in agents. Fine: const tgt = (f==='prey')? player : null and hunter faction group's target is also player. Both groups target the player. The player is always the hunted in some sense. OK so all hunt-mode agents target player. And flee-mode prey target their escape tile. Roles available to hunt groups: 'chase','intercept','flank','pounce'(stalker only),'anchor'(howler). Auction: Sort list by dist to target. Assign: first → chase; second (if count>2) → intercept; third → flank(side alternating); rest → flank/intercept alternating; pounce for stalkers: if stalker count>=2, one gets 'pounce'. Let's implement bidding properly-ish: text for(const a of list) a.bid = {chase: ..., intercept: ..., flank: ..., pounce: ..., anchor: ...}; // greedy: while unassigned exist: pick agent-role pair with max score where role not saturated (cap per role) That's a real auction and short. Implement: roles caps: chase cap1, intercept cap2, flank cap2, pounce cap2, anchor cap1(if howler present). Score formulas (0..1): chase = 0.55 + 0.3*(1-dist/18) + 0.1*rand intercept = 0.3 + 0.35*(dist>5) * speedFactor + ... let's define intercept = 0.35 + 0.3*(1-Math.abs(dist-8)/8) (best at mid range) flank = 0.3 + 0.28rand + 0.2(isFast?) → simpler: flank = 0.42 + 0.3*rand pounce = stalker? 0.4 + 0.4*(1 - dist/16) + 0.05 : 0 anchor = howler? 0.75 : 0.05 Also feral prey with low hp: fleeAgain? no. Greedy assignment loop: repeatedly find max (agent,role) score among unassigned agents & non-saturated roles → assign. This is a clean little "decision market" and worth calling out in the panel text. Now the "score" per intent for inspection: show top-3 scored intents for the inspected agent with bars. Now, the prey flee intent choice (also from a small score set): {burrow, flee, rally} as described. Now let's write. Also need predPos(a) knowledge prediction. Knowledge: store in world coords. a.know={x,y,age,conf} init at spawn (unknown → conf 0). Update in updateSenses. Write helper losClear(x0,y0,x1,y1) sampling per 0.5 tile checking solid. Sight ranges in px: skitter 140, stalker 230, howler 190 + pack broadcast. Player also "sees" enemies (used for dim/bright drawing) with range 260 (dim anyway). I think I'm ready. Also a.home/wander targets. Let me write out the file. I'll keep functions compact yet readable. Final list of drawing helper shapes: skitter = triangle (pointing at facing), stalker = square rotated/diamond, howler = hexagon. Player = chevron. Label letters: S, K, H, and player as "▲"? no label. Keep letters, they're the "placeholder graphics" charm and clarity. Let me write. For pops (floating text): pops.push({x,y,txt,col,t:0,life:0.9}). Kill effect: ring + "+80" and prey count. I also want to display on canvas the "combo" big number top-right when >1. Let's go. I'll write and then review for bugs mentally on critical paths: astar, stepMove, collision, state machine, overlay rendering. Edge: agents' cX() = Math.floor(x/TS). Let's write the whole file: Key risk points: astar recording & path smoothing correctness. stepMove partial-path consumption. Agent behavior when path empty. state transitions. Write it. For overlay cost field, drawing 1232 rects with alpha each frame is fine but let's only draw rough cells (rough>0.05) and grid lines with a cached offscreen background canvas (static map) — cache map rendering into an offscreen canvas, redraw only on new wave. Overlay grid drawn from cache? Overlay needs grid lines always? Only when overlay on; then draw grid lines to cache too? Grid lines faint always (nice look). So cache = floor + checker + walls + rough + grid + burrow base. Then dynamic overlay on top. Burrow pulse: draw dynamic ring each frame on top. Good: mapCanvas offscreen 880x560, redrawn at newWave. Performance: fine. Code for drawing entity: js function drawAgent(a){ ... } Let me write the answer now. For the inspector utility bars: I'll store a.scores = [['burrow',0.82],['flee',0.2],...] sorted; DOM update builds HTML each frame (only when inspected set / every 100ms to avoid churn — DOM updates each frame with textContent are fine-ish; I'll update every frame but only strings; cheap enough at 60fps? ~15 DOM writes/frame ok. I'll throttle to ~8fps updates.) Also show "SIGHT" (whether they see you = knowledge.conf>0.5) and knowledge age text. Alright — writing. One more mechanic check: how do you actually catch fast fleeing prey? Tools: (a) corner them into walls; (b) dash to cut off; (c) they must channel at burrow so you can arrive; (d) your cone is 72px with knockback; (e) feral prey come to you. Plus grit tiles slow them. Plus the pack's separation pushes. Should be OK. Let's also give strike a small "tag" effect: tagged prey (marked by strike) get slowed 12% for 1.2s → stacking? Keep: a.tagT slow 15% for 1.5s from hits, so hitting then following lands. Alright: implement slow as speedMul from tagT & rough tile & mark from your pulse. Total mul = (1/costTile) * (tagT>0?0.85:1) * (marked?0.9:1). Let me write the code. I'll write it now in full, in one shot.