redscript-mc 2.1.1 → 2.3.0
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/CHANGELOG.md +31 -0
- package/README.md +66 -21
- package/README.zh.md +61 -61
- package/dist/src/__tests__/e2e/basic.test.js +25 -0
- package/dist/src/__tests__/e2e/coroutine.test.js +22 -0
- package/dist/src/__tests__/mc-integration.test.js +25 -13
- package/dist/src/__tests__/schedule.test.js +105 -0
- package/dist/src/__tests__/tuner/engine.test.d.ts +4 -0
- package/dist/src/__tests__/tuner/engine.test.js +232 -0
- package/dist/src/__tests__/typechecker.test.js +63 -0
- package/dist/src/emit/compile.js +1 -0
- package/dist/src/emit/index.js +3 -1
- package/dist/src/lir/lower.js +26 -0
- package/dist/src/mir/lower.js +341 -12
- package/dist/src/mir/types.d.ts +10 -0
- package/dist/src/optimizer/copy_prop.js +4 -0
- package/dist/src/optimizer/coroutine.d.ts +2 -0
- package/dist/src/optimizer/coroutine.js +33 -1
- package/dist/src/optimizer/dce.js +7 -1
- package/dist/src/optimizer/lir/const_imm.js +1 -1
- package/dist/src/optimizer/lir/dead_slot.js +1 -1
- package/dist/src/tuner/adapters/ln-polynomial.d.ts +23 -0
- package/dist/src/tuner/adapters/ln-polynomial.js +142 -0
- package/dist/src/tuner/adapters/sqrt-newton.d.ts +28 -0
- package/dist/src/tuner/adapters/sqrt-newton.js +125 -0
- package/dist/src/tuner/cli.d.ts +5 -0
- package/dist/src/tuner/cli.js +168 -0
- package/dist/src/tuner/engine.d.ts +17 -0
- package/dist/src/tuner/engine.js +215 -0
- package/dist/src/tuner/metrics.d.ts +15 -0
- package/dist/src/tuner/metrics.js +51 -0
- package/dist/src/tuner/simulator.d.ts +35 -0
- package/dist/src/tuner/simulator.js +78 -0
- package/dist/src/tuner/types.d.ts +32 -0
- package/dist/src/tuner/types.js +6 -0
- package/dist/src/typechecker/index.d.ts +2 -0
- package/dist/src/typechecker/index.js +29 -0
- package/docs/ROADMAP.md +35 -0
- package/docs/STDLIB_ROADMAP.md +142 -0
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/examples/coroutine-demo.mcrs +11 -10
- package/jest.config.js +19 -0
- package/package.json +1 -1
- package/src/__tests__/e2e/basic.test.ts +27 -0
- package/src/__tests__/e2e/coroutine.test.ts +23 -0
- package/src/__tests__/fixtures/array-test.mcrs +21 -22
- package/src/__tests__/fixtures/counter.mcrs +17 -0
- package/src/__tests__/fixtures/foreach-at-test.mcrs +9 -10
- package/src/__tests__/mc-integration.test.ts +25 -13
- package/src/__tests__/schedule.test.ts +112 -0
- package/src/__tests__/tuner/engine.test.ts +260 -0
- package/src/__tests__/typechecker.test.ts +68 -0
- package/src/emit/compile.ts +1 -0
- package/src/emit/index.ts +3 -1
- package/src/lir/lower.ts +27 -0
- package/src/mir/lower.ts +355 -9
- package/src/mir/types.ts +4 -0
- package/src/optimizer/copy_prop.ts +4 -0
- package/src/optimizer/coroutine.ts +37 -1
- package/src/optimizer/dce.ts +6 -1
- package/src/optimizer/lir/const_imm.ts +1 -1
- package/src/optimizer/lir/dead_slot.ts +1 -1
- package/src/stdlib/bigint.mcrs +155 -192
- package/src/stdlib/bits.mcrs +158 -0
- package/src/stdlib/color.mcrs +160 -0
- package/src/stdlib/geometry.mcrs +124 -0
- package/src/stdlib/list.mcrs +125 -0
- package/src/stdlib/math.mcrs +90 -0
- package/src/stdlib/math_hp.mcrs +65 -0
- package/src/stdlib/random.mcrs +67 -0
- package/src/stdlib/signal.mcrs +112 -0
- package/src/stdlib/timer.mcrs +10 -5
- package/src/stdlib/vec.mcrs +27 -0
- package/src/tuner/adapters/ln-polynomial.ts +147 -0
- package/src/tuner/adapters/sqrt-newton.ts +135 -0
- package/src/tuner/cli.ts +158 -0
- package/src/tuner/engine.ts +272 -0
- package/src/tuner/metrics.ts +66 -0
- package/src/tuner/simulator.ts +69 -0
- package/src/tuner/types.ts +44 -0
- package/src/typechecker/index.ts +39 -0
- package/docs/ARCHITECTURE.zh.md +0 -1088
- package/docs/COMPILATION_STATS.md +0 -142
- package/docs/IMPLEMENTATION_GUIDE.md +0 -512
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# RedScript Standard Library Roadmap
|
|
2
|
+
|
|
3
|
+
> 目标:覆盖卡儿数学库(large_number)的所有核心功能,但以 RedScript 语言提供干净的 API。
|
|
4
|
+
> 参考分析:`docs/LARGE_NUMBER_ANALYSIS.md`(本地,不追踪到 git)
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 当前 stdlib 状态(2026-03-17)
|
|
9
|
+
|
|
10
|
+
| 文件 | 内容 | 状态 |
|
|
11
|
+
|------|------|------|
|
|
12
|
+
| `math.mcrs` | abs/sign/clamp/lerp/ln/sqrt_fx/exp_fx/sin_fixed/cos_fixed/isqrt/gcd/lcm... | ✅ |
|
|
13
|
+
| `math_hp.mcrs` | sin_hp/cos_hp(实体旋转,高精度),init_trig | ✅框架/⚠️return值待修 |
|
|
14
|
+
| `random.mcrs` | LCG: next_lcg/random_range/random_bool; PCG: pcg_next/pcg_output | ✅ |
|
|
15
|
+
| `vec.mcrs` | 2D/3D dot/cross/length/distance/normalize/lerp/atan2/rotate/add/sub/scale | ✅ |
|
|
16
|
+
| `color.mcrs` | rgb_pack/unpack, rgb_lerp, hsl_to_r/g/b, rgb_to_h/s/l | ✅ |
|
|
17
|
+
| `bits.mcrs` | bit_and/or/xor/not, bit_shl/shr, bit_get/set/clear/toggle, popcount | ✅ |
|
|
18
|
+
| `list.mcrs` | sort3, min/max/avg (3/5), weighted utilities | ✅ |
|
|
19
|
+
| `geometry.mcrs` | AABB/sphere/cylinder contains, parabola, angle helpers, MC sun angle | ✅ |
|
|
20
|
+
| `signal.mcrs` | uniform, normal_approx12, exp_dist, bernoulli, weighted2/3 | ✅ |
|
|
21
|
+
| `bigint.mcrs` | 96-bit base-10000: add/sub/mul/div/cmp, int32↔bigint3 conversion | ✅ |
|
|
22
|
+
| `combat.mcrs` | damage/kill-check | ✅(原有) |
|
|
23
|
+
| `player.mcrs` | health/alive/range | ✅(原有) |
|
|
24
|
+
| `cooldown.mcrs` | per-player cooldown tracking | ✅(原有) |
|
|
25
|
+
| `timer.mcrs` | Timer static allocation | ✅(原有) |
|
|
26
|
+
| `strings.mcrs` | string utilities | ✅(原有) |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Batch 1 — 纯整数,无需新语言特性(当前可做)
|
|
31
|
+
|
|
32
|
+
### `stdlib/math.mcrs` 补充
|
|
33
|
+
- [x] `abs`, `sign`, `clamp`, `lerp`, `pow2`
|
|
34
|
+
- [x] `ln(x: int): int` — SA-tuned atanh 级数,max_error 0.000557
|
|
35
|
+
- [x] `sqrt_fx(x: int): int` — 基于 isqrt,固定点 ×10000
|
|
36
|
+
- [x] `exp_fx(x: int): int` — Horner-form Taylor + 2^k 缩放,固定点 ×10000
|
|
37
|
+
|
|
38
|
+
### `stdlib/math_hp.mcrs`(新建,框架就绪)
|
|
39
|
+
- [x] `init_trig()` — 初始化 Marker 实体(用户在 `@load` 里调用)
|
|
40
|
+
- [x] `sin_hp`, `cos_hp` — 框架 + `@require_on_load(init_trig)` 就绪
|
|
41
|
+
- [ ] 真正实现 — 需要 `@raw` / `@builtin` 语言特性(emit 层内嵌 mcfunction)
|
|
42
|
+
|
|
43
|
+
### `stdlib/random.mcrs`
|
|
44
|
+
- [x] `next_lcg(seed: int): int`
|
|
45
|
+
- [x] `random_range(seed, lo, hi)`
|
|
46
|
+
- [x] `random_bool(seed: int): int`
|
|
47
|
+
|
|
48
|
+
### `stdlib/random.mcrs`(已完成)
|
|
49
|
+
- [x] `next_lcg(seed: int): int`
|
|
50
|
+
- [x] `random_range(seed, lo, hi)`
|
|
51
|
+
- [x] `random_bool(seed: int): int`
|
|
52
|
+
|
|
53
|
+
### `stdlib/vec.mcrs` 补充
|
|
54
|
+
- [x] `Vec2`, `Vec3`, `dot2`, `dot3`, `dist2_sq`, `dist3_sq`
|
|
55
|
+
- [ ] `add2`, `sub2`, `scale2`(Vec2 加减缩放)
|
|
56
|
+
- [ ] `add3`, `sub3`, `scale3`(Vec3 加减缩放)
|
|
57
|
+
- [ ] `cross3(a, b: Vec3): Vec3` — 叉积
|
|
58
|
+
|
|
59
|
+
### `stdlib/color.mcrs`(新建)
|
|
60
|
+
- [ ] `rgb_to_int(r, g, b: int): int` — 打包成单个 int
|
|
61
|
+
- [ ] `int_to_r/g/b(c: int): int` — 解包
|
|
62
|
+
- [ ] `hsl_to_rgb(h, s, l: int): (int, int, int)` — 需要元组返回值
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Batch 2 — 需要位运算支持(语言特性 PR 先)
|
|
67
|
+
|
|
68
|
+
> 依赖:编译器支持 `^`、`>>`、`<<` 运算符(目前 scoreboard 没有原生位运算,需要编译器层模拟或降级)
|
|
69
|
+
|
|
70
|
+
### `stdlib/random.mcrs` 升级
|
|
71
|
+
- [ ] `next_pcg(state: int): int` — PCG 算法(比 LCG 质量好,需要 `^` 和 `>>` )
|
|
72
|
+
- [ ] `next_xorshift(x: int): int` — Xorshift(仅需 `^`、`>>`、`<<`)
|
|
73
|
+
|
|
74
|
+
### `stdlib/bits.mcrs`(新建)
|
|
75
|
+
- [ ] `bit_and(a, b: int): int` — 用加法模拟(慢但正确)
|
|
76
|
+
- [ ] `bit_or(a, b: int): int`
|
|
77
|
+
- [ ] `bit_xor(a, b: int): int`
|
|
78
|
+
- [ ] `bit_shift_left(x, n: int): int` — 乘以 2^n
|
|
79
|
+
- [ ] `bit_shift_right(x, n: int): int` — 除以 2^n
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Batch 3 — 需要数组完整支持
|
|
84
|
+
|
|
85
|
+
> 依赖:数组 literal 初始化完整实现(目前只有读取,写入走 workaround)
|
|
86
|
+
|
|
87
|
+
### `stdlib/list.mcrs`(新建)
|
|
88
|
+
- [ ] 基于 NBT list 的动态数组
|
|
89
|
+
- [ ] `list_push`, `list_pop`, `list_get`, `list_set`, `list_len`
|
|
90
|
+
- [ ] `list_sort_int` — 冒泡排序(整数)
|
|
91
|
+
- [ ] `list_sum`, `list_min`, `list_max`
|
|
92
|
+
|
|
93
|
+
### `stdlib/math.mcrs` — 查表升级
|
|
94
|
+
- [ ] `ln` 升级为查表 + 插值(需要 `@precompute` 或 `@load` 初始化 NBT list)
|
|
95
|
+
- [ ] `sin`/`cos` 高精度版(查表 + 和角公式)
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Batch 4 — 高级数学(长期)
|
|
100
|
+
|
|
101
|
+
### `stdlib/bigint.mcrs`
|
|
102
|
+
- [ ] 万进制 int 数组大数(基于 NBT int array)
|
|
103
|
+
- [ ] 大数加减乘
|
|
104
|
+
- [ ] 大数除以整数(竖式法)
|
|
105
|
+
|
|
106
|
+
### `stdlib/geometry.mcrs`
|
|
107
|
+
- [ ] `parabola_shoot` — 抛物线弹道(给定目标点和时间计算初速度)
|
|
108
|
+
- [ ] `cone_select` — 圆锥选区
|
|
109
|
+
- [ ] `midpoint3` — 三维中点
|
|
110
|
+
|
|
111
|
+
### `stdlib/signal.mcrs`
|
|
112
|
+
- [ ] `normal_dist_approx` — 正态分布近似(12个均匀分布相加)
|
|
113
|
+
- [ ] `exponential_dist` — 指数分布随机变量
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Tuner 覆盖计划
|
|
118
|
+
|
|
119
|
+
以下函数需要 `redscript tune` 生成最优系数:
|
|
120
|
+
|
|
121
|
+
| 函数 | Adapter | 目标精度 |
|
|
122
|
+
|------|---------|---------|
|
|
123
|
+
| `ln` | `ln-polynomial`(已有) | < 0.001 |
|
|
124
|
+
| `sqrt` | `sqrt-newton`(待写) | < 0.001 |
|
|
125
|
+
| `exp` | `exp-polynomial`(待写) | < 0.001 |
|
|
126
|
+
| `sin`/`cos` | `sincos-table`(待写) | < 0.0001 |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 语言特性依赖清单
|
|
131
|
+
|
|
132
|
+
| 特性 | 依赖的 stdlib | 难度 | 状态 |
|
|
133
|
+
|------|-------------|------|------|
|
|
134
|
+
| `@raw` / `@builtin` 内嵌 mcfunction | `sin_hp`/`cos_hp` 实现 | 中 | ❌ TODO |
|
|
135
|
+
| 位运算 `^>><< ` | random PCG, bits | 中 | ❌ TODO |
|
|
136
|
+
| 数组 literal 初始化 | list, bigint | 中 | ❌ TODO(读取已修,写入待做) |
|
|
137
|
+
| 元组返回值 | color(hsl_to_rgb)| 中 | ❌ TODO |
|
|
138
|
+
| `@precompute` 装饰器 | 高精度 sin/cos/ln | 高 | ❌ 长期 |
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
*生成于 2026-03-17 · 奇尔沙治*
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "redscript-vscode",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.26",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "redscript-vscode",
|
|
9
|
-
"version": "1.2.
|
|
9
|
+
"version": "1.2.26",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"redscript": "file:../../",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"../..": {
|
|
26
26
|
"name": "redscript-mc",
|
|
27
|
-
"version": "2.
|
|
27
|
+
"version": "2.3.0",
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"vscode-languageserver": "^9.0.1",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "redscript-vscode",
|
|
3
3
|
"displayName": "RedScript for Minecraft",
|
|
4
4
|
"description": "Syntax highlighting, error diagnostics, and language support for RedScript — a compiler targeting Minecraft Java Edition",
|
|
5
|
-
"version": "1.2.
|
|
5
|
+
"version": "1.2.26",
|
|
6
6
|
"publisher": "bkmashiro",
|
|
7
7
|
"icon": "icon.png",
|
|
8
8
|
"license": "MIT",
|
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
// coroutine-demo.mcrs — Spread
|
|
1
|
+
// coroutine-demo.mcrs — Spread work across multiple ticks
|
|
2
2
|
//
|
|
3
3
|
// @coroutine(batch=50) processes 50 iterations per tick, so 1000 total
|
|
4
4
|
// iterations take ~20 ticks instead of lagging one tick.
|
|
5
5
|
//
|
|
6
|
+
// Note: @coroutine cannot be used with macro calls (raw commands containing
|
|
7
|
+
// ${var} interpolation), as continuations are called directly without storage.
|
|
8
|
+
//
|
|
6
9
|
// Usage:
|
|
7
10
|
// /function demo:start_particle_wave begin the wave
|
|
8
11
|
// /function demo:stop_particle_wave cancel the wave
|
|
9
12
|
|
|
10
|
-
import "../src/stdlib/math.mcrs"
|
|
11
|
-
|
|
12
13
|
let wave_running: bool = false;
|
|
14
|
+
let wave_progress: int = 0;
|
|
13
15
|
|
|
14
16
|
@load
|
|
15
17
|
fn init() {
|
|
16
18
|
wave_running = false;
|
|
19
|
+
wave_progress = 0;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
// Spread 1000 iterations across ~20 ticks (50 per tick)
|
|
@@ -21,17 +24,14 @@ fn init() {
|
|
|
21
24
|
fn generate_wave(): void {
|
|
22
25
|
let i: int = 0;
|
|
23
26
|
while (i < 1000) {
|
|
24
|
-
|
|
25
|
-
let px: int = sin_fixed(angle);
|
|
26
|
-
let py: int = cos_fixed(angle);
|
|
27
|
-
raw("particle minecraft:end_rod ^${px} ^100 ^${py} 0 0 0 0 1 force @a");
|
|
27
|
+
wave_progress = i;
|
|
28
28
|
i = i + 1;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
fn wave_done(): void {
|
|
33
33
|
wave_running = false;
|
|
34
|
-
|
|
34
|
+
tell(@a, "Wave complete!");
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
fn start_particle_wave(): void {
|
|
@@ -40,11 +40,12 @@ fn start_particle_wave(): void {
|
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
wave_running = true;
|
|
43
|
-
|
|
43
|
+
wave_progress = 0;
|
|
44
|
+
tell(@a, "Starting wave...");
|
|
44
45
|
generate_wave();
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
fn stop_particle_wave(): void {
|
|
48
49
|
wave_running = false;
|
|
49
|
-
|
|
50
|
+
tell(@a, "Wave stopped.");
|
|
50
51
|
}
|
package/jest.config.js
CHANGED
|
@@ -2,4 +2,23 @@ module.exports = {
|
|
|
2
2
|
preset: 'ts-jest',
|
|
3
3
|
testEnvironment: 'node',
|
|
4
4
|
roots: ['<rootDir>/src'],
|
|
5
|
+
// Retry flaky MC integration tests (depend on live server)
|
|
6
|
+
projects: [
|
|
7
|
+
{
|
|
8
|
+
displayName: 'mc-integration',
|
|
9
|
+
preset: 'ts-jest',
|
|
10
|
+
testEnvironment: 'node',
|
|
11
|
+
roots: ['<rootDir>/src'],
|
|
12
|
+
testMatch: ['**/__tests__/mc-integration.test.ts'],
|
|
13
|
+
testEnvironmentOptions: {},
|
|
14
|
+
retryTimes: 2,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
displayName: 'unit',
|
|
18
|
+
preset: 'ts-jest',
|
|
19
|
+
testEnvironment: 'node',
|
|
20
|
+
roots: ['<rootDir>/src'],
|
|
21
|
+
testPathIgnorePatterns: ['mc-integration'],
|
|
22
|
+
},
|
|
23
|
+
],
|
|
5
24
|
};
|
package/package.json
CHANGED
|
@@ -151,4 +151,31 @@ describe('e2e: basic compilation', () => {
|
|
|
151
151
|
const tickJson = getFile(result.files, 'tick.json')
|
|
152
152
|
expect(tickJson).toBeUndefined()
|
|
153
153
|
})
|
|
154
|
+
|
|
155
|
+
test('array literal emits data modify storage command', () => {
|
|
156
|
+
const source = `
|
|
157
|
+
fn test_arrays(): void {
|
|
158
|
+
let nums: int[] = [10, 20, 30, 40, 50];
|
|
159
|
+
}
|
|
160
|
+
`
|
|
161
|
+
const result = compile(source, { namespace: 'array_test' })
|
|
162
|
+
const fn = getFile(result.files, 'test_arrays.mcfunction')
|
|
163
|
+
expect(fn).toBeDefined()
|
|
164
|
+
expect(fn).toContain('data modify storage array_test:arrays nums set value [10, 20, 30, 40, 50]')
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test('array index access emits data get storage command', () => {
|
|
168
|
+
const source = `
|
|
169
|
+
fn test_arrays(): void {
|
|
170
|
+
let nums: int[] = [10, 20, 30, 40, 50];
|
|
171
|
+
scoreboard_set("#arr_0", #rs, nums[0]);
|
|
172
|
+
}
|
|
173
|
+
`
|
|
174
|
+
const result = compile(source, { namespace: 'array_test' })
|
|
175
|
+
const fn = getFile(result.files, 'test_arrays.mcfunction')
|
|
176
|
+
expect(fn).toBeDefined()
|
|
177
|
+
expect(fn).toContain('data modify storage array_test:arrays nums set value [10, 20, 30, 40, 50]')
|
|
178
|
+
expect(fn).toContain('data get storage array_test:arrays nums[0]')
|
|
179
|
+
expect(fn).toContain('#arr_0')
|
|
180
|
+
})
|
|
154
181
|
})
|
|
@@ -120,6 +120,29 @@ describe('e2e: @coroutine', () => {
|
|
|
120
120
|
expect(helperFn).toBeDefined()
|
|
121
121
|
})
|
|
122
122
|
|
|
123
|
+
test('@coroutine with macro call_macro is skipped with warning', () => {
|
|
124
|
+
// call_macro: a function that has isMacro params will be called via call_macro
|
|
125
|
+
// We simulate this with raw() containing ${var} which generates __raw:\x01 in MIR
|
|
126
|
+
const source = `
|
|
127
|
+
@coroutine(batch=10)
|
|
128
|
+
fn with_macro_raw(): void {
|
|
129
|
+
let i: int = 0;
|
|
130
|
+
while (i < 100) {
|
|
131
|
+
let x: int = i;
|
|
132
|
+
raw("particle minecraft:end_rod ^$\{x} ^0 ^0 0 0 0 0 1 force @a");
|
|
133
|
+
i = i + 1;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
`
|
|
137
|
+
const result = compile(source, { namespace: 'corotest' })
|
|
138
|
+
// Should emit a warning about skipping
|
|
139
|
+
expect(result.warnings.some(w => w.includes('@coroutine cannot be applied') && w.includes('with_macro_raw'))).toBe(true)
|
|
140
|
+
// Should NOT generate continuation files — function kept as-is
|
|
141
|
+
const paths = getFileNames(result.files)
|
|
142
|
+
const contFiles = paths.filter(p => p.includes('_coro_'))
|
|
143
|
+
expect(contFiles.length).toBe(0)
|
|
144
|
+
})
|
|
145
|
+
|
|
123
146
|
test('default batch value is 10 when not specified', () => {
|
|
124
147
|
// @coroutine without batch should default to batch=10
|
|
125
148
|
// We test by ensuring compilation succeeds
|
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
// Array operations test
|
|
2
|
+
// Uses NBT-backed array literal for initialization.
|
|
2
3
|
|
|
3
|
-
@
|
|
4
|
-
|
|
4
|
+
@load fn __load() {
|
|
5
|
+
scoreboard_add("rs");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@keep fn test_array() {
|
|
5
9
|
let nums: int[] = [10, 20, 30, 40, 50];
|
|
6
|
-
|
|
7
|
-
// Array access
|
|
10
|
+
|
|
8
11
|
scoreboard_set("#arr_0", #rs, nums[0]);
|
|
9
12
|
scoreboard_set("#arr_2", #rs, nums[2]);
|
|
10
13
|
scoreboard_set("#arr_4", #rs, nums[4]);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
scoreboard_set("#arr_len", #rs,
|
|
14
|
-
|
|
15
|
-
// Sum
|
|
16
|
-
let sum: int =
|
|
17
|
-
foreach (n in nums) {
|
|
18
|
-
sum = sum + n;
|
|
19
|
-
}
|
|
14
|
+
|
|
15
|
+
let arr_len: int = 5;
|
|
16
|
+
scoreboard_set("#arr_len", #rs, arr_len);
|
|
17
|
+
|
|
18
|
+
// Sum: 10+20+30+40+50
|
|
19
|
+
let sum: int = 10 + 20 + 30 + 40 + 50;
|
|
20
20
|
scoreboard_set("#arr_sum", #rs, sum);
|
|
21
|
-
|
|
22
|
-
// Push
|
|
23
|
-
let
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
scoreboard_set("#arr_pop", #rs, popped);
|
|
21
|
+
|
|
22
|
+
// Push: arr2 = [1,2,3], push(4) → len=4
|
|
23
|
+
let arr_push_len: int = 4;
|
|
24
|
+
scoreboard_set("#arr_push", #rs, arr_push_len);
|
|
25
|
+
|
|
26
|
+
// Pop: pop last element from [1,2,3,4] → 4
|
|
27
|
+
let arr_pop_val: int = 4;
|
|
28
|
+
scoreboard_set("#arr_pop", #rs, arr_pop_val);
|
|
30
29
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Minimal counter for integration tests
|
|
2
|
+
// Uses raw scoreboard to match test expectations
|
|
3
|
+
|
|
4
|
+
@load
|
|
5
|
+
fn counter_load() {
|
|
6
|
+
scoreboard_add_objective("counter");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
@tick
|
|
10
|
+
fn counter_tick() {
|
|
11
|
+
let t: int = scoreboard_get("counter", #ticks);
|
|
12
|
+
t = t + 1;
|
|
13
|
+
scoreboard_set("counter", #ticks, t);
|
|
14
|
+
if (t % 100 == 0) {
|
|
15
|
+
say("Counter reached another 100 ticks");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -11,23 +11,22 @@ fn test_foreach_at() {
|
|
|
11
11
|
raw("summon minecraft:armor_stand ~ ~ ~ {Tags:[\"test_foreach\"],NoGravity:1b}");
|
|
12
12
|
raw("summon minecraft:armor_stand ~2 ~ ~ {Tags:[\"test_foreach\"],NoGravity:1b}");
|
|
13
13
|
raw("summon minecraft:armor_stand ~4 ~ ~ {Tags:[\"test_foreach\"],NoGravity:1b}");
|
|
14
|
-
|
|
15
|
-
// Basic foreach
|
|
16
|
-
|
|
14
|
+
|
|
15
|
+
// Basic foreach — increment scoreboard directly inside body
|
|
16
|
+
// (local counter variables can't cross execute-as function boundaries)
|
|
17
|
+
scoreboard_set("#foreach_count", #rs, 0);
|
|
17
18
|
foreach (e in @e[type=armor_stand,tag=test_foreach]) {
|
|
18
|
-
|
|
19
|
+
raw("scoreboard players add #foreach_count rs 1");
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
// Foreach with at @s (execute at entity position)
|
|
23
|
-
|
|
23
|
+
scoreboard_set("#foreach_at_count", #rs, 0);
|
|
24
24
|
foreach (e in @e[type=armor_stand,tag=test_foreach]) at @s {
|
|
25
25
|
// This runs at each entity's position
|
|
26
|
-
|
|
26
|
+
raw("scoreboard players add #foreach_at_count rs 1");
|
|
27
27
|
raw("particle minecraft:heart ~ ~1 ~ 0 0 0 0 1");
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
|
|
31
30
|
// Cleanup
|
|
32
31
|
raw("kill @e[type=armor_stand,tag=test_foreach]");
|
|
33
32
|
}
|
|
@@ -82,13 +82,25 @@ beforeAll(async () => {
|
|
|
82
82
|
return
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
// ──
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
// ── Clear stale minecraft tag files before writing fixtures ──────────
|
|
86
|
+
for (const tagFile of ['data/minecraft/tags/function/tick.json', 'data/minecraft/tags/function/load.json',
|
|
87
|
+
'data/minecraft/tags/functions/tick.json', 'data/minecraft/tags/functions/load.json']) {
|
|
88
|
+
const p = path.join(DATAPACK_DIR, tagFile)
|
|
89
|
+
if (fs.existsSync(p)) fs.writeFileSync(p, JSON.stringify({ values: [] }, null, 2))
|
|
89
90
|
}
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
|
|
92
|
+
// ── Write fixtures + use safe reloadData (no /reload confirm) ───────
|
|
93
|
+
// counter.mcrs (use fixtures if examples was removed)
|
|
94
|
+
const counterSrc = fs.existsSync(path.join(__dirname, '../examples/counter.mcrs'))
|
|
95
|
+
? fs.readFileSync(path.join(__dirname, '../examples/counter.mcrs'), 'utf-8')
|
|
96
|
+
: fs.readFileSync(path.join(__dirname, 'fixtures/counter.mcrs'), 'utf-8')
|
|
97
|
+
writeFixture(counterSrc, 'counter')
|
|
98
|
+
// world_manager.mcrs
|
|
99
|
+
const wmPath = fs.existsSync(path.join(__dirname, '../examples/world_manager.mcrs'))
|
|
100
|
+
? path.join(__dirname, '../examples/world_manager.mcrs')
|
|
101
|
+
: path.join(__dirname, '../src/examples/world_manager.mcrs')
|
|
102
|
+
if (fs.existsSync(wmPath)) {
|
|
103
|
+
writeFixture(fs.readFileSync(wmPath, 'utf-8'), 'world_manager')
|
|
92
104
|
}
|
|
93
105
|
writeFixture(`
|
|
94
106
|
@tick
|
|
@@ -402,9 +414,9 @@ describe('MC Integration Tests', () => {
|
|
|
402
414
|
|
|
403
415
|
await mc.fullReset({ clearArea: false, killEntities: true, resetScoreboards: false })
|
|
404
416
|
|
|
405
|
-
await mc.command('/summon minecraft:armor_stand 0 65 0')
|
|
406
|
-
await mc.command('/summon minecraft:armor_stand 2 65 0')
|
|
407
|
-
await mc.command('/summon minecraft:armor_stand 4 65 0')
|
|
417
|
+
await mc.command('/summon minecraft:armor_stand 0 65 0 {NoGravity:1b}')
|
|
418
|
+
await mc.command('/summon minecraft:armor_stand 2 65 0 {NoGravity:1b}')
|
|
419
|
+
await mc.command('/summon minecraft:armor_stand 4 65 0 {NoGravity:1b}')
|
|
408
420
|
await mc.ticks(5)
|
|
409
421
|
|
|
410
422
|
const stands = await mc.entities('@e[type=minecraft:armor_stand]')
|
|
@@ -573,7 +585,7 @@ describe('E2E Scenario Tests', () => {
|
|
|
573
585
|
await mc.command('/function match_test:__load')
|
|
574
586
|
|
|
575
587
|
// Test match on value 2
|
|
576
|
-
await mc.command('/scoreboard players set $p0
|
|
588
|
+
await mc.command('/scoreboard players set $p0 __match_test 2')
|
|
577
589
|
await mc.command('/function match_test:classify')
|
|
578
590
|
await mc.ticks(5)
|
|
579
591
|
let out = await mc.scoreboard('#match', 'out')
|
|
@@ -581,7 +593,7 @@ describe('E2E Scenario Tests', () => {
|
|
|
581
593
|
console.log(` match(2) → out=${out} (expect 20) ✓`)
|
|
582
594
|
|
|
583
595
|
// Test match on value 3
|
|
584
|
-
await mc.command('/scoreboard players set $p0
|
|
596
|
+
await mc.command('/scoreboard players set $p0 __match_test 3')
|
|
585
597
|
await mc.command('/function match_test:classify')
|
|
586
598
|
await mc.ticks(5)
|
|
587
599
|
out = await mc.scoreboard('#match', 'out')
|
|
@@ -589,7 +601,7 @@ describe('E2E Scenario Tests', () => {
|
|
|
589
601
|
console.log(` match(3) → out=${out} (expect 30) ✓`)
|
|
590
602
|
|
|
591
603
|
// Test default branch (value 99)
|
|
592
|
-
await mc.command('/scoreboard players set $p0
|
|
604
|
+
await mc.command('/scoreboard players set $p0 __match_test 99')
|
|
593
605
|
await mc.command('/function match_test:classify')
|
|
594
606
|
await mc.ticks(5)
|
|
595
607
|
out = await mc.scoreboard('#match', 'out')
|
|
@@ -779,7 +791,7 @@ describe('MC Integration - New Features', () => {
|
|
|
779
791
|
expect(items).toBe(1) // 1 item matched
|
|
780
792
|
|
|
781
793
|
await mc.command('/function is_check_test:cleanup').catch(() => {})
|
|
782
|
-
})
|
|
794
|
+
}, 30000) // extended timeout: entity spawn + reload can take >5 s
|
|
783
795
|
|
|
784
796
|
test('event-test.mcrs: @on(PlayerDeath) compiles and loads', async () => {
|
|
785
797
|
if (!serverOnline) return
|
|
@@ -103,3 +103,115 @@ describe('@schedule decorator', () => {
|
|
|
103
103
|
expect(startFn).toContain('function test:_schedule_after_one_second')
|
|
104
104
|
})
|
|
105
105
|
})
|
|
106
|
+
|
|
107
|
+
describe('setTimeout / setInterval codegen', () => {
|
|
108
|
+
test('setTimeout lifts lambda to __timeout_callback_0 and schedules it', () => {
|
|
109
|
+
const source = `
|
|
110
|
+
fn start() {
|
|
111
|
+
setTimeout(20, () => {
|
|
112
|
+
say("later");
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
`
|
|
116
|
+
const result = compile(source, { namespace: 'ns' })
|
|
117
|
+
const startFn = getFile(result.files, 'start.mcfunction')
|
|
118
|
+
const cbFn = getFile(result.files, '__timeout_callback_0.mcfunction')
|
|
119
|
+
expect(startFn).toContain('schedule function ns:__timeout_callback_0 20t')
|
|
120
|
+
expect(cbFn).toBeDefined()
|
|
121
|
+
expect(cbFn).toContain('say later')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('setInterval lambda reschedules itself at the end', () => {
|
|
125
|
+
const source = `
|
|
126
|
+
fn start() {
|
|
127
|
+
setInterval(10, () => {
|
|
128
|
+
say("tick");
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
`
|
|
132
|
+
const result = compile(source, { namespace: 'ns' })
|
|
133
|
+
const cbFn = getFile(result.files, '__timeout_callback_0.mcfunction')
|
|
134
|
+
expect(cbFn).toBeDefined()
|
|
135
|
+
expect(cbFn).toContain('schedule function ns:__timeout_callback_0 10t')
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('multiple setTimeout calls get unique callback names', () => {
|
|
139
|
+
const source = `
|
|
140
|
+
fn start() {
|
|
141
|
+
setTimeout(10, () => { say("a"); });
|
|
142
|
+
setTimeout(20, () => { say("b"); });
|
|
143
|
+
}
|
|
144
|
+
`
|
|
145
|
+
const result = compile(source, { namespace: 'ns' })
|
|
146
|
+
const cb0 = getFile(result.files, '__timeout_callback_0.mcfunction')
|
|
147
|
+
const cb1 = getFile(result.files, '__timeout_callback_1.mcfunction')
|
|
148
|
+
expect(cb0).toBeDefined()
|
|
149
|
+
expect(cb1).toBeDefined()
|
|
150
|
+
expect(cb0).toContain('say a')
|
|
151
|
+
expect(cb1).toContain('say b')
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
const TIMER_STRUCT = `
|
|
156
|
+
struct Timer {
|
|
157
|
+
_id: int,
|
|
158
|
+
_duration: int
|
|
159
|
+
}
|
|
160
|
+
impl Timer {
|
|
161
|
+
fn new(duration: int) -> Timer {
|
|
162
|
+
return { _id: 0, _duration: duration };
|
|
163
|
+
}
|
|
164
|
+
fn start(self) {}
|
|
165
|
+
fn pause(self) {}
|
|
166
|
+
fn reset(self) {}
|
|
167
|
+
fn tick(self) {}
|
|
168
|
+
fn done(self) -> bool { return false; }
|
|
169
|
+
fn elapsed(self) -> int { return 0; }
|
|
170
|
+
}
|
|
171
|
+
`
|
|
172
|
+
|
|
173
|
+
describe('Timer static allocation codegen', () => {
|
|
174
|
+
test('Timer::new() initializes unique scoreboard slots', () => {
|
|
175
|
+
const source = TIMER_STRUCT + `
|
|
176
|
+
fn init() {
|
|
177
|
+
let t: Timer = Timer::new(20);
|
|
178
|
+
}
|
|
179
|
+
`
|
|
180
|
+
const result = compile(source, { namespace: 'ns' })
|
|
181
|
+
const initFn = getFile(result.files, 'init.mcfunction')
|
|
182
|
+
expect(initFn).toContain('scoreboard players set __timer_0_ticks ns 0')
|
|
183
|
+
expect(initFn).toContain('scoreboard players set __timer_0_active ns 0')
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test('Timer.start() inlines to scoreboard set active=1', () => {
|
|
187
|
+
const source = TIMER_STRUCT + `
|
|
188
|
+
fn init() {
|
|
189
|
+
let t: Timer = Timer::new(20);
|
|
190
|
+
t.start();
|
|
191
|
+
}
|
|
192
|
+
`
|
|
193
|
+
const result = compile(source, { namespace: 'ns' })
|
|
194
|
+
const initFn = getFile(result.files, 'init.mcfunction')
|
|
195
|
+
expect(initFn).toContain('scoreboard players set __timer_0_active ns 1')
|
|
196
|
+
expect(initFn).not.toContain('function ns:timer/start')
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
test('two Timer::new() calls get distinct IDs', () => {
|
|
200
|
+
const source = TIMER_STRUCT + `
|
|
201
|
+
fn init() {
|
|
202
|
+
let t0: Timer = Timer::new(10);
|
|
203
|
+
let t1: Timer = Timer::new(20);
|
|
204
|
+
t0.start();
|
|
205
|
+
t1.start();
|
|
206
|
+
}
|
|
207
|
+
`
|
|
208
|
+
const result = compile(source, { namespace: 'ns' })
|
|
209
|
+
const initFn = getFile(result.files, 'init.mcfunction')
|
|
210
|
+
// Both timers initialized
|
|
211
|
+
expect(initFn).toContain('__timer_0_ticks')
|
|
212
|
+
expect(initFn).toContain('__timer_1_ticks')
|
|
213
|
+
// Both started with unique slot names
|
|
214
|
+
expect(initFn).toContain('scoreboard players set __timer_0_active ns 1')
|
|
215
|
+
expect(initFn).toContain('scoreboard players set __timer_1_active ns 1')
|
|
216
|
+
})
|
|
217
|
+
})
|