redscript-mc 1.2.24 → 1.2.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish-extension-on-ci.yml +1 -0
- package/dist/__tests__/cli.test.js +1 -1
- package/dist/__tests__/codegen.test.js +12 -6
- package/dist/__tests__/e2e.test.js +6 -6
- package/dist/__tests__/lowering.test.js +8 -8
- package/dist/__tests__/optimizer.test.js +31 -0
- package/dist/__tests__/stdlib-advanced.test.d.ts +4 -0
- package/dist/__tests__/stdlib-advanced.test.js +264 -0
- package/dist/__tests__/stdlib-math.test.d.ts +7 -0
- package/dist/__tests__/stdlib-math.test.js +352 -0
- package/dist/__tests__/stdlib-vec.test.d.ts +4 -0
- package/dist/__tests__/stdlib-vec.test.js +264 -0
- package/dist/ast/types.d.ts +17 -1
- package/dist/codegen/mcfunction/index.js +159 -18
- package/dist/codegen/var-allocator.d.ts +17 -0
- package/dist/codegen/var-allocator.js +33 -3
- package/dist/compile.d.ts +14 -0
- package/dist/compile.js +62 -5
- package/dist/index.js +20 -1
- package/dist/ir/types.d.ts +4 -0
- package/dist/lexer/index.d.ts +1 -1
- package/dist/lexer/index.js +1 -0
- package/dist/lowering/index.d.ts +5 -0
- package/dist/lowering/index.js +83 -10
- package/dist/optimizer/dce.js +21 -5
- package/dist/optimizer/passes.js +18 -6
- package/dist/optimizer/structure.js +7 -0
- package/dist/parser/index.d.ts +5 -0
- package/dist/parser/index.js +43 -2
- package/dist/runtime/index.d.ts +6 -0
- package/dist/runtime/index.js +109 -9
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/package.json +1 -1
- package/src/__tests__/cli.test.ts +1 -1
- package/src/__tests__/codegen.test.ts +12 -6
- package/src/__tests__/e2e.test.ts +6 -6
- package/src/__tests__/lowering.test.ts +8 -8
- package/src/__tests__/optimizer.test.ts +33 -0
- package/src/__tests__/stdlib-advanced.test.ts +259 -0
- package/src/__tests__/stdlib-math.test.ts +374 -0
- package/src/__tests__/stdlib-vec.test.ts +259 -0
- package/src/ast/types.ts +11 -1
- package/src/codegen/mcfunction/index.ts +148 -19
- package/src/codegen/var-allocator.ts +36 -3
- package/src/compile.ts +72 -5
- package/src/index.ts +21 -1
- package/src/ir/types.ts +2 -0
- package/src/lexer/index.ts +2 -1
- package/src/lowering/index.ts +96 -10
- package/src/optimizer/dce.ts +22 -5
- package/src/optimizer/passes.ts +18 -5
- package/src/optimizer/structure.ts +6 -1
- package/src/parser/index.ts +47 -2
- package/src/runtime/index.ts +108 -10
- package/src/stdlib/advanced.mcrs +249 -0
- package/src/stdlib/math.mcrs +259 -19
- package/src/stdlib/vec.mcrs +246 -0
|
@@ -19,26 +19,32 @@ describe('generateDatapack', () => {
|
|
|
19
19
|
})
|
|
20
20
|
|
|
21
21
|
it('generates function file for simple add(a, b)', () => {
|
|
22
|
+
// IR now uses { kind: 'param', index: i } for param-copy instructions,
|
|
23
|
+
// matching what the lowering emits. Pass mangle:false so we can check
|
|
24
|
+
// readable names without worrying about sequential mangled names.
|
|
22
25
|
const mod: IRModule = {
|
|
23
26
|
namespace: 'mypack',
|
|
24
27
|
globals: [],
|
|
25
28
|
functions: [{
|
|
26
29
|
name: 'add',
|
|
27
|
-
params: ['a', 'b'],
|
|
28
|
-
locals: ['a', 'b', 'result'],
|
|
30
|
+
params: ['$a', '$b'],
|
|
31
|
+
locals: ['$a', '$b', '$result'],
|
|
29
32
|
blocks: [{
|
|
30
33
|
label: 'entry',
|
|
31
34
|
instrs: [
|
|
32
|
-
|
|
35
|
+
// param-copy instructions (what the lowering now emits)
|
|
36
|
+
{ op: 'assign', dst: '$a', src: { kind: 'param', index: 0 } },
|
|
37
|
+
{ op: 'assign', dst: '$b', src: { kind: 'param', index: 1 } },
|
|
38
|
+
{ op: 'binop', dst: '$result', lhs: { kind: 'var', name: '$a' }, bop: '+', rhs: { kind: 'var', name: '$b' } },
|
|
33
39
|
],
|
|
34
|
-
term: { op: 'return', value: { kind: 'var', name: 'result' } },
|
|
40
|
+
term: { op: 'return', value: { kind: 'var', name: '$result' } },
|
|
35
41
|
}],
|
|
36
42
|
}],
|
|
37
43
|
}
|
|
38
|
-
const files =
|
|
44
|
+
const files = generateDatapackWithStats(mod, { mangle: false }).files
|
|
39
45
|
const fn = files.find(f => f.path.includes('add.mcfunction'))
|
|
40
46
|
expect(fn).toBeDefined()
|
|
41
|
-
//
|
|
47
|
+
// param setup emitted from the IR
|
|
42
48
|
expect(fn!.content).toContain('scoreboard players operation $a rs = $p0 rs')
|
|
43
49
|
expect(fn!.content).toContain('scoreboard players operation $b rs = $p1 rs')
|
|
44
50
|
// Should have add operation
|
|
@@ -124,7 +124,7 @@ fn main() {
|
|
|
124
124
|
`)
|
|
125
125
|
const mainFn = getFunction(files, 'main')
|
|
126
126
|
expect(mainFn).toBeDefined()
|
|
127
|
-
expect(mainFn).toContain('scoreboard players set $
|
|
127
|
+
expect(mainFn).toContain('scoreboard players set $main_hp rs 105')
|
|
128
128
|
expect(mainFn).toContain('Arena Battle')
|
|
129
129
|
})
|
|
130
130
|
})
|
|
@@ -1852,7 +1852,7 @@ describe('for-range loop', () => {
|
|
|
1852
1852
|
const src = `fn test() { for i in 0..5 { say("hi"); } }`
|
|
1853
1853
|
const files = compile(src, 'forloop')
|
|
1854
1854
|
const fn = getFunction(files, 'test')
|
|
1855
|
-
expect(fn).toContain('scoreboard players set $
|
|
1855
|
+
expect(fn).toContain('scoreboard players set $test_i rs 0')
|
|
1856
1856
|
})
|
|
1857
1857
|
|
|
1858
1858
|
it('generates loop sub-function with increment and condition', () => {
|
|
@@ -1861,17 +1861,17 @@ describe('for-range loop', () => {
|
|
|
1861
1861
|
const subFn = files.find(f => f.path.includes('__for_0'))
|
|
1862
1862
|
expect(subFn).toBeDefined()
|
|
1863
1863
|
expect(subFn?.content).toContain('say hi')
|
|
1864
|
-
expect(subFn?.content).toContain('scoreboard players add $
|
|
1865
|
-
expect(subFn?.content).toContain('execute if score $
|
|
1864
|
+
expect(subFn?.content).toContain('scoreboard players add $test_i rs 1')
|
|
1865
|
+
expect(subFn?.content).toContain('execute if score $test_i rs matches ..4 run function forloop:test/__for_0')
|
|
1866
1866
|
})
|
|
1867
1867
|
|
|
1868
1868
|
it('supports non-zero start', () => {
|
|
1869
1869
|
const src = `fn test() { for x in 3..8 { say("loop"); } }`
|
|
1870
1870
|
const files = compile(src, 'forloop2')
|
|
1871
1871
|
const fn = getFunction(files, 'test')
|
|
1872
|
-
expect(fn).toContain('scoreboard players set $
|
|
1872
|
+
expect(fn).toContain('scoreboard players set $test_x rs 3')
|
|
1873
1873
|
const subFn = files.find(f => f.path.includes('__for_0'))
|
|
1874
|
-
expect(subFn?.content).toContain('execute if score $
|
|
1874
|
+
expect(subFn?.content).toContain('execute if score $test_x rs matches ..7 run function forloop2:test/__for_0')
|
|
1875
1875
|
})
|
|
1876
1876
|
})
|
|
1877
1877
|
|
|
@@ -50,7 +50,7 @@ describe('Lowering', () => {
|
|
|
50
50
|
const fn = getFunction(ir, 'foo')!
|
|
51
51
|
const instrs = getInstructions(fn)
|
|
52
52
|
expect(instrs.some(i =>
|
|
53
|
-
i.op === 'assign' && i.dst === '$
|
|
53
|
+
i.op === 'assign' && i.dst === '$foo_x' && (i.src as any).kind === 'param' && (i.src as any).index === 0
|
|
54
54
|
)).toBe(true)
|
|
55
55
|
})
|
|
56
56
|
|
|
@@ -139,7 +139,7 @@ fn foo() {
|
|
|
139
139
|
const fn = getFunction(ir, 'foo')!
|
|
140
140
|
const instrs = getInstructions(fn)
|
|
141
141
|
expect(instrs.some(i =>
|
|
142
|
-
i.op === 'assign' && i.dst === '$
|
|
142
|
+
i.op === 'assign' && i.dst === '$foo_x' && (i.src as any).kind === 'const' && (i.src as any).value === 100
|
|
143
143
|
)).toBe(true)
|
|
144
144
|
expect(ir.globals).not.toContain('$MAX_HP')
|
|
145
145
|
})
|
|
@@ -149,7 +149,7 @@ fn foo() {
|
|
|
149
149
|
const fn = getFunction(ir, 'foo')!
|
|
150
150
|
const instrs = getInstructions(fn)
|
|
151
151
|
expect(instrs.some(i =>
|
|
152
|
-
i.op === 'assign' && i.dst === '$
|
|
152
|
+
i.op === 'assign' && i.dst === '$foo_x' && (i.src as any).value === 42
|
|
153
153
|
)).toBe(true)
|
|
154
154
|
})
|
|
155
155
|
|
|
@@ -579,16 +579,16 @@ fn choose(dir: Direction) {
|
|
|
579
579
|
const ir = compile('fn test() { let score: int = 7; say("You have ${score} points"); }')
|
|
580
580
|
const fn = getFunction(ir, 'test')!
|
|
581
581
|
const rawCmds = getRawCommands(fn)
|
|
582
|
-
expect(rawCmds).toContain('tellraw @a ["",{"text":"You have "},{"score":{"name":"$
|
|
582
|
+
expect(rawCmds).toContain('tellraw @a ["",{"text":"You have "},{"score":{"name":"$test_score","objective":"rs"}},{"text":" points"}]')
|
|
583
583
|
})
|
|
584
584
|
|
|
585
585
|
it('lowers f-string output builtins to tellraw/title JSON components', () => {
|
|
586
586
|
const ir = compile('fn test() { let score: int = 7; say(f"Score: {score}"); tellraw(@a, f"Score: {score}"); actionbar(@s, f"Score: {score}"); title(@s, f"Score: {score}"); }')
|
|
587
587
|
const fn = getFunction(ir, 'test')!
|
|
588
588
|
const rawCmds = getRawCommands(fn)
|
|
589
|
-
expect(rawCmds).toContain('tellraw @a ["",{"text":"Score: "},{"score":{"name":"$
|
|
590
|
-
expect(rawCmds).toContain('title @s actionbar ["",{"text":"Score: "},{"score":{"name":"$
|
|
591
|
-
expect(rawCmds).toContain('title @s title ["",{"text":"Score: "},{"score":{"name":"$
|
|
589
|
+
expect(rawCmds).toContain('tellraw @a ["",{"text":"Score: "},{"score":{"name":"$test_score","objective":"rs"}}]')
|
|
590
|
+
expect(rawCmds).toContain('title @s actionbar ["",{"text":"Score: "},{"score":{"name":"$test_score","objective":"rs"}}]')
|
|
591
|
+
expect(rawCmds).toContain('title @s title ["",{"text":"Score: "},{"score":{"name":"$test_score","objective":"rs"}}]')
|
|
592
592
|
})
|
|
593
593
|
|
|
594
594
|
it('lowers summon()', () => {
|
|
@@ -1149,7 +1149,7 @@ fn count_down() {
|
|
|
1149
1149
|
const fn = getFunction(ir, 'test')!
|
|
1150
1150
|
const instrs = getInstructions(fn)
|
|
1151
1151
|
expect(instrs.some(i =>
|
|
1152
|
-
i.op === 'assign' && i.dst === '$
|
|
1152
|
+
i.op === 'assign' && i.dst === '$test_y' && (i.src as any).kind === 'var' && (i.src as any).name === '$count'
|
|
1153
1153
|
)).toBe(true)
|
|
1154
1154
|
})
|
|
1155
1155
|
|
|
@@ -106,6 +106,39 @@ describe('deadCodeElimination', () => {
|
|
|
106
106
|
})
|
|
107
107
|
})
|
|
108
108
|
|
|
109
|
+
describe('copyPropagation – stale alias invalidation', () => {
|
|
110
|
+
it('does not propagate $tmp = $y after $y is overwritten (swap pattern)', () => {
|
|
111
|
+
// Simulates: let tmp = y; y = x % y; x = tmp
|
|
112
|
+
// The copy $tmp = $y must be invalidated when $y is reassigned.
|
|
113
|
+
// Before fix: x = tmp was propagated to x = y (new y, wrong value).
|
|
114
|
+
const fn = makeFn([
|
|
115
|
+
{ op: 'assign', dst: '$tmp', src: { kind: 'var', name: '$y' } }, // tmp = y
|
|
116
|
+
{ op: 'binop', dst: '$r', lhs: { kind: 'var', name: '$x' }, bop: '%', rhs: { kind: 'var', name: '$y' } }, // r = x%y
|
|
117
|
+
{ op: 'assign', dst: '$y', src: { kind: 'var', name: '$r' } }, // y = r ← stale: tmp still points to OLD y
|
|
118
|
+
{ op: 'assign', dst: '$x', src: { kind: 'var', name: '$tmp' } }, // x = tmp (should NOT be x = y)
|
|
119
|
+
])
|
|
120
|
+
const opt = copyPropagation(fn)
|
|
121
|
+
const instrs = opt.blocks[0].instrs
|
|
122
|
+
const xAssign = instrs.find((i: any) => i.dst === '$x') as any
|
|
123
|
+
// x = tmp must NOT be optimised to x = $y (stale) or x = $r (new y).
|
|
124
|
+
// It should stay as x = $tmp (the original copy).
|
|
125
|
+
expect(xAssign.src).toEqual({ kind: 'var', name: '$tmp' })
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('still propagates simple non-conflicting copies', () => {
|
|
129
|
+
// a = 5; b = a; c = b → after propagation b and c should both be const 5
|
|
130
|
+
const fn = makeFn([
|
|
131
|
+
{ op: 'assign', dst: '$a', src: { kind: 'const', value: 5 } },
|
|
132
|
+
{ op: 'assign', dst: '$b', src: { kind: 'var', name: '$a' } },
|
|
133
|
+
{ op: 'assign', dst: '$c', src: { kind: 'var', name: '$b' } },
|
|
134
|
+
])
|
|
135
|
+
const opt = copyPropagation(fn)
|
|
136
|
+
const instrs = opt.blocks[0].instrs
|
|
137
|
+
const cAssign = instrs.find((i: any) => i.dst === '$c') as any
|
|
138
|
+
expect(cAssign.src).toEqual({ kind: 'const', value: 5 })
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
109
142
|
describe('optimize pipeline', () => {
|
|
110
143
|
it('combines all passes', () => {
|
|
111
144
|
// t0 = 2 + 3 (→ constant fold → t0 = 5)
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stdlib/advanced.mcrs — runtime behavioural tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs'
|
|
6
|
+
import * as path from 'path'
|
|
7
|
+
import { compile } from '../compile'
|
|
8
|
+
import { MCRuntime } from '../runtime'
|
|
9
|
+
|
|
10
|
+
const MATH_SRC = fs.readFileSync(path.join(__dirname, '../../src/stdlib/math.mcrs'), 'utf-8')
|
|
11
|
+
const ADV_SRC = fs.readFileSync(path.join(__dirname, '../../src/stdlib/advanced.mcrs'), 'utf-8')
|
|
12
|
+
|
|
13
|
+
function run(driver: string): MCRuntime {
|
|
14
|
+
const result = compile(driver, {
|
|
15
|
+
namespace: 'advtest',
|
|
16
|
+
librarySources: [MATH_SRC, ADV_SRC],
|
|
17
|
+
})
|
|
18
|
+
if (!result.success) throw new Error(result.error?.message ?? 'compile failed')
|
|
19
|
+
const rt = new MCRuntime('advtest')
|
|
20
|
+
for (const file of result.files ?? []) {
|
|
21
|
+
if (!file.path.endsWith('.mcfunction')) continue
|
|
22
|
+
const match = file.path.match(/data\/([^/]+)\/function\/(.+)\.mcfunction$/)
|
|
23
|
+
if (!match) continue
|
|
24
|
+
rt.loadFunction(`${match[1]}:${match[2]}`, file.content.split('\n'))
|
|
25
|
+
}
|
|
26
|
+
rt.load()
|
|
27
|
+
return rt
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function scoreOf(rt: MCRuntime, key: string): number {
|
|
31
|
+
return rt.getScore('out', `advtest.${key}`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ─── fib ─────────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
describe('fib', () => {
|
|
37
|
+
it.each([
|
|
38
|
+
[0, 0],
|
|
39
|
+
[1, 1],
|
|
40
|
+
[2, 1],
|
|
41
|
+
[5, 5],
|
|
42
|
+
[10, 55],
|
|
43
|
+
[20, 6765],
|
|
44
|
+
])('fib(%d) == %d', (n, expected) => {
|
|
45
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", fib(${n})); }`)
|
|
46
|
+
rt.execFunction('test')
|
|
47
|
+
expect(scoreOf(rt, 'r')).toBe(expected)
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// ─── is_prime ─────────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
describe('is_prime', () => {
|
|
54
|
+
it.each([
|
|
55
|
+
[0, 0], [1, 0], [2, 1], [3, 1], [4, 0],
|
|
56
|
+
[7, 1], [9, 0], [11, 1], [97, 1], [100, 0],
|
|
57
|
+
])('is_prime(%d) == %d', (n, expected) => {
|
|
58
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", is_prime(${n})); }`)
|
|
59
|
+
rt.execFunction('test')
|
|
60
|
+
expect(scoreOf(rt, 'r')).toBe(expected)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// ─── collatz_steps ───────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
describe('collatz_steps', () => {
|
|
67
|
+
it.each([
|
|
68
|
+
[1, 0],
|
|
69
|
+
[2, 1],
|
|
70
|
+
[4, 2],
|
|
71
|
+
[6, 8],
|
|
72
|
+
[27, 111],
|
|
73
|
+
])('collatz_steps(%d) == %d', (n, expected) => {
|
|
74
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", collatz_steps(${n})); }`)
|
|
75
|
+
rt.execFunction('test')
|
|
76
|
+
expect(scoreOf(rt, 'r')).toBe(expected)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// ─── digit helpers ───────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
describe('digit_sum', () => {
|
|
83
|
+
it.each([
|
|
84
|
+
[0, 0], [1, 1], [9, 9], [123, 6], [999, 27], [-42, 6],
|
|
85
|
+
])('digit_sum(%d) == %d', (n, expected) => {
|
|
86
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", digit_sum(${n})); }`)
|
|
87
|
+
rt.execFunction('test')
|
|
88
|
+
expect(scoreOf(rt, 'r')).toBe(expected)
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
describe('count_digits', () => {
|
|
93
|
+
it.each([
|
|
94
|
+
[0, 1], [9, 1], [10, 2], [100, 3], [9999, 4],
|
|
95
|
+
])('count_digits(%d) == %d', (n, expected) => {
|
|
96
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", count_digits(${n})); }`)
|
|
97
|
+
rt.execFunction('test')
|
|
98
|
+
expect(scoreOf(rt, 'r')).toBe(expected)
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe('reverse_int', () => {
|
|
103
|
+
it.each([
|
|
104
|
+
[12345, 54321],
|
|
105
|
+
[100, 1],
|
|
106
|
+
[7, 7],
|
|
107
|
+
])('reverse_int(%d) == %d', (n, expected) => {
|
|
108
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", reverse_int(${n})); }`)
|
|
109
|
+
rt.execFunction('test')
|
|
110
|
+
expect(scoreOf(rt, 'r')).toBe(expected)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// ─── mod_pow ─────────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
describe('mod_pow', () => {
|
|
117
|
+
it.each([
|
|
118
|
+
[2, 10, 1000, 24], // 2^10 = 1024, 1024 mod 1000 = 24
|
|
119
|
+
[3, 4, 100, 81], // 3^4 = 81
|
|
120
|
+
[2, 0, 10, 1], // any^0 = 1
|
|
121
|
+
[5, 1, 100, 5],
|
|
122
|
+
[7, 3, 13, 343 % 13], // 343 mod 13 = 5
|
|
123
|
+
])('mod_pow(%d,%d,%d) == %d', (b, e, m, expected) => {
|
|
124
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", mod_pow(${b},${e},${m})); }`)
|
|
125
|
+
rt.execFunction('test')
|
|
126
|
+
expect(scoreOf(rt, 'r')).toBe(expected)
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// ─── hash_int ────────────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
describe('hash_int', () => {
|
|
133
|
+
it('is deterministic', () => {
|
|
134
|
+
const rt1 = run(`fn test() { scoreboard_set("out", "r", hash_int(42)); }`)
|
|
135
|
+
rt1.execFunction('test')
|
|
136
|
+
const rt2 = run(`fn test() { scoreboard_set("out", "r", hash_int(42)); }`)
|
|
137
|
+
rt2.execFunction('test')
|
|
138
|
+
expect(scoreOf(rt1, 'r')).toBe(scoreOf(rt2, 'r'))
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('different inputs → different outputs', () => {
|
|
142
|
+
const rt = run(`fn test() {
|
|
143
|
+
scoreboard_set("out", "a", hash_int(0));
|
|
144
|
+
scoreboard_set("out", "b", hash_int(1));
|
|
145
|
+
scoreboard_set("out", "c", hash_int(1000));
|
|
146
|
+
}`)
|
|
147
|
+
rt.execFunction('test')
|
|
148
|
+
const a = scoreOf(rt, 'a')
|
|
149
|
+
const b = scoreOf(rt, 'b')
|
|
150
|
+
const c = scoreOf(rt, 'c')
|
|
151
|
+
expect(a).not.toBe(b)
|
|
152
|
+
expect(b).not.toBe(c)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('output is non-negative', () => {
|
|
156
|
+
for (const n of [-1000, -1, 0, 1, 999, 46340]) {
|
|
157
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", hash_int(${n})); }`)
|
|
158
|
+
rt.execFunction('test')
|
|
159
|
+
expect(scoreOf(rt, 'r')).toBeGreaterThanOrEqual(0)
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
// ─── noise1d ─────────────────────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
describe('noise1d', () => {
|
|
167
|
+
it('output in [0, 999]', () => {
|
|
168
|
+
for (const x of [0, 100, 500, 999, 1000, 2000]) {
|
|
169
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", noise1d(${x})); }`)
|
|
170
|
+
rt.execFunction('test')
|
|
171
|
+
const v = scoreOf(rt, 'r')
|
|
172
|
+
expect(v).toBeGreaterThanOrEqual(0)
|
|
173
|
+
expect(v).toBeLessThan(1000)
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('is deterministic', () => {
|
|
178
|
+
const rt1 = run(`fn test() { scoreboard_set("out", "r", noise1d(1234)); }`)
|
|
179
|
+
rt1.execFunction('test')
|
|
180
|
+
const rt2 = run(`fn test() { scoreboard_set("out", "r", noise1d(1234)); }`)
|
|
181
|
+
rt2.execFunction('test')
|
|
182
|
+
expect(scoreOf(rt1, 'r')).toBe(scoreOf(rt2, 'r'))
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
// ─── bezier ──────────────────────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
describe('bezier_quad', () => {
|
|
189
|
+
it.each([
|
|
190
|
+
[0, 500, 1000, 0, 0], // t=0: start
|
|
191
|
+
[0, 500, 1000, 1000, 1000], // t=1000: end
|
|
192
|
+
[0, 1000, 0, 500, 500], // arch midpoint
|
|
193
|
+
])('bezier_quad(%d,%d,%d,t=%d) == %d', (p0, p1, p2, t, expected) => {
|
|
194
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", bezier_quad(${p0},${p1},${p2},${t})); }`)
|
|
195
|
+
rt.execFunction('test')
|
|
196
|
+
expect(scoreOf(rt, 'r')).toBe(expected)
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
describe('bezier_cubic', () => {
|
|
201
|
+
it('t=0: start', () => {
|
|
202
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", bezier_cubic(0,333,667,1000,0)); }`)
|
|
203
|
+
rt.execFunction('test')
|
|
204
|
+
expect(scoreOf(rt, 'r')).toBe(0)
|
|
205
|
+
})
|
|
206
|
+
it('t=1000: end', () => {
|
|
207
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", bezier_cubic(0,333,667,1000,1000)); }`)
|
|
208
|
+
rt.execFunction('test')
|
|
209
|
+
expect(scoreOf(rt, 'r')).toBe(1000)
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
// ─── mandelbrot_iter ─────────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
describe('mandelbrot_iter', () => {
|
|
216
|
+
// c = 0 + 0i → always in set (z always 0)
|
|
217
|
+
it('origin (0,0) stays bounded for max_iter=20', () => {
|
|
218
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", mandelbrot_iter(0,0,20)); }`)
|
|
219
|
+
rt.execFunction('test')
|
|
220
|
+
expect(scoreOf(rt, 'r')).toBe(20)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
// c = 2 + 0i → escapes immediately (|z1| = 2, |z2| = 6 > 2)
|
|
224
|
+
it('c=(2000,0) escapes quickly', () => {
|
|
225
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", mandelbrot_iter(2000,0,50)); }`)
|
|
226
|
+
rt.execFunction('test')
|
|
227
|
+
expect(scoreOf(rt, 'r')).toBeLessThan(5)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
// c = -1 + 0i → stays bounded (period-2 cycle: 0 → -1 → 0 → ...)
|
|
231
|
+
it('c=(-1000,0) is in the set', () => {
|
|
232
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", mandelbrot_iter(-1000,0,50)); }`)
|
|
233
|
+
rt.execFunction('test')
|
|
234
|
+
expect(scoreOf(rt, 'r')).toBe(50)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
// c = 0.5 + 0.5i → escapes
|
|
238
|
+
it('c=(500,500) escapes before max_iter=100', () => {
|
|
239
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", mandelbrot_iter(500,500,100)); }`)
|
|
240
|
+
rt.execFunction('test')
|
|
241
|
+
expect(scoreOf(rt, 'r')).toBeLessThan(100)
|
|
242
|
+
})
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
// ─── julia_iter ──────────────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
describe('julia_iter', () => {
|
|
248
|
+
it('z0=(0,0) with c=(0,0) stays bounded', () => {
|
|
249
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", julia_iter(0,0,0,0,20)); }`)
|
|
250
|
+
rt.execFunction('test')
|
|
251
|
+
expect(scoreOf(rt, 'r')).toBe(20)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('z0=(3000,0) escapes immediately', () => {
|
|
255
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", julia_iter(3000,0,0,0,20)); }`)
|
|
256
|
+
rt.execFunction('test')
|
|
257
|
+
expect(scoreOf(rt, 'r')).toBe(0)
|
|
258
|
+
})
|
|
259
|
+
})
|