fix: correct pedagogical algorithm specification and tests
Key corrections based on feedback: - Fix 5's complement definition: d = (5 - (5-d)) when can't push d lowers - Use a + d ≥ 10 as strict trigger for 10's complement - Add ripple-carry logic for cascading through 9s - Include borrowing rules for subtraction during complements - Update test cases with mathematically correct expectations - Add worked examples showing proper bead state tracking 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,77 +1,110 @@
|
||||
# Soroban Pedagogical Expansion Algorithm
|
||||
|
||||
## Overview
|
||||
This algorithm generates pedagogical expansions that show how to perform arithmetic operations on a soroban (Japanese abacus) by analyzing physical bead movement constraints.
|
||||
This algorithm generates pedagogical expansions that show how to perform arithmetic operations on a soroban (Japanese abacus) by analyzing physical bead movement constraints and current abacus state.
|
||||
|
||||
## Key Principle
|
||||
The LHS (starting value) is ALREADY on the abacus. The pedagogical expansion only shows how to perform the operation (RHS) to reach the target value.
|
||||
The LHS (starting value) is ALREADY on the abacus. The pedagogical expansion only shows how to perform the operation (addend/RHS) to reach the target value.
|
||||
|
||||
## Addition Algorithm
|
||||
|
||||
### Input
|
||||
- Current abacus state = LHS value (already displayed)
|
||||
- Operation to perform = RHS value (the number to add)
|
||||
### Setup
|
||||
- Abacus shows LHS value (already displayed)
|
||||
- RHS is the addend (number to add)
|
||||
- Leave an extra blank rod on the left to absorb carries
|
||||
- Target = LHS + RHS
|
||||
|
||||
### Process
|
||||
1. **Parse RHS digit by digit from highest place value to lowest**
|
||||
2. **For each digit D at place value P:**
|
||||
### Process - Left to Right Processing
|
||||
|
||||
**Step A: Try Direct Entry**
|
||||
- Attempt to add digit D directly by moving beads at place P
|
||||
- If successful, continue to next digit
|
||||
For each digit at place P from most-significant to least-significant:
|
||||
|
||||
**Step B: Try 5's Complement**
|
||||
- If direct entry fails, try using heaven bead
|
||||
- Replace D with `(5 + (D-5))` if heaven bead available
|
||||
- If successful, continue to next digit
|
||||
1. **Setup for Place P**
|
||||
- Let `d` = RHS digit at place P. If `d = 0`, continue to next place.
|
||||
- Let `a` = current digit showing at place P (0–9)
|
||||
|
||||
**Step C: Try 10's Complement**
|
||||
- If heaven bead already active OR digit still won't fit
|
||||
- Add 10 to place value P+1 (next highest place)
|
||||
- Subtract `(10 - D)` from place value P
|
||||
- Expression: `D = (10 - (10-D))`
|
||||
2. **Decision: Direct Addition vs 10's Complement**
|
||||
- **If `a + d ≤ 9` (no carry needed):** Add within place P
|
||||
- **If `a + d ≥ 10` (carry needed):** Use 10's complement
|
||||
|
||||
**Step D: Try 100's Complement**
|
||||
- If can't add 10 to P+1 (because it shows 9)
|
||||
- Add 100 to place value P+2
|
||||
- Subtract 90 from place value P+1
|
||||
- Subtract `(10 - D)` from place value P
|
||||
3. **Case A: Direct Addition at Place P (a + d ≤ 9)**
|
||||
|
||||
**Step E: Try 1000's Complement**
|
||||
- If can't add 100 to P+2 (because it shows 9)
|
||||
- Add 1000 to place value P+3
|
||||
- Subtract 900 from place value P+2
|
||||
- Subtract 90 from place value P+1
|
||||
- Subtract `(10 - D)` from place value P
|
||||
**For d ≤ 4:**
|
||||
- If you can push `d` lower beads: do it directly
|
||||
- Else (not enough lower capacity, upper bead is up): Use 5's complement:
|
||||
- Add 5 (activate upper bead)
|
||||
- Subtract `(5 - d)` (remove lower beads)
|
||||
- Expression: `d = (5 - (5-d))`
|
||||
|
||||
**Step F: Continue Pattern**
|
||||
- Keep cascading up place values as needed
|
||||
- Each level adds the next power of 10 and subtracts appropriate complements
|
||||
**For d ≥ 5:**
|
||||
- If possible: activate upper bead (if not already) and push `d - 5` lower beads
|
||||
- If that won't fit: fall back to Case B (10's complement)
|
||||
|
||||
3. **Generate Parenthesized Expressions**
|
||||
- Each complement operation becomes a parenthesized replacement
|
||||
- Example: `7 = (10 - 3)` for ten's complement
|
||||
- Example: `6 = (5 + 1)` for five's complement
|
||||
4. **Case B: 10's Complement (a + d ≥ 10)**
|
||||
|
||||
4. **Process Left to Right**
|
||||
- Continue until entire RHS is processed
|
||||
- Each digit gets handled with appropriate complement strategy
|
||||
**Ripple-Carry Process:**
|
||||
- Find the nearest higher non-9 place value
|
||||
- Increment that place by 1
|
||||
- Set any intervening 9s to 0
|
||||
|
||||
## Examples
|
||||
**Subtraction at Place P:**
|
||||
- Subtract `(10 - d)` at place P
|
||||
- **If can't subtract at P (borrowing needed):**
|
||||
- Borrow 10 from the place just incremented (decrement by 1, add 10 to P)
|
||||
- Then subtract `(10 - d)` at place P
|
||||
|
||||
### Direct Entry
|
||||
- `4 + 3 = 7` → No complement needed, direct bead movement
|
||||
**Expression:** `d = (10 - (10-d))`
|
||||
|
||||
### Five's Complement
|
||||
- `0 + 6 = 0 + (5 + 1) = 6` → Replace 6 with (5 + 1)
|
||||
5. **Continue to Next Place**
|
||||
- Move to next place value to the right
|
||||
- Repeat process
|
||||
|
||||
### Ten's Complement
|
||||
- `4 + 7 = 4 + (10 - 3) = 11` → Replace 7 with (10 - 3)
|
||||
### Invariant
|
||||
After finishing each place, every rod shows a single decimal digit (0–9), and the abacus equals LHS + the processed prefix of RHS.
|
||||
|
||||
### Multi-Place Processing
|
||||
- `89 + 25` → Process as `80 + 9 + 20 + 5`
|
||||
- Handle 20 first (add to tens place), then 5 (add to ones place)
|
||||
## Worked Examples
|
||||
|
||||
### Example 1: 268 + 795 = 1063
|
||||
```
|
||||
Start: 2|6|8
|
||||
Hundreds (7): a=2, d=7, a+d=9 ≤ 9 → Direct addition (5+2 lowers)
|
||||
Result: 9|6|8
|
||||
Tens (9): a=6, d=9, a+d=15 ≥ 10 → 10's complement
|
||||
Ripple-carry: hundreds=9 → set to 0, increment thousands → 1|0|6|8
|
||||
Subtract (10-9)=1 from tens: 6-1=5 → 1|0|5|8
|
||||
Ones (5): a=8, d=5, a+d=13 ≥ 10 → 10's complement
|
||||
Carry: tens 5→6
|
||||
Subtract (10-5)=5 from ones: 8-5=3
|
||||
Result: 1|0|6|3 = 1063
|
||||
```
|
||||
|
||||
### Example 2: 999 + 1 = 1000
|
||||
```
|
||||
Start: 9|9|9
|
||||
Ones (1): a=9, d=1, a+d=10 ≥ 10 → 10's complement
|
||||
Ripple-carry across 9s: increment thousands to 1, clear hundreds and tens to 0
|
||||
Subtract (10-1)=9 from ones: 9-9=0
|
||||
Result: 1|0|0|0 = 1000
|
||||
```
|
||||
|
||||
### Example 3: 4 + 3 = 7 (Direct)
|
||||
```
|
||||
Start: 4 (heaven up, 4 lowers down)
|
||||
Ones (3): a=4, d=3, a+d=7 ≤ 9 → But can't push 3 more lowers (would be 7 lowers)
|
||||
Use 5's complement: 3 = (5 - 2)
|
||||
Add 5 (second heaven bead), subtract 2 lowers
|
||||
Result: 7 (both heaven beads, 2 lowers) = 7
|
||||
```
|
||||
|
||||
### Example 4: 7 + 8 = 15 (5's complement impossible)
|
||||
```
|
||||
Start: 7 (heaven up, 2 lowers down)
|
||||
Ones (8): a=7, d=8, a+d=15 ≥ 10 → 10's complement
|
||||
Add 10 to tens place
|
||||
Subtract (10-8)=2: but need to borrow first
|
||||
Borrow 10 from tens (decrement tens, add 10 to ones): 7+10=17
|
||||
Subtract 2: 17-2=15, but 15 > 9 so carry: tens+1, ones=5
|
||||
Result: 1|5 = 15
|
||||
```
|
||||
|
||||
## Subtraction Algorithm
|
||||
**TODO: Implement in separate sprint**
|
||||
|
||||
388
apps/web/src/utils/__tests__/pedagogicalAlgorithmTests.test.ts
Normal file
388
apps/web/src/utils/__tests__/pedagogicalAlgorithmTests.test.ts
Normal file
@@ -0,0 +1,388 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { generateUnifiedInstructionSequence } from '../unifiedStepGenerator'
|
||||
|
||||
describe('Pedagogical Expansion Algorithm - Addition Only', () => {
|
||||
|
||||
describe('Level 1: Direct Entry (No Complements)', () => {
|
||||
const directEntryTests = [
|
||||
{
|
||||
start: 0, target: 1,
|
||||
expected: {
|
||||
decomposition: '0 + 1 = 1',
|
||||
meaningful: false,
|
||||
steps: [{ term: '1', value: 1, instruction: /add.*1.*earth/i }]
|
||||
}
|
||||
},
|
||||
{
|
||||
start: 1, target: 3,
|
||||
expected: {
|
||||
decomposition: '1 + 2 = 3',
|
||||
meaningful: false,
|
||||
steps: [{ term: '2', value: 3, instruction: /add.*2.*earth/i }]
|
||||
}
|
||||
},
|
||||
{
|
||||
start: 0, target: 4,
|
||||
expected: {
|
||||
decomposition: '0 + 4 = 4',
|
||||
meaningful: false,
|
||||
steps: [{ term: '4', value: 4, instruction: /add.*4.*earth/i }]
|
||||
}
|
||||
},
|
||||
{
|
||||
start: 0, target: 5,
|
||||
expected: {
|
||||
decomposition: '0 + 5 = 5',
|
||||
meaningful: false,
|
||||
steps: [{ term: '5', value: 5, instruction: /activate.*heaven/i }]
|
||||
}
|
||||
},
|
||||
{
|
||||
start: 5, target: 7,
|
||||
expected: {
|
||||
decomposition: '5 + 2 = 7',
|
||||
meaningful: false,
|
||||
steps: [{ term: '2', value: 7, instruction: /add.*2.*earth/i }]
|
||||
}
|
||||
},
|
||||
{
|
||||
start: 0, target: 10,
|
||||
expected: {
|
||||
decomposition: '0 + 10 = 10',
|
||||
meaningful: false,
|
||||
steps: [{ term: '10', value: 10, instruction: /add.*1.*tens/i }]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
directEntryTests.forEach(test => {
|
||||
it(`should handle direct entry: ${test.start} + ${test.target - test.start} = ${test.target}`, () => {
|
||||
const result = generateUnifiedInstructionSequence(test.start, test.target)
|
||||
|
||||
expect(result.fullDecomposition).toContain(test.expected.decomposition)
|
||||
expect(result.isMeaningfulDecomposition).toBe(test.expected.meaningful)
|
||||
expect(result.steps).toHaveLength(test.expected.steps.length)
|
||||
|
||||
test.expected.steps.forEach((expectedStep, i) => {
|
||||
expect(result.steps[i].mathematicalTerm).toBe(expectedStep.term)
|
||||
expect(result.steps[i].expectedValue).toBe(expectedStep.value)
|
||||
expect(result.steps[i].englishInstruction).toMatch(expectedStep.instruction)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Level 2: Five-Complements Required', () => {
|
||||
const fiveComplementTests = [
|
||||
{
|
||||
start: 4, target: 7, // 4 + 3, but can't add 3 lowers (4+3=7 lowers, max is 4)
|
||||
expected: {
|
||||
decomposition: '4 + 3 = 4 + (5 - 2) = 7',
|
||||
meaningful: true,
|
||||
steps: [
|
||||
{ term: '5', value: 9, instruction: /add.*5/i }, // Add upper bead
|
||||
{ term: '-2', value: 7, instruction: /remove.*2.*earth/i } // Remove 2 lowers
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
start: 3, target: 5, // 3 + 2, but can't add 2 lowers (3+2=5 lowers, max is 4)
|
||||
expected: {
|
||||
decomposition: '3 + 2 = 3 + (5 - 3) = 5',
|
||||
meaningful: true,
|
||||
steps: [
|
||||
{ term: '5', value: 8, instruction: /add.*5/i },
|
||||
{ term: '-3', value: 5, instruction: /remove.*3.*earth/i }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
start: 2, target: 3, // 2 + 1, but can't add 1 lower (2+1=3 lowers, this should actually be direct)
|
||||
expected: {
|
||||
decomposition: '2 + 1 = 3',
|
||||
meaningful: false, // This is direct addition
|
||||
steps: [{ term: '1', value: 3, instruction: /add.*1.*earth/i }]
|
||||
}
|
||||
},
|
||||
{
|
||||
start: 0, target: 6, // 0 + 6 = 5 + 1 (direct decomposition, not complement)
|
||||
expected: {
|
||||
decomposition: '0 + 6 = 0 + 5 + 1 = 6',
|
||||
meaningful: false, // Direct: activate heaven, add 1 lower
|
||||
steps: [
|
||||
{ term: '5', value: 5, instruction: /activate.*heaven/i },
|
||||
{ term: '1', value: 6, instruction: /add.*1.*earth/i }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
start: 1, target: 5, // 1 + 4, but can't add 4 lowers (1+4=5 lowers, max is 4)
|
||||
expected: {
|
||||
decomposition: '1 + 4 = 1 + (5 - 1) = 5',
|
||||
meaningful: true,
|
||||
steps: [
|
||||
{ term: '5', value: 6, instruction: /add.*5/i },
|
||||
{ term: '-1', value: 5, instruction: /remove.*1.*earth/i }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
fiveComplementTests.forEach(test => {
|
||||
it(`should handle five-complement: ${test.start} → ${test.target}`, () => {
|
||||
const result = generateUnifiedInstructionSequence(test.start, test.target)
|
||||
|
||||
expect(result.fullDecomposition).toContain(test.expected.decomposition)
|
||||
expect(result.isMeaningfulDecomposition).toBe(test.expected.meaningful)
|
||||
expect(result.steps).toHaveLength(test.expected.steps.length)
|
||||
|
||||
test.expected.steps.forEach((expectedStep, i) => {
|
||||
expect(result.steps[i].mathematicalTerm).toBe(expectedStep.term)
|
||||
expect(result.steps[i].expectedValue).toBe(expectedStep.value)
|
||||
expect(result.steps[i].englishInstruction).toMatch(expectedStep.instruction)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Level 3: Ten-Complements Required', () => {
|
||||
const tenComplementTests = [
|
||||
{
|
||||
start: 4, target: 11, // 4 + 7, a+d = 11 ≥ 10
|
||||
expected: {
|
||||
decomposition: '4 + 7 = 4 + (10 - 3) = 11',
|
||||
meaningful: true,
|
||||
steps: [
|
||||
{ term: '10', value: 14, instruction: /add.*1.*tens/i },
|
||||
{ term: '-3', value: 11, instruction: /remove.*3.*earth/i }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
start: 6, target: 15, // 6 + 9, a+d = 15 ≥ 10
|
||||
expected: {
|
||||
decomposition: '6 + 9 = 6 + (10 - 1) = 15',
|
||||
meaningful: true,
|
||||
steps: [
|
||||
{ term: '10', value: 16, instruction: /add.*1.*tens/i },
|
||||
{ term: '-1', value: 15, instruction: /remove.*1.*earth/i }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
start: 7, target: 15, // 7 + 8, a+d = 15 ≥ 10
|
||||
expected: {
|
||||
decomposition: '7 + 8 = 7 + (10 - 2) = 15',
|
||||
meaningful: true,
|
||||
steps: [
|
||||
{ term: '10', value: 17, instruction: /add.*1.*tens/i },
|
||||
{ term: '-2', value: 15, instruction: /remove.*2.*earth/i }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
start: 5, target: 9, // 5 + 4, a+d = 9 ≤ 9 (should be direct)
|
||||
expected: {
|
||||
decomposition: '5 + 4 = 9',
|
||||
meaningful: false,
|
||||
steps: [{ term: '4', value: 9, instruction: /add.*4.*earth/i }]
|
||||
}
|
||||
},
|
||||
{
|
||||
start: 9, target: 18, // 9 + 9, a+d = 18 ≥ 10
|
||||
expected: {
|
||||
decomposition: '9 + 9 = 9 + (10 - 1) = 18',
|
||||
meaningful: true,
|
||||
steps: [
|
||||
{ term: '10', value: 19, instruction: /add.*1.*tens/i },
|
||||
{ term: '-1', value: 18, instruction: /remove.*1.*earth/i }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
tenComplementTests.forEach(test => {
|
||||
it(`should handle ten-complement: ${test.start} → ${test.target}`, () => {
|
||||
const result = generateUnifiedInstructionSequence(test.start, test.target)
|
||||
|
||||
expect(result.fullDecomposition).toContain(test.expected.decomposition)
|
||||
expect(result.isMeaningfulDecomposition).toBe(test.expected.meaningful)
|
||||
expect(result.steps).toHaveLength(test.expected.steps.length)
|
||||
|
||||
test.expected.steps.forEach((expectedStep, i) => {
|
||||
expect(result.steps[i].mathematicalTerm).toBe(expectedStep.term)
|
||||
expect(result.steps[i].expectedValue).toBe(expectedStep.value)
|
||||
expect(result.steps[i].englishInstruction).toMatch(expectedStep.instruction)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Level 4: Multi-Place Value Operations', () => {
|
||||
const multiPlaceTests = [
|
||||
{
|
||||
start: 12, target: 34,
|
||||
expected: {
|
||||
decomposition: '12 + 22 = 12 + 20 + 2 = 34',
|
||||
meaningful: true,
|
||||
steps: [
|
||||
{ term: '20', value: 32, instruction: /add.*2.*tens/i },
|
||||
{ term: '2', value: 34, instruction: /add.*2.*earth/i }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
start: 23, target: 47,
|
||||
expected: {
|
||||
decomposition: '23 + 24 = 23 + 20 + 4 = 47',
|
||||
meaningful: true,
|
||||
steps: [
|
||||
{ term: '20', value: 43, instruction: /add.*2.*tens/i },
|
||||
{ term: '4', value: 47, instruction: /add.*4.*earth/i }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
start: 34, target: 78,
|
||||
expected: {
|
||||
decomposition: '34 + 44 = 34 + 40 + 4 = 78',
|
||||
meaningful: true,
|
||||
steps: [
|
||||
{ term: '40', value: 74, instruction: /add.*4.*tens/i },
|
||||
{ term: '4', value: 78, instruction: /add.*4.*earth/i }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
multiPlaceTests.forEach(test => {
|
||||
it(`should handle multi-place: ${test.start} → ${test.target}`, () => {
|
||||
const result = generateUnifiedInstructionSequence(test.start, test.target)
|
||||
|
||||
expect(result.fullDecomposition).toContain(test.expected.decomposition)
|
||||
expect(result.isMeaningfulDecomposition).toBe(test.expected.meaningful)
|
||||
expect(result.steps).toHaveLength(test.expected.steps.length)
|
||||
|
||||
test.expected.steps.forEach((expectedStep, i) => {
|
||||
expect(result.steps[i].mathematicalTerm).toBe(expectedStep.term)
|
||||
expect(result.steps[i].expectedValue).toBe(expectedStep.value)
|
||||
expect(result.steps[i].englishInstruction).toMatch(expectedStep.instruction)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Level 5: Complex Cases with Cascading Complements', () => {
|
||||
const complexTests = [
|
||||
{
|
||||
start: 89, target: 97,
|
||||
expected: {
|
||||
decomposition: '89 + 8 = 89 + (10 - 2) = 97',
|
||||
meaningful: true,
|
||||
steps: [
|
||||
{ term: '10', value: 99, instruction: /add.*1.*tens/i },
|
||||
{ term: '-2', value: 97, instruction: /remove.*2.*earth/i }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
start: 99, target: 107,
|
||||
expected: {
|
||||
decomposition: '99 + 8 = 99 + (100 - 90) + (10 - 2) = 107',
|
||||
meaningful: true,
|
||||
steps: [
|
||||
{ term: '100', value: 199, instruction: /add.*1.*hundreds/i },
|
||||
{ term: '-90', value: 109, instruction: /remove.*9.*tens/i },
|
||||
{ term: '-2', value: 107, instruction: /remove.*2.*earth/i }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
complexTests.forEach(test => {
|
||||
it(`should handle cascading complements: ${test.start} → ${test.target}`, () => {
|
||||
const result = generateUnifiedInstructionSequence(test.start, test.target)
|
||||
|
||||
expect(result.fullDecomposition).toContain(test.expected.decomposition)
|
||||
expect(result.isMeaningfulDecomposition).toBe(test.expected.meaningful)
|
||||
expect(result.steps).toHaveLength(test.expected.steps.length)
|
||||
|
||||
test.expected.steps.forEach((expectedStep, i) => {
|
||||
expect(result.steps[i].mathematicalTerm).toBe(expectedStep.term)
|
||||
expect(result.steps[i].expectedValue).toBe(expectedStep.value)
|
||||
expect(result.steps[i].englishInstruction).toMatch(expectedStep.instruction)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge Cases and Validation', () => {
|
||||
it('should handle zero difference (no operation)', () => {
|
||||
const result = generateUnifiedInstructionSequence(5, 5)
|
||||
expect(result.isMeaningfulDecomposition).toBe(false)
|
||||
expect(result.steps).toHaveLength(0)
|
||||
expect(result.fullDecomposition).toBe('5 + 0 = 5')
|
||||
})
|
||||
|
||||
it('should maintain abacus state consistency throughout', () => {
|
||||
const result = generateUnifiedInstructionSequence(0, 17)
|
||||
|
||||
// Each step should have valid expected state
|
||||
result.steps.forEach(step => {
|
||||
expect(step.expectedState).toBeDefined()
|
||||
expect(step.expectedValue).toBeGreaterThan(0)
|
||||
expect(step.isValid).toBe(true)
|
||||
})
|
||||
|
||||
// Final step should reach target
|
||||
const finalStep = result.steps[result.steps.length - 1]
|
||||
expect(finalStep.expectedValue).toBe(17)
|
||||
})
|
||||
|
||||
it('should generate proper bead movement data', () => {
|
||||
const result = generateUnifiedInstructionSequence(0, 6)
|
||||
|
||||
result.steps.forEach(step => {
|
||||
expect(Array.isArray(step.beadMovements)).toBe(true)
|
||||
step.beadMovements.forEach(movement => {
|
||||
expect(movement).toHaveProperty('placeValue')
|
||||
expect(movement).toHaveProperty('beadType')
|
||||
expect(movement).toHaveProperty('direction')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Algorithm Bookkeeping Verification', () => {
|
||||
it('should track abacus state after every operation', () => {
|
||||
const result = generateUnifiedInstructionSequence(34, 89)
|
||||
|
||||
let currentValue = 34
|
||||
result.steps.forEach(step => {
|
||||
// Each step should progress toward target
|
||||
expect(step.expectedValue).not.toBe(currentValue)
|
||||
currentValue = step.expectedValue
|
||||
|
||||
// State should be valid for abacus constraints
|
||||
Object.values(step.expectedState).forEach(beadState => {
|
||||
expect(beadState.earthActive).toBeGreaterThanOrEqual(0)
|
||||
expect(beadState.earthActive).toBeLessThanOrEqual(4)
|
||||
expect(typeof beadState.heavenActive).toBe('boolean')
|
||||
})
|
||||
})
|
||||
|
||||
// Final value should match target
|
||||
expect(currentValue).toBe(89)
|
||||
})
|
||||
|
||||
it('should make correct complement decisions based on current state', () => {
|
||||
// Test case where heaven bead is already active
|
||||
const result = generateUnifiedInstructionSequence(7, 13) // 7 + 6
|
||||
|
||||
// Should NOT use five-complement for 6 since heaven already active
|
||||
// Should either use ten-complement or break down 6 differently
|
||||
const hasInvalidFiveComplement = result.fullDecomposition.includes('(5 + 1)')
|
||||
expect(hasInvalidFiveComplement).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user