AgentSkillsCN

matrix-data-model-progression-testing

利用 ASCII 图表验证矩阵数据模型。在处理 *Progressions.ts 文件、调用 defineProgression() 函数,或测试二维数值网格随时间演化的过程中使用此方法。当编辑匹配 *Progressions.ts 或 src/test-utils/ascii*.ts 的文件时,自动启用该方法。

SKILL.md
--- frontmatter
name: matrix-data-model-progression-testing
description: Matrix data model verification using ASCII diagrams. Use when working with *Progressions.ts files, defineProgression(), or testing how 2D numeric grids evolve over time. Auto-apply when editing files matching *Progressions.ts or src/test-utils/ascii*.ts.

Matrix Data Model Progression Testing Skill

Verifies how 2D numeric matrices (the underlying data model) evolve over simulated time. Uses compact ASCII diagrams inspired by RxJS marble testing.

This is about data correctness, not rendering. The wave simulation uses matrices to represent:

  • Bathymetry (depth values)
  • Energy fields (wave energy at each cell)
  • Foam density (foam amount at each cell)

When to Use This

  • Editing *Progressions.ts files (bathymetry, energy field, foam, etc.)
  • Designing new simulation behaviors
  • Debugging why a layer produces unexpected data output
  • Verifying time-series evolution of matrix values

Architecture

code
src/test-utils/
  asciiMatrix.ts           → ASCII encoding/decoding utilities
  asciiMatrix.test.ts      → Tests for the utilities themselves
  progression.ts           → defineProgression() framework
  progression.test.ts      → Tests for the framework

