lantern

sprout-garden-core-test

Test: Sprout Garden Core

Following [[pattern-looking-glass-development]] - the tests ARE the documentation.

Subject

The core simulation engine (src/core/simulation.js) implementing Hot Potato execution:

  • Position key conversion (A1, B2, etc.)
  • Seed/Fruit direction matching
  • Sprout movement and cloning
  • Tick-based execution

config

path: null
base_url: file:///Users/graemefawcett/working/ben.fawcett.family/activities/sprout-garden

Spec

Test: Position Key Conversion

// posKey(x, y) converts grid coords to Excel-style keys
// (0,0) → "A1", (1,0) → "A2", (0,1) → "B1"

describe('posKey', () => {
  it('converts (0,0) to A1', () => {
    expect(posKey(0, 0)).toBe('A1');
  });
  
  it('converts (1,0) to A2', () => {
    expect(posKey(1, 0)).toBe('A2');
  });
  
  it('converts (0,1) to B1', () => {
    expect(posKey(0, 1)).toBe('B1');
  });
  
  it('handles large grids', () => {
    expect(posKey(9, 7)).toBe('H10');
  });
});

Test: Key to Coords Conversion

// keyToCoords(key) reverses posKey
// "A1" → {x: 0, y: 0}, "B2" → {x: 1, y: 1}

describe('keyToCoords', () => {
  it('converts A1 to (0,0)', () => {
    expect(keyToCoords('A1')).toEqual({x: 0, y: 0});
  });
  
  it('converts B2 to (1,1)', () => {
    expect(keyToCoords('B2')).toEqual({x: 1, y: 1});
  });
  
  it('handles H10', () => {
    expect(keyToCoords('H10')).toEqual({x: 9, y: 7});
  });
});

Test: Can Receive Direction

// canReceive(plotType, fromDirection) checks if plot accepts from direction
// fromDirection is where sprout came FROM, check opposite

describe('canReceive', () => {
  it('vine-right accepts from left', () => {
    // vine-right has seeds: ['left', 'up', 'down']
    expect(canReceive('vine-right', 'right')).toBe(true); // came from right, enters from left
  });
  
  it('vine-right rejects from right', () => {
    expect(canReceive('vine-right', 'left')).toBe(false); // came from left, would enter from right
  });
  
  it('harvest accepts from all directions', () => {
    expect(canReceive('harvest', 'left')).toBe(true);
    expect(canReceive('harvest', 'right')).toBe(true);
    expect(canReceive('harvest', 'up')).toBe(true);
    expect(canReceive('harvest', 'down')).toBe(true);
  });
});

Test: Get Fruit Directions

// getFruitDirections(plotType, incomingDirection) returns output directions

describe('getFruitDirections', () => {
  it('pass-through continues same direction', () => {
    // form-grower has fruits: ['pass-through']
    expect(getFruitDirections('form-grower', 'right')).toEqual(['right']);
    expect(getFruitDirections('form-grower', 'down')).toEqual(['down']);
  });
  
  it('perpendicular splits 90 degrees', () => {
    // splitter has fruits: ['perpendicular']
    expect(getFruitDirections('splitter', 'right')).toEqual(['up', 'down']);
    expect(getFruitDirections('splitter', 'up')).toEqual(['left', 'right']);
  });
  
  it('vine outputs single direction', () => {
    expect(getFruitDirections('vine-right', 'right')).toEqual(['right']);
  });
});

Test: Sprout Cloning for Splits

// When a splitter outputs to multiple directions, sprout is cloned

describe('Sprout Cloning', () => {
  it('creates separate sprouts for each output', () => {
    const sim = new Simulation(gardenWithSplitter);
    sim.run();
    
    // Started with 1 sprout, should have 2 after split
    expect(sim.sprouts.length).toBe(2);
  });
  
  it('cloned sprouts have copied lineage', () => {
    const sim = new Simulation(gardenWithSplitter);
    sim.run();
    
    const [original, clone] = sim.sprouts;
    expect(clone.lineage.length).toBeGreaterThan(0);
  });
  
  it('cloned sprouts have unique IDs', () => {
    const sim = new Simulation(gardenWithSplitter);
    sim.run();
    
    const [original, clone] = sim.sprouts;
    expect(original.id).not.toBe(clone.id);
  });
});

Test: Hot Potato Execution

// All sprouts move together at end of tick

describe('Hot Potato Execution', () => {
  it('all movements happen simultaneously', () => {
    const sim = new Simulation(gardenWithTwoSprings);
    
    // After tick 1, both sprouts should have moved
    sim.emitFromSprings();
    sim.executeTick();
    
    const positions = sim.sprouts.map(s => s.position);
    // Neither should be at spring position anymore
    expect(positions).not.toContain('A1');
    expect(positions).not.toContain('E1');
  });
  
  it('sprout marked lost only if NO valid direction found', () => {
    const sim = new Simulation(gardenWithDeadEnd);
    sim.run();
    
    const lostSprout = sim.sprouts.find(s => s.status === 'lost');
    expect(lostSprout.lineage.at(-1).action).toBe('stuck');
  });
});

Results

{
  "summary": "0/2 tests passing",
  "tests": [
    {
      "test": "simulation.js exists",
      "status": "fail",
      "details": "file not found"
    },
    {
      "test": "constants.js exists",
      "status": "fail",
      "details": "file not found"
    }
  ]
}

Provenance

Fences

test-position-keys

  • Status: Spec only (JS tests pending vitest setup)
  • By: Claude (2025-11-29)
  • Note: Defines expected behavior for posKey()

test-key-to-coords

  • Status: Spec only
  • By: Claude (2025-11-29)
  • Note: Defines expected behavior for keyToCoords()

test-can-receive

  • Status: Spec only
  • By: Claude (2025-11-29)
  • Note: Defines seed direction matching

test-fruit-directions

  • Status: Spec only
  • By: Claude (2025-11-29)
  • Note: Defines output direction logic including pass-through and perpendicular

test-sprout-cloning

  • Status: Spec only
  • By: Claude (2025-11-29)
  • Note: Defines splitter behavior

test-hot-potato

  • Status: Spec only
  • By: Claude (2025-11-29)
  • Note: Defines simultaneous movement semantics

sprout-garden-core-results

  • Status: Verified (Python static analysis)
  • By: Claude (2025-11-29)
  • Note: Checks source files exist and contain expected functions

Slots

North

slots:
- sprout-garden-core

South

slots: []

East

slots: []

West

slots:
- pattern-looking-glass-development