fuzzyworld

herd / files / plan.md

plan.md

22.2 KB · history · blame

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

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

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

Dog

Pen

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:

Deliverable: sheep flock together and scatter when the dog approaches.


Phase 5 — Dog Behaviour & Player Input

File: src/input/InputHandler.ts, update Dog

Input

Dog movement

Dog influence on sheep

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

Fail condition (optional, adds pressure)

UI overlays

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

Dog

Pen

Logo / favicon

Environment

Feedback


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

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


Phase 9 — Audio (Optional / Later)


Phase 10 — GitLab CI/CD

File: .gitlab-ci.yml

pages:
  stage: deploy
  image: node:20
  script:
    - npm ci
    - npm run build
    - mv dist public
  artifacts:
    paths:
      - public
  only:
    - main

Assets & Attribution

Asset File Source
Grass / field icon (favicon + logo) plant.png Icongeek26 — Outline collection via Flaticon

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)

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.

Source
1# Herd — Implementation Plan
2
3## Overview
4
5A 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.
6
7Rendering: HTML5 Canvas (no heavy game engine).
8Physics/AI: hand-rolled boids flocking + dog steering.
9Stack: TypeScript · Vite · npm · GitLab Pages.
10
11---
12
13## Phase 1 — Project Scaffold
14
15- `npm create vite@latest herd -- --template vanilla-ts`
16- Add `.gitlab-ci.yml` for GitLab Pages deployment (build → `dist/`, artifact on `public/` path).
17- Configure `vite.config.ts` with `base: '/herd/'` (adjust to match the repo name / Pages path).
18- Set up `tsconfig.json` with `strict: true`.
19- Add `eslint` + `prettier` configs (optional but recommended for the long run).
20- Copy `plant.png` into the project root and reference it as the favicon in `index.html`:
21 `<link rel="icon" type="image/png" href="/plant.png" />`
22- Verify `npm run dev` and `npm run build` both work before writing any game code.
23
24Deliverable: blank Vite page with favicon, deployed to GitLab Pages.
25
26---
27
28## Phase 2 — Game Loop & Canvas
29
30Files: `src/main.ts`, `src/game/GameLoop.ts`, `src/game/Canvas.ts`
31
32- Create a `<canvas>` element that fills the viewport, scaling with `devicePixelRatio` for crisp rendering on retina/HiDPI screens.
33- Handle `resize` events — recalculate canvas size and reposition entities proportionally.
34- Implement a fixed-timestep game loop using `requestAnimationFrame`:
35 - Accumulate `dt` (delta time in ms).
36 - Run `update(dt)` in fixed 16 ms steps (≈60 Hz) to keep physics deterministic.
37 - Run `render(interpolationAlpha)` every frame.
38- Expose `GameLoop.start()` / `GameLoop.stop()`.
39
40Deliverable: blank canvas with a running loop that logs FPS to console.
41
42---
43
44## Phase 3 — Entity System
45
46Files: `src/entities/Entity.ts`, `src/entities/Sheep.ts`, `src/entities/Dog.ts`, `src/entities/Pen.ts`
47
48### Entity base
49Each entity has:
50- `position: Vec2`
51- `velocity: Vec2`
52- `radius: number` (for collision/influence zones)
53- `update(dt: number): void`
54- `draw(ctx: CanvasRenderingContext2D): void`
55
56### Vec2 math helper
57File: `src/math/Vec2.ts`
58Implement: `add`, `sub`, `scale`, `normalize`, `length`, `distance`, `limit`, `lerp`.
59Keep it immutable — return new `Vec2` instances.
60
61### Sheep
62- Circular sprite (or a simple polygon) with a facing direction.
63- Carries boids state: a list of currently visible neighbors (updated each tick).
64- Has a `fearLevel` scalar that rises when the dog is close, decays over time.
65
66### Dog
67- Distinct visual so it's easy to tell apart from sheep.
68- Has a `target: Vec2 | null` — the position the player last tapped.
69- Moves toward target at a fixed speed; stops when within ~8 px.
70
71### Pen
72- An axis-aligned rectangle defined by `x, y, width, height`.
73- `contains(pos: Vec2): boolean` — used for win-condition checking.
74- Drawn as a fenced rectangle with an open side (the "gate").
75
76Deliverable: entities visible on canvas, nothing moving yet.
77
78---
79
80## Phase 4 — Sheep Flocking (Boids)
81
82File: `src/ai/Boids.ts`
83
84Classic boids with sheep-specific extensions. Each frame, for each sheep, compute a weighted sum of steering forces:
85
86| Force | Weight | Description |
87| --- | --- | --- |
88| Separation | 1.8 | Steer away from neighbors within `separationRadius` (~30 px) |
89| Alignment | 1.0 | Match average velocity of neighbors within `alignRadius` (~80 px) |
90| Cohesion | 1.0 | Steer toward centroid of neighbors within `cohesionRadius` (~100 px) |
91| Dog flee | 3.5 | Strong repulsion when dog is within `fleeRadius` (~150 px); magnitude scales with `1/distance²` |
92| Boundary | 2.0 | Smooth repulsion from canvas edges (starts ~60 px from edge) |
93| Pen attraction | 0.3 | Weak pull toward pen center (simulates sheep "settling" once near the pen) |
94
95Implementation notes:
96
97- Neighbor lookup is O(n²) — fine for ≤40 sheep.
98- Cap final velocity with `Vec2.limit(maxSpeed)`.
99- `maxSpeed` for sheep: ~90 px/s. `maxForce` (acceleration cap per frame): ~6 px/s².
100- When `fearLevel` is high, multiply dog-flee weight and max speed by a multiplier (panicked sheep move faster).
101
102Deliverable: sheep flock together and scatter when the dog approaches.
103
104---
105
106## Phase 5 — Dog Behaviour & Player Input
107
108File: `src/input/InputHandler.ts`, update `Dog`
109
110### Input
111- Listen for `pointerdown` on the canvas (covers mouse, touch, stylus).
112- Convert from screen coordinates to canvas/world coordinates (account for `devicePixelRatio` and canvas CSS size).
113- Set `dog.target` to the tapped world position.
114
115### Dog movement
116- Each tick: compute vector from `dog.position` to `dog.target`.
117- Steer toward target with a simple seek behaviour (`Vec2.normalize(diff).scale(speed)`).
118- Dog speed: ~160 px/s — faster than sheep so it can actually herd.
119- Stop when distance to target < arrival threshold (~10 px); set `target = null`.
120
121### Dog influence on sheep
122- Each sheep tick, compute distance to dog.
123- If within `fleeRadius`: increase sheep's `fearLevel`; apply flee force.
124- `fearLevel` decays exponentially when dog is far: `fearLevel *= 0.95` per tick.
125
126Deliverable: tapping moves the dog; sheep scatter away from it.
127
128---
129
130## Phase 6 — Game State & Win Condition
131
132File: `src/game/GameState.ts`
133
134States: `IDLE` → `PLAYING` → `WIN` | `FAIL`
135
136### Win condition
137- Check every N ticks (e.g., every 30 frames).
138- Count sheep whose `position` is `pen.contains(sheep.position)`.
139- Win when ≥ threshold (e.g., all sheep, or configurable percentage like 80%).
140- Brief celebration (all sheep in pen, show "You Win!" overlay).
141
142### Fail condition (optional, adds pressure)
143- A countdown timer. If it hits zero before the win condition, show "Time's Up!" overlay.
144
145### UI overlays
146- Render directly on canvas (no DOM elements during gameplay for performance).
147- `IDLE`: "Tap to start" message.
148- `PLAYING`: sheep-in-pen counter (e.g., "3 / 8 🐑") + timer bar.
149- `WIN` / `FAIL`: message + "Play Again" prompt.
150
151Deliverable: complete game loop from start to end state, restartable.
152
153---
154
155## Phase 7 — Visual Polish
156
157Keep it readable and charming without requiring art assets (use canvas primitives).
158
159### Sheep
160- White filled circle, darker ellipse for the "wool" texture, two small dots for eyes, tiny legs.
161- Or: import simple SVG sprite at a later pass.
162
163### Dog
164
165- Brown/black filled circle with pointed ears, different color from sheep.
166
167### Pen
168- Dashed or solid rectangle, green fill at low opacity, fence posts along three sides, open gate on one side with a visual indicator.
169
170### Logo / favicon
171
172- `plant.png` (grass icon) is already in the project root and wired up in `index.html`.
173- Render it in the top-left corner of the canvas as a small watermark during gameplay (e.g. 32×32 px at low opacity).
174- Can also appear on the level-select / idle screens at full size as the game's title graphic.
175
176### Environment
177- Green fill for the field.
178- Optional: a few static obstacle circles (rocks / trees) that sheep path around — add boundary repulsion force for each.
179
180### Feedback
181- Footprints that fade out behind the dog.
182- Sheep briefly turn red/orange when at high `fearLevel`.
183- Pen fill color shifts from transparent → light green as more sheep enter.
184
185---
186
187## Phase 8 — Levels & Configuration
188
189File: `src/levels/LevelConfig.ts`
190
191All coordinates are fractional (0.0–1.0) relative to canvas size so levels are resolution-independent.
192
193### LevelConfig type
194
195```ts
196interface SheepOverride {
197 index: number;
198 stubborn?: boolean; // higher flee radius, lower cohesion pull
199 leader?: boolean; // larger cohesion broadcast radius — others weight this sheep more
200 subFlock?: number; // group ID; sheep strongly prefer neighbors in the same group
201 startFear?: number; // 0–1 initial fearLevel (default 0)
202}
203
204interface DynamicObstacle {
205 x: number; y: number; r: number;
206 appearsAtSeconds: number; // obstacle materializes partway through the level
207}
208
209interface LevelConfig {
210 id: number;
211 title: string;
212 subtitle: string; // one-line real-world resonance note shown on level card
213
214 sheepCount: number;
215 penRect: { x: number; y: number; w: number; h: number };
216 penGateSide: 'top' | 'right' | 'bottom' | 'left';
217 timeLimitSeconds: number | null; // null = no timer
218 winThreshold: number; // fraction of flock that must be penned, e.g. 1.0 = all
219
220 spawnClusters: Array<{ cx: number; cy: number; r: number; count: number }>;
221 // where sheep start — allows scattered vs. grouped starts
222
223 obstacles: Array<{ x: number; y: number; r: number }>;
224 dynamicObstacles: DynamicObstacle[];
225
226 sheepOverrides: SheepOverride[];
227
228 darkMode: boolean; // canvas is dark; only the dog's vicinity is lit
229 penShrinksEverySeconds: number | null; // pen width/height reduce by 10% on each interval
230}
231```
232
233---
234
235### The 10 Levels
236
237---
238
239#### Level 1 — "First Lesson"
240
241*No real-world parallel — pure tutorial.*
242
243| Property | Value |
244|---|---|
245| Sheep | 6 |
246| Win threshold | 80% (5 of 6) |
247| Timer | 120 s |
248| Pen | Large (0.25 × 0.25), top-right corner |
249| Spawn | One cluster, center of field |
250| Obstacles | None |
251| Special | None |
252
253The 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.
254
255---
256
257#### Level 2 — "The Scatter"
258
259*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.*
260
261| Property | Value |
262|---|---|
263| Sheep | 12 |
264| Win threshold | 100% |
265| Timer | 150 s |
266| Pen | Medium (0.20 × 0.20), bottom-center |
267| Spawn | 4 clusters of 3, one in each corner of the field |
268| Obstacles | None |
269| 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. |
270
271The mechanical lesson: cohesion only kicks in at range. A scattered flock can't self-organize; it needs active bridging.
272
273---
274
275#### Level 3 — "The Holdouts"
276
277*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.*
278
279| Property | Value |
280|---|---|
281| Sheep | 15 |
282| Win threshold | 100% |
283| Timer | 180 s |
284| Pen | Medium, left side |
285| Spawn | One main cluster (12) + 3 holdouts spread across far corners |
286| Obstacles | None |
287| Special | 3 `stubborn` sheep: flee radius 2× normal, cohesion pull 0.3× normal. Marked with a small visual indicator. |
288
289The 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.
290
291---
292
293#### Level 4 — "Two Tribes"
294
295*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.*
296
297| Property | Value |
298|---|---|
299| Sheep | 20 (10 per tribe, visually distinct — e.g. white vs. grey) |
300| Win threshold | 100% |
301| Timer | 180 s |
302| Pen | Large, center of field |
303| Spawn | Two clusters on opposite sides of the field |
304| Obstacles | None |
305| 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. |
306
307The 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).
308
309---
310
311#### Level 5 — "Shrinking Pasture"
312
313*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.*
314
315| Property | Value |
316|---|---|
317| Sheep | 15 |
318| Win threshold | 85% |
319| Timer | 210 s |
320| Pen | Medium, top-left corner |
321| Spawn | Spread across the center |
322| Obstacles | 3 static (rocks along the right edge) |
323| 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. |
324
325The 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.
326
327---
328
329#### Level 6 — "Already Spooked"
330
331*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.*
332
333| Property | Value |
334|---|---|
335| Sheep | 18 |
336| Win threshold | 80% |
337| Timer | 240 s |
338| Pen | Large, bottom-right |
339| Spawn | Scattered (6 small clusters of 3) |
340| Obstacles | None |
341| 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. |
342
343The 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.
344
345---
346
347#### Level 7 — "The Influencers"
348
349*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.*
350
351| Property | Value |
352|---|---|
353| Sheep | 22 |
354| Win threshold | 90% |
355| Timer | 180 s |
356| Pen | Medium, right side |
357| Spawn | One large cluster in the left half |
358| Obstacles | 2 rocks in the mid-field |
359| 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. |
360
361The 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.
362
363---
364
365#### Level 8 — "Race to Market"
366
367*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.*
368
369| Property | Value |
370|---|---|
371| Sheep | 30 |
372| Win threshold | 80% (24 of 30) |
373| Timer | 75 s |
374| Pen | Medium (fits ~20 comfortably, so sheep jostle) |
375| Spawn | Two large clusters |
376| Obstacles | None (clear field — no excuses) |
377| Special | Dog speed increased to 200 px/s for this level. Sheep max speed also increased by 20% — everything moves faster. Timer is severe. |
378
379The 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.
380
381---
382
383#### Level 9 — "Lights Out"
384
385*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.*
386
387| Property | Value |
388|---|---|
389| Sheep | 16 |
390| Win threshold | 100% |
391| Timer | 270 s |
392| Pen | Medium, glows faintly (always visible) |
393| Spawn | Scattered evenly across the whole field |
394| Obstacles | 2 rocks |
395| 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. |
396
397To 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.
398
399> ⚠️ 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.
400
401---
402
403#### Level 10 — "The Last Pasture"
404
405*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.*
406
407| Property | Value |
408|---|---|
409| Sheep | 25 |
410| Win threshold | 100% |
411| Timer | None (no timer displayed — this is important) |
412| 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. |
413| Spawn | Two clusters |
414| Obstacles | One large central obstacle (a dried-out lake / flood zone) bisecting the direct route to the pen |
415| 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. |
416
417The 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.
418
419---
420
421### Difficulty Curve Summary
422
423| # | Title | Sheep | Threshold | Special mechanic |
424| --- | --- | --- | --- | --- |
425| 1 | First Lesson | 6 | 80% | None |
426| 2 | The Scatter | 12 | 100% | Dispersed spawn, no natural cohesion |
427| 3 | The Holdouts | 15 | 100% | Stubborn minority |
428| 4 | Two Tribes | 20 | 100% | Inter-flock repulsion |
429| 5 | Shrinking Pasture | 15 | 85% | Dynamic obstacles |
430| 6 | Already Spooked | 18 | 80% | Max start fear, slower dog |
431| 7 | The Influencers | 22 | 90% | Leader sheep |
432| 8 | Race to Market | 30 | 80% | Fast everything, brutal timer |
433| 9 | Lights Out | 16 | 100% | Dark canvas, audio cues |
434| 10 | The Last Pasture | 25 | 100% | Shrinking pen, combined mechanics, no timer |
435
436### Extended LevelConfig notes
437
438- All coordinates are 0.0–1.0 fractions of canvas dimensions.
439- `spawnClusters` defines where sheep appear; sheep within a cluster are placed with small random offsets from the cluster center.
440- `sheepOverrides` are applied by index after spawn assignment.
441- `darkMode` activates a canvas compositing layer; the "torch" radius and falloff are tunable constants.
442- `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).
443- Level metadata (`title`, `subtitle`) is shown on a pre-level card screen before the game starts.
444
445---
446
447## Phase 9 — Audio (Optional / Later)
448
449- Use the Web Audio API directly (no library).
450- Sounds: soft baa on scare, dog bark on tap, success chime.
451- Keep audio files small (< 50 KB total).
452- Add a mute toggle button in the corner.
453
454---
455
456## Phase 10 — GitLab CI/CD
457
458File: `.gitlab-ci.yml`
459
460```yaml
461pages:
462 stage: deploy
463 image: node:20
464 script:
465 - npm ci
466 - npm run build
467 - mv dist public
468 artifacts:
469 paths:
470 - public
471 only:
472 - main
473```
474
475- Set `base` in `vite.config.ts` to match the GitLab Pages URL path.
476- Confirm the deployed URL loads and runs the game.
477
478---
479
480## Assets & Attribution
481
482| Asset | File | Source |
483| --- | --- | --- |
484| 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) |
485
486The 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.
487
488---
489
490## File Structure (target)
491
492```text
493herd/
494├── .gitlab-ci.yml
495├── index.html
496├── package.json
497├── plant.png # favicon + logo (Icongeek26 / Flaticon, outline collection)
498├── tsconfig.json
499├── vite.config.ts
500└── src/
501 ├── main.ts # entry point — mounts canvas, starts GameLoop
502 ├── math/
503 │ └── Vec2.ts
504 ├── game/
505 │ ├── Canvas.ts # canvas sizing, dpr scaling, coord transform
506 │ ├── GameLoop.ts # rAF loop, fixed timestep
507 │ └── GameState.ts # state machine, win/fail checks, UI overlays
508 ├── entities/
509 │ ├── Entity.ts
510 │ ├── Sheep.ts
511 │ ├── Dog.ts
512 │ └── Pen.ts
513 ├── ai/
514 │ └── Boids.ts # force calculation, neighbor queries
515 ├── input/
516 │ └── InputHandler.ts # pointer events → world coords
517 └── levels/
518 └── LevelConfig.ts
519```
520
521---
522
523## Key Constants to Tune (start here, adjust by feel)
524
525| Constant | Starting Value |
526| --- | --- |
527| `SHEEP_MAX_SPEED` | 90 px/s |
528| `SHEEP_MAX_FORCE` | 6 px/s² |
529| `DOG_SPEED` | 160 px/s |
530| `FLEE_RADIUS` | 150 px |
531| `SEPARATION_RADIUS` | 30 px |
532| `ALIGN_RADIUS` | 80 px |
533| `COHESION_RADIUS` | 100 px |
534| `FEAR_DECAY` | 0.95 per tick |
535
536These will need iteration. The game feel lives entirely in these numbers.
537
538---
539
540## Development Order
541
5421. Scaffold (Phase 1) — get CI green with a blank page first.
5432. Canvas + loop (Phase 2).
5443. Vec2 + entities rendering statically (Phase 3).
5454. Boids forces in isolation with no dog — just watch sheep flock (Phase 4).
5465. Add dog input and flee force (Phase 5).
5476. Win/fail states (Phase 6).
5487. Polish passes (Phase 7+) as desired.
549
550Do **not** skip to polish before the core herding feel is fun. The boids tuning in Phase 4–5 is the game.