feat(02-02): sim/garden — types, plants table, growth state machine, plantSeed
- src/sim/garden/types.ts: Tile/PlantInstance/PlantType/PlantTypeId/GrowthStage interfaces; tileIdx(row,col) + tileCoords(idx) + emptyTiles() helpers (RESEARCH Pitfall 2 canonical row*4+col encoding) - src/sim/garden/plants.ts: 3 Season-1 plants per D-03 (rosemary 600t / yarrow 900t / winter-rose 1500t — D-08/D-09 2–5min band) with placeholder tints (D-26) - src/sim/garden/growth.ts: advanceGrowth() pure function — Sprout (0%) → Mature (33%) → Ready (100%); Math.max clamp on negative deltas defends Pitfall 1 system-clock rewind - src/sim/garden/commands.ts: plantSeed (D-05 unlock-gate + occupied silent no-op + immutability) + simulateOneTick (BLOCKER 3 — writes tickCount, NEVER lastTickAt) + tileGrowthStage helper - src/sim/garden/index.ts: barrel - src/sim/index.ts: re-export garden barrel - 25 new tests (11 growth + 14 commands) — all green; lint clean; build green - ESLint sim-purity rule from Plan 02-01 confirms zero Date.now/setInterval call sites under src/sim/garden/
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { advanceGrowth, GROWTH_THRESHOLDS } from './growth';
|
||||
import { PLANT_TYPES } from './plants';
|
||||
import type { PlantInstance } from './types';
|
||||
|
||||
const rosemary = PLANT_TYPES.rosemary;
|
||||
const yarrow = PLANT_TYPES.yarrow;
|
||||
const winterRose = PLANT_TYPES['winter-rose'];
|
||||
|
||||
function plant(plantedAtTick: number, plantTypeId: PlantInstance['plantTypeId'] = 'rosemary'): PlantInstance {
|
||||
return { plantedAtTick, plantTypeId };
|
||||
}
|
||||
|
||||
describe('advanceGrowth (D-08, D-09; pure function of currentTick + duration)', () => {
|
||||
it('returns sprout at tick=plantedAtTick', () => {
|
||||
expect(advanceGrowth(plant(0), rosemary, 0)).toBe('sprout');
|
||||
});
|
||||
|
||||
it('returns sprout just below the 33% mature threshold', () => {
|
||||
// 600 * 0.33 = 198. Tick 197 is below the threshold.
|
||||
expect(advanceGrowth(plant(0), rosemary, 197)).toBe('sprout');
|
||||
});
|
||||
|
||||
it('returns mature at the 33% threshold (≥, not >)', () => {
|
||||
expect(advanceGrowth(plant(0), rosemary, 198)).toBe('mature');
|
||||
});
|
||||
|
||||
it('returns mature just below the ready threshold', () => {
|
||||
expect(advanceGrowth(plant(0), rosemary, 599)).toBe('mature');
|
||||
});
|
||||
|
||||
it('returns ready at the duration boundary (100%)', () => {
|
||||
expect(advanceGrowth(plant(0), rosemary, 600)).toBe('ready');
|
||||
});
|
||||
|
||||
it('returns sprout when just planted (currentTick === plantedAtTick != 0)', () => {
|
||||
expect(advanceGrowth(plant(100), rosemary, 100)).toBe('sprout');
|
||||
});
|
||||
|
||||
it('clamps negative deltas to sprout (Pitfall 1 — system-clock rewind defense)', () => {
|
||||
expect(advanceGrowth(plant(100), rosemary, 50)).toBe('sprout');
|
||||
});
|
||||
|
||||
it('overgrowth stays at ready (no overflow stage)', () => {
|
||||
expect(advanceGrowth(plant(0), rosemary, 100000)).toBe('ready');
|
||||
});
|
||||
|
||||
it('respects per-plant duration — yarrow at 900 ticks is ready', () => {
|
||||
// Yarrow 33% threshold = 297; 900 = ready.
|
||||
expect(advanceGrowth(plant(0), yarrow, 296)).toBe('sprout');
|
||||
expect(advanceGrowth(plant(0), yarrow, 297)).toBe('mature');
|
||||
expect(advanceGrowth(plant(0), yarrow, 899)).toBe('mature');
|
||||
expect(advanceGrowth(plant(0), yarrow, 900)).toBe('ready');
|
||||
});
|
||||
|
||||
it('respects per-plant duration — winter-rose at 1500 ticks is ready', () => {
|
||||
// 1500 * 0.33 = 495.
|
||||
expect(advanceGrowth(plant(0), winterRose, 494)).toBe('sprout');
|
||||
expect(advanceGrowth(plant(0), winterRose, 495)).toBe('mature');
|
||||
expect(advanceGrowth(plant(0), winterRose, 1499)).toBe('mature');
|
||||
expect(advanceGrowth(plant(0), winterRose, 1500)).toBe('ready');
|
||||
});
|
||||
|
||||
it('GROWTH_THRESHOLDS is frozen (no accidental mutation)', () => {
|
||||
expect(Object.isFrozen(GROWTH_THRESHOLDS)).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user