herd / commit
Initial commit
.vscode/settings.json | 5 + README.md | 97 +++++++++ plan.md | 550 ++++++++++++++++++++++++++++++++++++++++++++++++++ plant.png | Bin 0 -> 28041 bytes 4 files changed, 652 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1a305dd --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "boids" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a010f5c --- /dev/null +++ b/README.md @@ -0,0 +1,97 @@ +# Herd π + +A browser game about herding sheep. Tap the field to move your border collie and guide the flock into the pen before time runs out. + +**[Play it β](https://pavel.gitlab.io/herd)** *(live once deployed)* + +--- + +## How to Play + +- **Tap / click** anywhere on the field to send the dog to that spot. +- The dog scares nearby sheep β use this to push the flock toward the pen. +- Get enough sheep inside the pen to win the level. +- If the timer runs out before the flock is penned, you lose. + +Sheep flock together naturally, so a little pressure in the right direction goes a long way. Chasing them head-on scatters the herd β work from the sides and behind. + +--- + +## Tech Stack + +| Tool | Role | +| --- | --- | +| TypeScript | Language | +| Vite | Dev server & bundler | +| HTML5 Canvas | Rendering | +| npm | Package management | +| GitLab Pages | Hosting | + +No game engine β physics, flocking AI, and rendering are all hand-rolled. + +--- + +## Development + +### Prerequisites + +- Node.js 20+ +- npm 10+ + +### Setup + +```bash +git clone https://gitlab.com/pavel/herd.git +cd herd +npm install +``` + +### Run locally + +```bash +npm run dev +``` + +Opens at `http://localhost:5173`. + +### Build + +```bash +npm run build # outputs to dist/ +npm run preview # serve the production build locally +``` + +--- + +## Deployment + +Pushes to `main` automatically deploy to GitLab Pages via `.gitlab-ci.yml`. The live URL is `https://<namespace>.gitlab.io/herd`. + +--- + +## Project Structure + +```text +src/ +βββ main.ts # entry point +βββ math/Vec2.ts # immutable 2D vector math +βββ game/ # loop, canvas setup, game state machine +βββ entities/ # Sheep, Dog, Pen +βββ ai/Boids.ts # flocking forces (separation, alignment, cohesion, flee) +βββ input/ # pointer event β world coordinate mapping +βββ levels/ # level configs (sheep count, pen size, time limit) +``` + +The sheep use a [boids](https://www.red3d.com/cwr/boids/) flocking algorithm extended with a flee force triggered by the dog's proximity. + +--- + +## Credits + +Grass / field icon (favicon + logo) by [Icongeek26](https://www.flaticon.com/authors/icongeek26) from the [Flaticon outline collection](https://www.flaticon.com/authors/icongeek26/outline?author_id=296&type=standard). + +--- + +## License + +MIT diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..9d5428e --- /dev/null +++ b/plan.md @@ -0,0 +1,550 @@ +# Herd β Implementation Plan + +## Overview + +A 2D top-down browser game. The player taps/clicks on the canvas to direct a border collie, which exerts pressure on a flock of sheep. The objective is to guide the entire flock into a marked pen area before time runs out. + +Rendering: HTML5 Canvas (no heavy game engine). +Physics/AI: hand-rolled boids flocking + dog steering. +Stack: TypeScript Β· Vite Β· npm Β· GitLab Pages. + +--- + +## Phase 1 β Project Scaffold + +- `npm create vite@latest herd -- --template vanilla-ts` +- Add `.gitlab-ci.yml` for GitLab Pages deployment (build β `dist/`, artifact on `public/` path). +- Configure `vite.config.ts` with `base: '/herd/'` (adjust to match the repo name / Pages path). +- Set up `tsconfig.json` with `strict: true`. +- Add `eslint` + `prettier` configs (optional but recommended for the long run). +- Copy `plant.png` into the project root and reference it as the favicon in `index.html`: + `<link rel="icon" type="image/png" href="/plant.png" />` +- Verify `npm run dev` and `npm run build` both work before writing any game code. + +Deliverable: blank Vite page with favicon, deployed to GitLab Pages. + +--- + +## Phase 2 β Game Loop & Canvas + +Files: `src/main.ts`, `src/game/GameLoop.ts`, `src/game/Canvas.ts` + +- Create a `<canvas>` element that fills the viewport, scaling with `devicePixelRatio` for crisp rendering on retina/HiDPI screens. +- Handle `resize` events β recalculate canvas size and reposition entities proportionally. +- Implement a fixed-timestep game loop using `requestAnimationFrame`: + - Accumulate `dt` (delta time in ms). + - Run `update(dt)` in fixed 16 ms steps (β60 Hz) to keep physics deterministic. + - Run `render(interpolationAlpha)` every frame. +- Expose `GameLoop.start()` / `GameLoop.stop()`. + +Deliverable: blank canvas with a running loop that logs FPS to console. + +--- + +## Phase 3 β Entity System + +Files: `src/entities/Entity.ts`, `src/entities/Sheep.ts`, `src/entities/Dog.ts`, `src/entities/Pen.ts` + +### Entity base +Each entity has: +- `position: Vec2` +- `velocity: Vec2` +- `radius: number` (for collision/influence zones) +- `update(dt: number): void` +- `draw(ctx: CanvasRenderingContext2D): void` + +### Vec2 math helper +File: `src/math/Vec2.ts` +Implement: `add`, `sub`, `scale`, `normalize`, `length`, `distance`, `limit`, `lerp`. +Keep it immutable β return new `Vec2` instances. + +### Sheep +- Circular sprite (or a simple polygon) with a facing direction. +- Carries boids state: a list of currently visible neighbors (updated each tick). +- Has a `fearLevel` scalar that rises when the dog is close, decays over time. + +### Dog +- Distinct visual so it's easy to tell apart from sheep. +- Has a `target: Vec2 | null` β the position the player last tapped. +- Moves toward target at a fixed speed; stops when within ~8 px. + +### Pen +- An axis-aligned rectangle defined by `x, y, width, height`. +- `contains(pos: Vec2): boolean` β used for win-condition checking. +- Drawn as a fenced rectangle with an open side (the "gate"). + +Deliverable: entities visible on canvas, nothing moving yet. + +--- + +## Phase 4 β Sheep Flocking (Boids) + +File: `src/ai/Boids.ts` + +Classic boids with sheep-specific extensions. Each frame, for each sheep, compute a weighted sum of steering forces: + +| Force | Weight | Description | +| --- | --- | --- | +| Separation | 1.8 | Steer away from neighbors within `separationRadius` (~30 px) | +| Alignment | 1.0 | Match average velocity of neighbors within `alignRadius` (~80 px) | +| Cohesion | 1.0 | Steer toward centroid of neighbors within `cohesionRadius` (~100 px) | +| Dog flee | 3.5 | Strong repulsion when dog is within `fleeRadius` (~150 px); magnitude scales with `1/distanceΒ²` | +| Boundary | 2.0 | Smooth repulsion from canvas edges (starts ~60 px from edge) | +| Pen attraction | 0.3 | Weak pull toward pen center (simulates sheep "settling" once near the pen) | + +Implementation notes: + +- Neighbor lookup is O(nΒ²) β fine for β€40 sheep. +- Cap final velocity with `Vec2.limit(maxSpeed)`. +- `maxSpeed` for sheep: ~90 px/s. `maxForce` (acceleration cap per frame): ~6 px/sΒ². +- When `fearLevel` is high, multiply dog-flee weight and max speed by a multiplier (panicked sheep move faster). + +Deliverable: sheep flock together and scatter when the dog approaches. + +--- + +## Phase 5 β Dog Behaviour & Player Input + +File: `src/input/InputHandler.ts`, update `Dog` + +### Input +- Listen for `pointerdown` on the canvas (covers mouse, touch, stylus). +- Convert from screen coordinates to canvas/world coordinates (account for `devicePixelRatio` and canvas CSS size). +- Set `dog.target` to the tapped world position. + +### Dog movement +- Each tick: compute vector from `dog.position` to `dog.target`. +- Steer toward target with a simple seek behaviour (`Vec2.normalize(diff).scale(speed)`). +- Dog speed: ~160 px/s β faster than sheep so it can actually herd. +- Stop when distance to target < arrival threshold (~10 px); set `target = null`. + +### Dog influence on sheep +- Each sheep tick, compute distance to dog. +- If within `fleeRadius`: increase sheep's `fearLevel`; apply flee force. +- `fearLevel` decays exponentially when dog is far: `fearLevel *= 0.95` per tick. + +Deliverable: tapping moves the dog; sheep scatter away from it. + +--- + +## Phase 6 β Game State & Win Condition + +File: `src/game/GameState.ts` + +States: `IDLE` β `PLAYING` β `WIN` | `FAIL` + +### Win condition +- Check every N ticks (e.g., every 30 frames). +- Count sheep whose `position` is `pen.contains(sheep.position)`. +- Win when β₯ threshold (e.g., all sheep, or configurable percentage like 80%). +- Brief celebration (all sheep in pen, show "You Win!" overlay). + +### Fail condition (optional, adds pressure) +- A countdown timer. If it hits zero before the win condition, show "Time's Up!" overlay. + +### UI overlays +- Render directly on canvas (no DOM elements during gameplay for performance). +- `IDLE`: "Tap to start" message. +- `PLAYING`: sheep-in-pen counter (e.g., "3 / 8 π") + timer bar. +- `WIN` / `FAIL`: message + "Play Again" prompt. + +Deliverable: complete game loop from start to end state, restartable. + +--- + +## Phase 7 β Visual Polish + +Keep it readable and charming without requiring art assets (use canvas primitives). + +### Sheep +- White filled circle, darker ellipse for the "wool" texture, two small dots for eyes, tiny legs. +- Or: import simple SVG sprite at a later pass. + +### Dog + +- Brown/black filled circle with pointed ears, different color from sheep. + +### Pen +- Dashed or solid rectangle, green fill at low opacity, fence posts along three sides, open gate on one side with a visual indicator. + +### Logo / favicon + +- `plant.png` (grass icon) is already in the project root and wired up in `index.html`. +- Render it in the top-left corner of the canvas as a small watermark during gameplay (e.g. 32Γ32 px at low opacity). +- Can also appear on the level-select / idle screens at full size as the game's title graphic. + +### Environment +- Green fill for the field. +- Optional: a few static obstacle circles (rocks / trees) that sheep path around β add boundary repulsion force for each. + +### Feedback +- Footprints that fade out behind the dog. +- Sheep briefly turn red/orange when at high `fearLevel`. +- Pen fill color shifts from transparent β light green as more sheep enter. + +--- + +## Phase 8 β Levels & Configuration + +File: `src/levels/LevelConfig.ts` + +All coordinates are fractional (0.0β1.0) relative to canvas size so levels are resolution-independent. + +### LevelConfig type + +```ts +interface SheepOverride { + index: number; + stubborn?: boolean; // higher flee radius, lower cohesion pull + leader?: boolean; // larger cohesion broadcast radius β others weight this sheep more + subFlock?: number; // group ID; sheep strongly prefer neighbors in the same group + startFear?: number; // 0β1 initial fearLevel (default 0) +} + +interface DynamicObstacle { + x: number; y: number; r: number; + appearsAtSeconds: number; // obstacle materializes partway through the level +} + +interface LevelConfig { + id: number; + title: string; + subtitle: string; // one-line real-world resonance note shown on level card + + sheepCount: number; + penRect: { x: number; y: number; w: number; h: number }; + penGateSide: 'top' | 'right' | 'bottom' | 'left'; + timeLimitSeconds: number | null; // null = no timer + winThreshold: number; // fraction of flock that must be penned, e.g. 1.0 = all + + spawnClusters: Array<{ cx: number; cy: number; r: number; count: number }>; + // where sheep start β allows scattered vs. grouped starts + + obstacles: Array<{ x: number; y: number; r: number }>; + dynamicObstacles: DynamicObstacle[]; + + sheepOverrides: SheepOverride[]; + + darkMode: boolean; // canvas is dark; only the dog's vicinity is lit + penShrinksEverySeconds: number | null; // pen width/height reduce by 10% on each interval +} +``` + +--- + +### The 10 Levels + +--- + +#### Level 1 β "First Lesson" + +*No real-world parallel β pure tutorial.* + +| Property | Value | +|---|---| +| Sheep | 6 | +| Win threshold | 80% (5 of 6) | +| Timer | 120 s | +| Pen | Large (0.25 Γ 0.25), top-right corner | +| Spawn | One cluster, center of field | +| Obstacles | None | +| Special | None | + +The player learns that tapping moves the dog, the dog scares sheep, and sheep flock together. The generous threshold and timer mean almost any approach works. A tooltip appears on first tap. + +--- + +#### Level 2 β "The Scatter" + +*Real-world parallel: rural depopulation. Communities spread too thin across a landscape to self-organize. Someone has to physically go out and bring them back together before cohesion can happen at all.* + +| Property | Value | +|---|---| +| Sheep | 12 | +| Win threshold | 100% | +| Timer | 150 s | +| Pen | Medium (0.20 Γ 0.20), bottom-center | +| Spawn | 4 clusters of 3, one in each corner of the field | +| Obstacles | None | +| Special | Clusters start too far apart to naturally flock β boids cohesion radius won't reach across the gap. Player must push sub-groups together before the flock coheres. | + +The mechanical lesson: cohesion only kicks in at range. A scattered flock can't self-organize; it needs active bridging. + +--- + +#### Level 3 β "The Holdouts" + +*Real-world parallel: herd immunity. You need a population-wide threshold, but a small minority requires dramatically more effort than the rest. The burden of 100% is not evenly distributed.* + +| Property | Value | +|---|---| +| Sheep | 15 | +| Win threshold | 100% | +| Timer | 180 s | +| Pen | Medium, left side | +| Spawn | One main cluster (12) + 3 holdouts spread across far corners | +| Obstacles | None | +| Special | 3 `stubborn` sheep: flee radius 2Γ normal, cohesion pull 0.3Γ normal. Marked with a small visual indicator. | + +The main flock pens easily. Then the player discovers that the three holdouts need individual, sustained pressure β they can't be herded efficiently as a group and will break away from the main flock if spooked. + +--- + +#### Level 4 β "Two Tribes" + +*Real-world parallel: political polarization. Two groups have strong internal cohesion but actively repel each other. Getting both into the same space requires patient, impartial pressure β heavy-handed intervention on one tribe panics the other.* + +| Property | Value | +|---|---| +| Sheep | 20 (10 per tribe, visually distinct β e.g. white vs. grey) | +| Win threshold | 100% | +| Timer | 180 s | +| Pen | Large, center of field | +| Spawn | Two clusters on opposite sides of the field | +| Obstacles | None | +| Special | `subFlock` IDs 0 and 1. Within a flock: normal flocking. Between flocks: separation force is 3Γ higher than within-flock separation. They will actively avoid mixing. | + +The player quickly learns that chasing one group into the pen scatters the other. Gentle, alternating pressure from behind both groups is the only solution. The pen is large enough to hold both β once they're in, proximity to the pen edges overrides the inter-flock repulsion (sheep stop caring about tribe when they're already safe). + +--- + +#### Level 5 β "Shrinking Pasture" + +*Real-world parallel: habitat loss / urban sprawl. The environment erodes in real time. What was open ground becomes obstacles. The sheep adapt by clustering, which paradoxically makes them easier to herd β but the route to the pen gets harder.* + +| Property | Value | +|---|---| +| Sheep | 15 | +| Win threshold | 85% | +| Timer | 210 s | +| Pen | Medium, top-left corner | +| Spawn | Spread across the center | +| Obstacles | 3 static (rocks along the right edge) | +| Special | 6 `dynamicObstacles` appear at t = 30 s, 60 s, 90 s, 120 s, 150 s, 180 s β each one materializes with a brief visual pulse, starting from the right and bottom edges and progressing inward. Total effect: the usable field shrinks to about 55% of its original area by the end. | + +The player doesn't need to hurry at first β but procrastination means the path to the pen gets blocked. The level is completable early with a moderate strategy or late with a very tight squeeze. + +--- + +#### Level 6 β "Already Spooked" + +*Real-world parallel: displaced or traumatized populations. Every intervention triggers a panic response. Heavy-handed herding makes things worse. The player must slow down, reduce pressure, and use patience rather than force.* + +| Property | Value | +|---|---| +| Sheep | 18 | +| Win threshold | 80% | +| Timer | 240 s | +| Pen | Large, bottom-right | +| Spawn | Scattered (6 small clusters of 3) | +| Obstacles | None | +| Special | All sheep start with `startFear = 1.0`. Fear decay rate is 0.5Γ normal (they stay spooked longer). Dog flee radius is 1.5Γ normal. The dog's speed is reduced to 110 px/s for this level only. | + +The player who taps aggressively will send sheep careening into walls and each other. The level is easier with a slow dog, moved in short steps, looping wide around the flock. The generous timer and threshold explicitly reward restraint over speed. + +--- + +#### Level 7 β "The Influencers" + +*Real-world parallel: social contagion and opinion dynamics. In any group, a small number of nodes determine collective movement. Identify and redirect the key nodes and the crowd follows. Ignore them and you're pushing against invisible resistance.* + +| Property | Value | +|---|---| +| Sheep | 22 | +| Win threshold | 90% | +| Timer | 180 s | +| Pen | Medium, right side | +| Spawn | One large cluster in the left half | +| Obstacles | 2 rocks in the mid-field | +| Special | 4 `leader` sheep (marked visually). Other sheep weight leader neighbors 3Γ more heavily in alignment and cohesion calculations. If all 4 leaders are penned, the remaining sheep's pen-attraction force doubles β they drift in. If no leaders are penned, non-leaders resist the pen even under direct pressure. | + +The player who tries to herd the whole flock at once will be confused why it doesn't work. The player who figures out to target the leaders first will watch the rest follow almost by themselves. + +--- + +#### Level 8 β "Race to Market" + +*Real-world parallel: industrial livestock production. Volume, speed, and efficiency above all else. The level is deliberately exhausting β a comment on what it feels like to operate at industrial scale.* + +| Property | Value | +|---|---| +| Sheep | 30 | +| Win threshold | 80% (24 of 30) | +| Timer | 75 s | +| Pen | Medium (fits ~20 comfortably, so sheep jostle) | +| Spawn | Two large clusters | +| Obstacles | None (clear field β no excuses) | +| Special | Dog speed increased to 200 px/s for this level. Sheep max speed also increased by 20% β everything moves faster. Timer is severe. | + +The player has to be decisive and aggressive. The 80% threshold gives a little slack but not much. It's the one level where patience is actively punished. After the calm required by Level 6, the gear shift is jarring β intentionally. + +--- + +#### Level 9 β "Lights Out" + +*Real-world parallel: information asymmetry β making decisions when you can only see a fraction of the picture. The flock is out there. You just can't see where. You respond to what's in front of you and hope.* + +| Property | Value | +|---|---| +| Sheep | 16 | +| Win threshold | 100% | +| Timer | 270 s | +| Pen | Medium, glows faintly (always visible) | +| Spawn | Scattered evenly across the whole field | +| Obstacles | 2 rocks | +| Special | `darkMode: true`. Canvas is rendered dark with a radial gradient "torch" around the dog (radius ~120 px). Sheep outside the lit area are invisible. Sound cue (soft rustling) plays when unlit sheep are within ~200 px of the dog. | + +To win you must sweep the entire field systematically. The 100% threshold means you can't leave anyone behind, and you can't see who's left. After clearing most sheep, the final one or two are found by listening. + +> β οΈ The rustling audio cue is gated on Phase 9 being implemented. Without audio, the level is still playable but loses its most important feedback signal β implement Phase 9 before shipping Level 9. + +--- + +#### Level 10 β "The Last Pasture" + +*Real-world parallel: climate crisis. The deadline isn't a countdown β it's a slow erosion. You have time until suddenly you don't. The goal seems achievable right up until the moment it isn't.* + +| Property | Value | +|---|---| +| Sheep | 25 | +| Win threshold | 100% | +| Timer | None (no timer displayed β this is important) | +| Pen | Starts large; `penShrinksEverySeconds: 25`. Every 25 s the pen shrinks by 10% in both dimensions. After ~5 minutes it becomes too small to physically contain 25 sheep and the level becomes impossible. | +| Spawn | Two clusters | +| Obstacles | One large central obstacle (a dried-out lake / flood zone) bisecting the direct route to the pen | +| Special | 3 `stubborn` sheep (from Level 3 mechanic), 2 `leader` sheep (from Level 7 mechanic). Combines the hard-to-herd minority with the influence dynamic. | + +The player is told only that all sheep must reach the pen. There is no visible clock. The pen visually shows its current size, and a subtle ambient sound grows more urgent as the pen contracts. Players who dawdle discover the deadline too late. The level is designed to be replayed β on a second run, knowing the pen is shrinking changes everything about how you approach it. + +--- + +### Difficulty Curve Summary + +| # | Title | Sheep | Threshold | Special mechanic | +| --- | --- | --- | --- | --- | +| 1 | First Lesson | 6 | 80% | None | +| 2 | The Scatter | 12 | 100% | Dispersed spawn, no natural cohesion | +| 3 | The Holdouts | 15 | 100% | Stubborn minority | +| 4 | Two Tribes | 20 | 100% | Inter-flock repulsion | +| 5 | Shrinking Pasture | 15 | 85% | Dynamic obstacles | +| 6 | Already Spooked | 18 | 80% | Max start fear, slower dog | +| 7 | The Influencers | 22 | 90% | Leader sheep | +| 8 | Race to Market | 30 | 80% | Fast everything, brutal timer | +| 9 | Lights Out | 16 | 100% | Dark canvas, audio cues | +| 10 | The Last Pasture | 25 | 100% | Shrinking pen, combined mechanics, no timer | + +### Extended LevelConfig notes + +- All coordinates are 0.0β1.0 fractions of canvas dimensions. +- `spawnClusters` defines where sheep appear; sheep within a cluster are placed with small random offsets from the cluster center. +- `sheepOverrides` are applied by index after spawn assignment. +- `darkMode` activates a canvas compositing layer; the "torch" radius and falloff are tunable constants. +- `penShrinksEverySeconds` contracts both `w` and `h` by a fixed fraction each interval; minimum pen size is capped to prevent a zero-area pen (which would make 100% threshold impossible and trigger a specific "too late" lose state). +- Level metadata (`title`, `subtitle`) is shown on a pre-level card screen before the game starts. + +--- + +## Phase 9 β Audio (Optional / Later) + +- Use the Web Audio API directly (no library). +- Sounds: soft baa on scare, dog bark on tap, success chime. +- Keep audio files small (< 50 KB total). +- Add a mute toggle button in the corner. + +--- + +## Phase 10 β GitLab CI/CD + +File: `.gitlab-ci.yml` + +```yaml +pages: + stage: deploy + image: node:20 + script: + - npm ci + - npm run build + - mv dist public + artifacts: + paths: + - public + only: + - main +``` + +- Set `base` in `vite.config.ts` to match the GitLab Pages URL path. +- Confirm the deployed URL loads and runs the game. + +--- + +## Assets & Attribution + +| Asset | File | Source | +| --- | --- | --- | +| Grass / field icon (favicon + logo) | `plant.png` | [Icongeek26 β Outline collection](https://www.flaticon.com/authors/icongeek26/outline?author_id=296&type=standard) via [Flaticon](https://www.flaticon.com/authors/icongeek26) | + +The Flaticon license requires attribution when the free tier is used. Attribution is included in the README and (where space permits) in an in-game "Credits" screen or overlay. + +--- + +## File Structure (target) + +```text +herd/ +βββ .gitlab-ci.yml +βββ index.html +βββ package.json +βββ plant.png # favicon + logo (Icongeek26 / Flaticon, outline collection) +βββ tsconfig.json +βββ vite.config.ts +βββ src/ + βββ main.ts # entry point β mounts canvas, starts GameLoop + βββ math/ + β βββ Vec2.ts + βββ game/ + β βββ Canvas.ts # canvas sizing, dpr scaling, coord transform + β βββ GameLoop.ts # rAF loop, fixed timestep + β βββ GameState.ts # state machine, win/fail checks, UI overlays + βββ entities/ + β βββ Entity.ts + β βββ Sheep.ts + β βββ Dog.ts + β βββ Pen.ts + βββ ai/ + β βββ Boids.ts # force calculation, neighbor queries + βββ input/ + β βββ InputHandler.ts # pointer events β world coords + βββ levels/ + βββ LevelConfig.ts +``` + +--- + +## Key Constants to Tune (start here, adjust by feel) + +| Constant | Starting Value | +| --- | --- | +| `SHEEP_MAX_SPEED` | 90 px/s | +| `SHEEP_MAX_FORCE` | 6 px/sΒ² | +| `DOG_SPEED` | 160 px/s | +| `FLEE_RADIUS` | 150 px | +| `SEPARATION_RADIUS` | 30 px | +| `ALIGN_RADIUS` | 80 px | +| `COHESION_RADIUS` | 100 px | +| `FEAR_DECAY` | 0.95 per tick | + +These will need iteration. The game feel lives entirely in these numbers. + +--- + +## Development Order + +1. Scaffold (Phase 1) β get CI green with a blank page first. +2. Canvas + loop (Phase 2). +3. Vec2 + entities rendering statically (Phase 3). +4. Boids forces in isolation with no dog β just watch sheep flock (Phase 4). +5. Add dog input and flee force (Phase 5). +6. Win/fail states (Phase 6). +7. Polish passes (Phase 7+) as desired. + +Do **not** skip to polish before the core herding feel is fun. The boids tuning in Phase 4β5 is the game. diff --git a/plant.png b/plant.png new file mode 100644 index 0000000..8759401 Binary files /dev/null and b/plant.png differ