src/render/*Progressions.ts    → Layer progression definitions
src/state/energyFieldProgressions.ts

ASCII Matrix Format

Each character represents a cell value (0.0-1.0):

CharValue RangeMeaning
-< 0.05No energy / zero
10.05-0.14Very low
20.15-0.24Low
30.25-0.34Low-medium
40.35-0.44Medium-low
A0.45-0.54Medium
B0.55-0.64Medium-high
C0.65-0.74High
D0.75-0.84Higher
E0.85-0.94Very high
F>= 0.95Full energy / max

Single Matrix Example

code
FFFFF    ← Row 0: Full energy at horizon
-----    ← Row 1: No energy
-----    ← Row 2: No energy
-----    ← Row 3: No energy
-----    ← Row 4: No energy (shore)

Multi-Frame Progression Example

Shows energy propagating downward over time:

code
t=0s     t=1s     t=2s     t=3s
FFFFF    BBBBB    44444    22222
-----    AAAAA    AAAAA    44444
-----    22222    44444    44444
-----    11111    22222    33333
-----    -----    11111    22222

Core Utilities

typescript
import {
  matrixToAscii,
  asciiToMatrix,
  progressionToAscii,
  asciiToProgression,
  matricesMatchAscii,
  valueToChar,
  charToValue,
} from '../test-utils/asciiMatrix';

// Single matrix
matrixToAscii([[1.0, 0.5], [0.0, 0.2]])  // → "FA\n-2"
asciiToMatrix("FA\n-2")                   // → [[1.0, 0.5], [0.0, 0.2]]

// Multi-frame progression
progressionToAscii(snapshots)             // → side-by-side frames with headers
asciiToProgression(asciiString)           // → array of {time, matrix}

// Comparison (tolerant to ASCII precision)
matricesMatchAscii(actual, expected)      // → true if same when encoded

defineProgression() Framework

typescript
import { defineProgression } from '../test-utils/progression';

export const PROGRESSION_ENERGY_PROPAGATION = defineProgression({
  id: 'energy-field/propagation',
  description: 'Energy propagates from horizon toward shore',
  initialMatrix: [
    [1.0, 1.0, 1.0, 1.0, 1.0],  // Horizon: full energy
    [0.0, 0.0, 0.0, 0.0, 0.0],
    [0.0, 0.0, 0.0, 0.0, 0.0],
    [0.0, 0.0, 0.0, 0.0, 0.0],
    [0.0, 0.0, 0.0, 0.0, 0.0],  // Shore: no energy yet
  ],
  updateFn: (matrix, dt) => propagateEnergy(matrix, dt),
  captureTimes: [0, 1, 2, 3, 4, 5],  // Capture snapshots at these times
});

// Access computed snapshots
PROGRESSION_ENERGY_PROPAGATION.snapshots[0].matrix  // t=0
PROGRESSION_ENERGY_PROPAGATION.snapshots[3].matrix  // t=3

Testing Progressions

Inline ASCII Assertions

typescript
import { describe, it, expect } from 'vitest';
import { progressionToAscii } from '../test-utils/asciiMatrix';

describe('PROGRESSION_ENERGY_PROPAGATION', () => {
  it('produces expected time evolution', () => {
    const ascii = progressionToAscii(PROGRESSION_ENERGY_PROPAGATION.snapshots);

    expect(ascii).toBe(`
t=0s     t=1s     t=2s     t=3s
FFFFF    BBBBB    44444    22222
-----    AAAAA    AAAAA    44444
-----    22222    44444    44444
-----    11111    22222    33333
-----    -----    11111    22222
`.trim());
  });
});

Point Assertions

typescript
it('energy reaches row 3 by t=3s', () => {
  const snapshot = PROGRESSION_ENERGY_PROPAGATION.snapshots[3];
  expect(snapshot.matrix[3][0]).toBeGreaterThan(0);
});

Workflow: Designing a New Progression

  1. Sketch ASCII first - Draw what you expect the evolution to look like
  2. Write the test - Use progressionToAscii() with your expected ASCII
  3. Implement the logic - Write the updateFn that produces this behavior
  4. Run test - npx vitest run src/render/myProgressions.test.ts
  5. Iterate - Adjust coefficients until ASCII matches expectations
code
Design-first workflow:
  Sketch ASCII → Write test → Implement → Verify

NOT trial-and-error:
  Implement → Run → "Hmm, that looks wrong" → Tweak → Repeat

Why ASCII Over Vitest Snapshots?

AspectASCIIVitest Snapshots
ReadabilityVisual pattern obviousWall of numbers
Size7 lines for 6 frames1000+ lines
LocationInline in testSeparate file
DiffsShows exactly which cells changedHard to parse
DesignCan sketch expected output firstMust run to generate

Relationship to Visual Testing

Progression tests verify the data (matrix values). Visual tests verify the rendering (pixels on screen).

code
Progression Test (this skill)     Visual Test (visual-testing skill)
        ↓                                    ↓
   Matrix data                         Screenshot
   [1.0, 0.5, 0.2]                    [PNG pixels]
        ↓                                    ↓
   ASCII: "FA2"                       Baseline comparison

Always verify data first. If the matrix is wrong, the visual will be wrong too. Don't debug rendering when the underlying data is the problem.

See visual-testing skill for screenshot-based regression testing.

Commands

bash
# Run progression tests
npx vitest run src/render/bathymetryProgressions.test.ts
npx vitest run src/state/energyFieldProgressions.test.ts

# Run all test utilities (including ASCII)
npx vitest run src/test-utils/

# Watch mode for development
npx vitest src/render/foamProgressions.test.ts

Common Patterns

Testing Boundary Conditions

typescript
it('handles zero initial energy gracefully', () => {
  const progression = defineProgression({
    initialMatrix: createZeroMatrix(5, 5),
    updateFn: propagateEnergy,
    captureTimes: [0, 1, 2],
  });

  // Should remain zero (no energy to propagate)
  expect(matrixToAscii(progression.snapshots[2].matrix)).toBe(
    '-----\n-----\n-----\n-----\n-----'
  );
});

Testing Conservation Laws

typescript
it('total energy decreases with damping', () => {
  const snapshots = PROGRESSION_WITH_DAMPING.snapshots;
  const totalEnergy = (m: number[][]) => m.flat().reduce((a, b) => a + b, 0);

  expect(totalEnergy(snapshots[3].matrix))
    .toBeLessThan(totalEnergy(snapshots[0].matrix));
});

Comparing Two Progressions

typescript
it('damped version has less energy than undamped', () => {
  const damped = PROGRESSION_WITH_DAMPING.snapshots[3].matrix;
  const undamped = PROGRESSION_NO_DAMPING.snapshots[3].matrix;

  const sum = (m: number[][]) => m.flat().reduce((a, b) => a + b, 0);
  expect(sum(damped)).toBeLessThan(sum(undamped));
});