diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index eb5e7049..8cfffb25 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -1,5 +1,9 @@
## 更新记录
+#### 2026.5.13
+* Breaking: 移除未使用的 `RollConfig.ValueStoreSource` 字段,相关调用方需停止读写该配置。
+* Breaking: playground 预置人物卡变量由 `player` 改为 `actor`,旧脚本不再兼容。
+
#### 2025.10.14
* 新增自定义算符 `CustomDiceStream` 流式解析能力,可在回调中逐字符消费输入、读取表达式并携带 payload,示例与测试同步更新。
diff --git a/docs/GUIDE.md b/docs/GUIDE.md
index e7a9f0f9..ca4926c3 100644
--- a/docs/GUIDE.md
+++ b/docs/GUIDE.md
@@ -777,7 +777,7 @@ function roll(text) {
```
go mod install
go install github.com/fy0/pigeon@latest
-go install github.com/gopherjs/gopherjs@v1.18.0-beta1
+go install github.com/gopherjs/gopherjs@v1.19.0-beta2
pigeon -nolint -optimize-parser -optimize-ref-expr-by-index -o .\roll.peg.go .\roll.peg
```
@@ -788,5 +788,8 @@ go build
如果你使用JS:
```
-gopherjs build github.com/sealdice/dicescript/jsport -o jsport/dicescript.cjs
+gopherjs build -m -o jsport/dicescript.cjs github.com/sealdice/dicescript/jsport
+cd jsport
+npm install
+npm run build
```
diff --git a/jsport/index.html b/jsport/index.html
index bcd86a35..d18f7754 100644
--- a/jsport/index.html
+++ b/jsport/index.html
@@ -177,6 +177,10 @@
.highlight-layer code {
display: block;
min-height: 100%;
+ font: inherit;
+ letter-spacing: inherit;
+ font-kerning: inherit;
+ font-variant-ligatures: inherit;
}
.code-input {
@@ -605,7 +609,7 @@
提示: 还在开发中,支持语法详情看 语法指南
控制台里有上一条指令的字节码,建议多ctrl+f5以免遇到旧版
-
有一个人物卡变量叫player,通过"_测试30"这种形式可以读取出数字30,可用于ra判定
+
有一个人物卡变量叫actor,通过"_测试30"这种形式可以读取出数字30,可用于ra判定
diff --git a/jsport/main.go b/jsport/main.go
index e6feba83..d76092e0 100644
--- a/jsport/main.go
+++ b/jsport/main.go
@@ -14,11 +14,11 @@ import (
var scope = map[string]*ds.VMValue{}
func newVM(name string) *js.Object {
- player := ds.NewDictVal(nil)
- player.Store("力量", ds.NewIntVal(50))
- player.Store("敏捷", ds.NewIntVal(60))
- player.Store("智力", ds.NewIntVal(70))
- scope["player"] = player.V()
+ actor := ds.NewDictVal(nil)
+ actor.Store("力量", ds.NewIntVal(50))
+ actor.Store("敏捷", ds.NewIntVal(60))
+ actor.Store("智力", ds.NewIntVal(70))
+ scope["actor"] = actor.V()
vm := ds.NewVM()
//vm.GlobalValueStoreFunc = func(name string, v *ds.VMValue) {
@@ -33,7 +33,7 @@ func newVM(name string) *js.Object {
// return ds.VMValueNewInt(ds.IntType(val))
// }
//
- // if v, exists := player.Load(name); exists {
+ // if v, exists := actor.Load(name); exists {
// return v
// }
//
diff --git a/jsport/package.json b/jsport/package.json
index 1c626334..fd33176b 100644
--- a/jsport/package.json
+++ b/jsport/package.json
@@ -1,6 +1,6 @@
{
"name": "dicescript",
- "version": "0.1.7",
+ "version": "0.2.0",
"description": "Simple script language for TRPG dice engine.",
"type": "module",
"main": "./dist/main.mjs",
diff --git a/rollvm_callback_test.go b/rollvm_callback_test.go
index 9a791872..757d5504 100644
--- a/rollvm_callback_test.go
+++ b/rollvm_callback_test.go
@@ -42,6 +42,79 @@ func TestHookFuncValueLoadOverwrite(t *testing.T) {
}
}
+func TestHookValueStore(t *testing.T) {
+ t.Run("local overwrite", func(t *testing.T) {
+ vm := NewVM()
+ calls := 0
+ vm.Config.HookValueStore = func(ctx *Context, name string, v *VMValue) (*VMValue, bool) {
+ calls++
+ assert.Same(t, vm, ctx)
+ assert.Equal(t, "hp", name)
+ assert.True(t, valueEqual(v, ni(1)))
+ return ni(2), false
+ }
+
+ vm.StoreName("hp", ni(1), true)
+
+ actual, ok := vm.Attrs.Load("hp")
+ if assert.True(t, ok) {
+ assert.True(t, valueEqual(actual, ni(2)))
+ }
+ assert.Equal(t, 1, calls)
+ })
+
+ t.Run("global overwrite", func(t *testing.T) {
+ vm := NewVM()
+ vm.globalNames.Store("hp", ni(0))
+
+ var storedName string
+ var storedVal *VMValue
+ vm.GlobalValueStoreFunc = func(name string, v *VMValue) {
+ storedName = name
+ storedVal = v
+ }
+ vm.Config.HookValueStore = func(ctx *Context, name string, v *VMValue) (*VMValue, bool) {
+ assert.Same(t, vm, ctx)
+ assert.Equal(t, "hp", name)
+ assert.True(t, valueEqual(v, ni(1)))
+ return ni(3), false
+ }
+
+ vm.StoreName("hp", ni(1), true)
+
+ assert.Equal(t, "hp", storedName)
+ if assert.NotNil(t, storedVal) {
+ assert.True(t, valueEqual(storedVal, ni(3)))
+ }
+ _, ok := vm.Attrs.Load("hp")
+ assert.False(t, ok)
+ assert.NoError(t, vm.Error)
+ })
+
+ t.Run("solved skips remaining storage", func(t *testing.T) {
+ vm := NewVM()
+ vm.globalNames.Store("hp", ni(0))
+
+ stored := false
+ vm.GlobalValueStoreFunc = func(name string, v *VMValue) {
+ stored = true
+ }
+ vm.Config.HookValueStore = func(ctx *Context, name string, v *VMValue) (*VMValue, bool) {
+ assert.Same(t, vm, ctx)
+ assert.Equal(t, "hp", name)
+ assert.True(t, valueEqual(v, ni(1)))
+ return nil, true
+ }
+
+ vm.StoreName("hp", ni(1), true)
+
+ assert.False(t, stored)
+ _, ok := vm.Attrs.Load("hp")
+ assert.False(t, ok)
+ assert.NoError(t, vm.Error)
+ })
+}
+
func TestCustomDetailSpanRewrite(t *testing.T) {
vm := NewVM()
vm.Attrs.Store("x", ni(5))
diff --git a/types.go b/types.go
index ce3b1b15..8f0c6a7f 100644
--- a/types.go
+++ b/types.go
@@ -79,8 +79,6 @@ type RollConfig struct {
DisableStmts bool // 禁用语句语法(如if while等),仅允许表达式
DisableNDice bool // 禁用Nd语法,即只能2d6这样写,不能写2d
- ValueStoreSource string // ValueStoreSource 用于区分来源以便于 HookValueStore 的调用判断持久化方式
-
// 如果返回值为true,那么跳过剩下的储存流程。如果overwrite不为nil,对v进行覆盖。
// 另注: 钩子函数中含有ctx的原因是可能在函数中进行调用,此时ctx会发生变化
HookValueStore func(ctx *Context, name string, v *VMValue) (overwrite *VMValue, solved bool)