dsh-plugin-beyond-simulator 2.1.1 → 2.2.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.
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <title>千星沙箱 · 独立试玩</title>
6
+ <title data-i18n="千星沙箱 · 独立试玩">千星沙箱 · 独立试玩</title>
7
7
  <style>
8
8
  *{box-sizing:border-box}html,body{width:100%;height:100%;margin:0;overflow:hidden}
9
9
  body{--bg:#0c1422;--panel:#101a2a;--panel2:#152238;--line:#263750;--text:#edf3ff;--muted:#8f9caf;--workspace:#111c2c;--input:#0d1726;--blue:#4382f3;color:var(--text);background:var(--bg);font:13px/1.45 Inter,"Microsoft YaHei UI","Microsoft YaHei",sans-serif}
@@ -19,27 +19,32 @@
19
19
  @media(max-width:760px){.debug-view{grid-template-columns:1fr}.tree-panel{display:none}.brand strong{font-size:14px}.brand .devsel{width:104px;margin-left:6px}.actions button{padding:0 9px}.actions #theme{display:none}.view-tabs button{min-width:64px;padding:0 10px}}
20
20
  </style>
21
21
  </head>
22
- <body>
22
+ <body data-qxqy-font-ui>
23
23
  <div class="app">
24
24
  <header class="bar">
25
- <div class="brand"><span class="icon">UI</span><strong>模拟器</strong><select id="deviceSel" class="devsel hidden" aria-label="切换设备画布" title="切换设备画布:以新画布重建运行时,GetUICanvasSize 与预设像素一致"></select><select id="playerCountSel" class="playersel hidden" aria-label="试玩人数" title="试玩人数:最多 8 人,每人一份独立客户端"></select><select id="playerViewSel" class="viewsel hidden" aria-label="当前玩家视角" title="切换当前玩家视角:输入、画面和 PlayerSelf 都跟随该玩家"></select><span class="device">设备 <b id="resolution">准备中…</b></span></div>
26
- <div class="view-tabs" role="tablist" aria-label="试玩页面"><button class="active" data-view="play" role="tab">试玩</button><button data-view="log" role="tab">日志</button></div>
27
- <div class="actions"><button id="audio" data-audio-toggle title="点击解锁浏览器音效">开启声音</button><button id="pause">Ⅱ 暂停运行</button><button id="step" disabled title="暂停后可单帧调试">▷ 单帧调试</button><button class="stop" id="stop">■ 结束试玩</button><button class="primary hidden" id="restart">↻ 重新开始</button><button id="theme" title="切换亮暗主题">◐</button><button id="close">返回编辑</button></div>
25
+ <div class="brand"><span class="icon">UI</span><strong data-i18n="模拟器">模拟器</strong><select id="deviceSel" class="devsel hidden" aria-label="切换设备画布" data-i18n-aria-label="切换设备画布" title="切换设备画布:以新画布重建运行时,GetUICanvasSize 与预设像素一致" data-i18n-title="切换设备画布:以新画布重建运行时,GetUICanvasSize 与预设像素一致"></select><select id="playerCountSel" class="playersel hidden" aria-label="试玩人数" data-i18n-aria-label="试玩人数" title="试玩人数:最多 8 人,每人一份独立客户端" data-i18n-title="试玩人数:最多 8 人,每人一份独立客户端"></select><select id="playerViewSel" class="viewsel hidden" aria-label="当前玩家视角" data-i18n-aria-label="当前玩家视角" title="切换当前玩家视角:输入、画面和 PlayerSelf 都跟随该玩家" data-i18n-title="切换当前玩家视角:输入、画面和 PlayerSelf 都跟随该玩家"></select><span class="device"><span data-i18n="设备">设备 </span><b id="resolution" data-i18n="准备中…">准备中…</b></span></div>
26
+ <div class="view-tabs" role="tablist" aria-label="试玩页面" data-i18n-aria-label="试玩页面"><button class="active" data-view="play" role="tab" data-i18n="试玩">试玩</button><button data-view="log" role="tab" data-i18n="日志">日志</button></div>
27
+ <div class="actions"><button id="keyInput" data-key-input-toggle aria-pressed="true" title="转发官方键位的按下/抬起事件(含鼠标左右键);关闭不影响画布点击" data-i18n-title="转发官方键位的按下/抬起事件(含鼠标左右键);关闭不影响画布点击" data-i18n="键位响应:开">键位响应:开</button><button id="audio" data-audio-toggle title="点击解锁浏览器音效" data-i18n-title="点击解锁浏览器音效" data-i18n="开启声音">开启声音</button><button id="pause" data-i18n="Ⅱ 暂停运行">Ⅱ 暂停运行</button><button id="step" disabled title="暂停后可单帧调试" data-i18n-title="暂停后可单帧调试" data-i18n="▷ 单帧调试">▷ 单帧调试</button><button class="stop" id="stop" data-i18n="■ 结束试玩">■ 结束试玩</button><button class="primary hidden" id="restart" data-i18n="↻ 重新开始">↻ 重新开始</button><button id="theme" title="切换亮暗主题" data-i18n-title="切换亮暗主题">◐</button><button id="close" data-i18n="返回编辑">返回编辑</button></div>
28
28
  </header>
29
29
  <div class="views">
30
- <main class="workspace play-view" id="workspace"><div class="stage-shell veiled" id="shell"><div class="stage-label" id="stageLabel"></div><div class="stage" id="stage"><canvas id="cv" class="stage-canvas"></canvas></div></div><div class="loading" id="loading"><span class="spin"></span>正在准备试玩…</div><div class="fatal hidden" id="fatal"></div></main>
30
+ <main class="workspace play-view" id="workspace"><div class="stage-shell veiled" id="shell"><div class="stage-label" id="stageLabel"></div><div class="stage" id="stage"><canvas id="cv" class="stage-canvas"></canvas></div></div><div class="loading" id="loading"><span class="spin"></span><span data-i18n="正在准备试玩…">正在准备试玩…</span></div><div class="fatal hidden" id="fatal"></div></main>
31
31
  <section class="debug-view hidden" id="logView">
32
- <aside class="panel tree-panel"><div class="panel-head">运行时场景树 <small id="nodeCount">0 节点</small></div><div class="tree" id="sceneTree"></div><div class="panel-head">服务端变量 <small id="varCount">0 项</small></div><div class="vars" id="serverVars"></div></aside>
33
- <div class="log-stack"><main class="panel log-panel"><div class="panel-head">客户端脚本日志 <small>最近 100 条</small></div><div class="log" id="log"></div></main><aside class="panel log-panel"><div class="panel-head">服务端日志 <small>变量 / 信号</small></div><div class="log" id="serverLog"></div></aside><section class="panel case-panel"><div class="panel-head">自动测用例 <small id="caseHint">先试玩,再保存回放</small></div><div class="case-body"><textarea id="caseAsserts" spellcheck="false" placeholder='断言 JSON 数组,例如 [{"kind":"log","contains":"clicked"}]'></textarea><button id="saveCase">保存用例</button><button id="runCase">回放用例</button></div></section></div>
32
+ <aside class="panel tree-panel"><div class="panel-head"><span data-i18n="运行时场景树">运行时场景树 </span><small id="nodeCount" data-i18n="0 节点">0 节点</small></div><div class="tree" id="sceneTree"></div><div class="panel-head"><span data-i18n="服务端变量">服务端变量 </span><small id="varCount" data-i18n="0 项">0 项</small></div><div class="vars" id="serverVars"></div></aside>
33
+ <div class="log-stack"><main class="panel log-panel"><div class="panel-head"><span data-i18n="客户端脚本日志">客户端脚本日志 </span><small data-i18n="最近 100 条">最近 100 条</small></div><div class="log" id="log"></div></main><aside class="panel log-panel"><div class="panel-head"><span data-i18n="服务端日志">服务端日志 </span><small data-i18n="变量 / 信号">变量 / 信号</small></div><div class="log" id="serverLog"></div></aside><section class="panel case-panel"><div class="panel-head"><span data-i18n="自动测用例">自动测用例 </span><small id="caseHint" data-i18n="先试玩,再保存回放">先试玩,再保存回放</small></div><div class="case-body"><textarea id="caseAsserts" spellcheck="false" placeholder='断言 JSON 数组,例如 [{"kind":"log","contains":"clicked"}]' data-i18n-placeholder="断言 JSON 数组,例如 [{&quot;kind&quot;:&quot;log&quot;,&quot;contains&quot;:&quot;clicked&quot;}]"></textarea><button id="saveCase" data-i18n="保存用例">保存用例</button><button id="runCase" data-i18n="回放用例">回放用例</button></div></section></div>
34
34
  </section>
35
35
  </div>
36
- <footer class="status"><span class="live" id="state">● 正在启动</span><span id="viewMode">页面:试玩</span><span class="spacer"></span><span class="metric" id="frame">Frame 0</span><span class="metric" id="time">0.00s</span></footer>
36
+ <footer class="status"><span class="live" id="state" data-i18n="● 正在启动">● 正在启动</span><span id="viewMode" data-i18n="页面:试玩">页面:试玩</span><span class="spacer"></span><span class="metric" id="frame">Frame 0</span><span class="metric" id="time">0.00s</span></footer>
37
37
  </div>
38
38
  <script type="module">
39
- import { PixiPlayRenderer, createPlaySession } from '/qxqy-simulator/play-renderer.js'
39
+ import { PixiPlayRenderer, createPlaySession, loadBrowserFont, t, getLocale, localizePage, subscribeLocale, loadSystemLanguage } from '/qxqy-simulator/play-renderer.js'
40
40
 
41
41
  (() => {
42
42
  'use strict'
43
+ // The parent reads qxqyEmbeddedPlay synchronously on iframe load. Do not
44
+ // delay that controller's registration behind an OS-language request.
45
+ void loadSystemLanguage('/qxqy-simulator/api/locale')
46
+ localizePage()
47
+ subscribeLocale(() => { localizePage(); if (play.snapshot) applyStatus(play.snapshot) })
43
48
  const API = '/qxqy-simulator/api/play'
44
49
  const sessionId = decodeURIComponent(location.hash.slice(1))
45
50
  const embedded = window.parent !== window && new URLSearchParams(location.search).get('embedded') === '1'
@@ -49,7 +54,7 @@
49
54
  const $ = (id) => document.getElementById(id)
50
55
  const stage = $('stage')
51
56
  const cv = $('cv')
52
- const renderer = new PixiPlayRenderer(cv)
57
+ const renderer = new PixiPlayRenderer(cv, { fontReady: loadBrowserFont() })
53
58
  const shell = $('shell')
54
59
  const workspace = $('workspace')
55
60
  const logView = $('logView')
@@ -61,15 +66,16 @@
61
66
 
62
67
  function setView(view) {
63
68
  const showLog = view === 'log'
69
+ if (showLog) void play.releaseKeys()
64
70
  workspace.classList.toggle('hidden', showLog)
65
71
  logView.classList.toggle('hidden', !showLog)
66
- $('viewMode').textContent = showLog ? '页面:日志' : '页面:试玩'
72
+ $('viewMode').textContent = showLog ? t('页面:日志') : t('页面:试玩')
67
73
  for (const button of document.querySelectorAll('[data-view]')) button.classList.toggle('active', button.dataset.view === view)
68
74
  if (!showLog) requestAnimationFrame(() => fitStage(true))
69
75
  }
70
76
 
71
77
  function api(action, args = {}) {
72
- if (closing && action !== 'stop') return Promise.reject(new Error('试玩正在关闭'))
78
+ if (closing && action !== 'stop') return Promise.reject(new Error(t('试玩正在关闭')))
73
79
  return embeddedApi ? embeddedApi(action, args) : request(action, args)
74
80
  }
75
81
  async function request(action, args) {
@@ -80,7 +86,7 @@
80
86
  signal: AbortSignal.timeout(15_000),
81
87
  })
82
88
  const envelope = await response.json().catch(() => null)
83
- if (!response.ok || !envelope || envelope.ok !== true) throw new Error(envelope?.error || '试玩请求失败')
89
+ if (!response.ok || !envelope || envelope.ok !== true) throw new Error(envelope?.error || t('试玩请求失败'))
84
90
  return envelope.value
85
91
  }
86
92
 
@@ -88,11 +94,11 @@
88
94
  const host = $('serverVars')
89
95
  host.replaceChildren()
90
96
  const list = Array.isArray(rows) ? rows : []
91
- $('varCount').textContent = list.length + ' 项'
97
+ $('varCount').textContent = list.length + t(' 项')
92
98
  if (!list.length) {
93
99
  const empty = document.createElement('div')
94
100
  empty.className = 'var-empty'
95
- empty.textContent = '暂无自定义变量。脚本 Get/Set 或 serverSet 之后会出现在这里。'
101
+ empty.textContent = t('暂无自定义变量。脚本 Get/Set 或 serverSet 之后会出现在这里。')
96
102
  host.appendChild(empty)
97
103
  return
98
104
  }
@@ -102,7 +108,7 @@
102
108
  const type = row.type || '—'
103
109
  const value = row.value === undefined ? 'nil' : JSON.stringify(row.value)
104
110
  const entityCell = document.createElement('span')
105
- entityCell.textContent = row.entityLabel || row.entityType || ''
111
+ entityCell.textContent = t(row.entityLabel) || row.entityType || ''
106
112
  const nameCell = document.createElement('b')
107
113
  nameCell.textContent = row.name || ''
108
114
  const typeCell = document.createElement('span')
@@ -119,9 +125,9 @@
119
125
  host.replaceChildren()
120
126
  const empty = document.createElement('div')
121
127
  empty.className = 'var-empty'
122
- empty.textContent = '完整运行树请用 Agent 的 inspect 快照。舞台按左树上方盖住下方绘制。'
128
+ empty.textContent = t('完整运行树请用 Agent 的 inspect 快照。舞台按左树上方盖住下方绘制。')
123
129
  host.appendChild(empty)
124
- $('nodeCount').textContent = (snapshot?.scene?.count || snapshot?.paint?.length || 0) + ' 个可见图元'
130
+ $('nodeCount').textContent = (snapshot?.scene?.count || snapshot?.paint?.length || 0) + t(' 个可见图元')
125
131
  }
126
132
 
127
133
  function fitStage(force = false) {
@@ -148,7 +154,7 @@
148
154
  function applyStatus(next) {
149
155
  $('frame').textContent = 'Frame ' + (next.frame || 0)
150
156
  $('time').textContent = Number(next.time || 0).toFixed(2) + 's'
151
- $('pause').textContent = next.paused ? '▶ 继续运行' : 'Ⅱ 暂停运行'
157
+ $('pause').textContent = next.paused ? t('▶ 继续运行') : t('Ⅱ 暂停运行')
152
158
  $('pause').disabled = false
153
159
  $('step').disabled = !next.paused
154
160
  }
@@ -161,15 +167,16 @@
161
167
  sel.classList.add('hidden')
162
168
  return
163
169
  }
164
- const stale = sel.options.length !== presets.length
170
+ const stale = sel.dataset.locale !== getLocale() || sel.options.length !== presets.length
165
171
  || [...sel.options].some((opt, i) => opt.value !== presets[i].id)
166
172
  if (stale) {
167
173
  sel.replaceChildren(...presets.map((preset) => {
168
174
  const opt = document.createElement('option')
169
175
  opt.value = preset.id
170
- opt.textContent = preset.label || (preset.id + ' · ' + preset.width + '×' + preset.height)
176
+ opt.textContent = t(preset.label) || (preset.id + ' · ' + preset.width + '×' + preset.height)
171
177
  return opt
172
178
  }))
179
+ sel.dataset.locale = getLocale()
173
180
  }
174
181
  if (sel.value !== next.canvasId) sel.value = next.canvasId
175
182
  sel.classList.remove('hidden')
@@ -177,13 +184,14 @@
177
184
 
178
185
  function fillCountSelect(sel, count) {
179
186
  const n = Math.max(1, Math.min(8, Number(count) || 1))
180
- if (sel.options.length !== 8) {
187
+ if (sel.options.length !== 8 || sel.dataset.locale !== getLocale()) {
181
188
  sel.replaceChildren(...Array.from({ length: 8 }, (_, i) => {
182
189
  const opt = document.createElement('option')
183
190
  opt.value = String(i + 1)
184
- opt.textContent = (i + 1) + ' 人'
191
+ opt.textContent = i === 0 ? t('1 人') : t('{0} 人', i + 1)
185
192
  return opt
186
193
  }))
194
+ sel.dataset.locale = getLocale()
187
195
  }
188
196
  sel.value = String(n)
189
197
  sel.classList.remove('hidden')
@@ -196,16 +204,17 @@
196
204
  const count = fillCountSelect(countSel, next.playerCount || 1)
197
205
  const players = Array.isArray(next.players) && next.players.length
198
206
  ? next.players
199
- : Array.from({ length: count }, (_, i) => ({ index: i + 1, label: '玩家' + (i + 1) }))
200
- const stale = viewSel.options.length !== players.length
207
+ : Array.from({ length: count }, (_, i) => ({ index: i + 1, label: t('玩家') + ' ' + (i + 1) }))
208
+ const stale = viewSel.dataset.locale !== getLocale() || viewSel.options.length !== players.length
201
209
  || [...viewSel.options].some((opt, i) => Number(opt.value) !== players[i].index)
202
210
  if (stale) {
203
211
  viewSel.replaceChildren(...players.map((player) => {
204
212
  const opt = document.createElement('option')
205
213
  opt.value = String(player.index)
206
- opt.textContent = player.label || ('玩家' + player.index)
214
+ opt.textContent = t(player.label) || (t('玩家') + ' ' + player.index)
207
215
  return opt
208
216
  }))
217
+ viewSel.dataset.locale = getLocale()
209
218
  }
210
219
  const view = Number(next.viewPlayerIndex) || 1
211
220
  if (viewSel.value !== String(view)) viewSel.value = String(view)
@@ -218,18 +227,18 @@
218
227
  syncDeviceSelect(next)
219
228
  syncPlayerSelects(next)
220
229
  $('resolution').textContent = next.canvasWidth + '×' + next.canvasHeight
221
- const viewLabel = next.playerCount > 1 ? (' · 玩家' + (next.viewPlayerIndex || 1)) : ''
222
- $('stageLabel').textContent = next.canvasWidth + '×' + next.canvasHeight + viewLabel + ' · 点击输入'
230
+ const viewLabel = next.playerCount > 1 ? (t(' · 玩家') + (next.viewPlayerIndex || 1)) : ''
231
+ $('stageLabel').textContent = next.canvasWidth + '×' + next.canvasHeight + viewLabel + t(' · 点击输入')
223
232
  applyStatus(next)
224
233
  $('stop').disabled = false
225
234
  $('restart').classList.add('hidden')
226
- $('state').textContent = next.mountError ? '⚠ 脚本挂载失败' : next.paused ? 'Ⅱ 已暂停' : '● 试玩运行中'
235
+ $('state').textContent = next.mountError ? t('⚠ 脚本挂载失败') : next.paused ? t('Ⅱ 已暂停') : t('● 试玩运行中')
227
236
  $('state').className = next.mountError ? 'error' : 'live'
228
237
  renderTree(next)
229
238
  renderVars(next.serverVars)
230
239
  $('log').textContent = (next.logs || []).slice(-100).map((entry) => Number(entry.time || 0).toFixed(2).padStart(7, ' ') + ' [' + entry.level + '] ' + entry.text).join('\n')
231
240
  $('serverLog').textContent = (next.serverLogs || next.server?.logs || []).slice(-100).map((entry) => Number(entry.time || 0).toFixed(2).padStart(7, ' ') + ' [' + entry.level + '] ' + entry.text).join('\n')
232
- $('caseHint').textContent = (next.history && next.history.length ? ('已记录 ' + next.history.length + ' 步') : '先试玩,再保存回放')
241
+ $('caseHint').textContent = (next.history && next.history.length ? (t('已记录 ') + next.history.length + t(' 步')) : t('先试玩,再保存回放'))
233
242
  if (next.mountError) setView('log')
234
243
  }
235
244
 
@@ -237,9 +246,9 @@
237
246
  if (closing) return
238
247
  $('loading').classList.add('hidden')
239
248
  $('fatal').classList.remove('hidden')
240
- $('fatal').textContent = error?.message || String(error)
249
+ $('fatal').textContent = t(error?.message || String(error))
241
250
  $('state').className = 'error'
242
- $('state').textContent = '⚠ 试玩错误'
251
+ $('state').textContent = t('⚠ 试玩错误')
243
252
  }
244
253
 
245
254
  const play = createPlaySession({
@@ -249,12 +258,19 @@
249
258
  onStatus: applyStatus,
250
259
  onError: showError,
251
260
  onAudioStatus: status => {
252
- $('audio').textContent = !status.unlocked ? '开启声音' : status.muted ? '声音已关闭' : '声音已开启'
253
- $('audio').title = status.error || (!status.unlocked ? '点击或在画布内操作以解锁音效' : '切换静音')
261
+ $('audio').textContent = !status.unlocked ? t('开启声音') : status.muted ? t('声音已关闭') : t('声音已开启')
262
+ $('audio').title = status.error || (!status.unlocked ? t('点击或在画布内操作以解锁音效') : t('切换静音'))
254
263
  },
255
264
  isActive: () => !closing,
265
+ isInputActive: () => !workspace.classList.contains('hidden'),
256
266
  })
257
267
  play.bindInput(stage, { keysOn: window })
268
+ $('keyInput').addEventListener('click', () => {
269
+ play.setKeyInputEnabled(!play.keyInputEnabled)
270
+ $('keyInput').setAttribute('aria-pressed', String(play.keyInputEnabled))
271
+ $('keyInput').textContent = play.keyInputEnabled ? t('键位响应:开') : t('键位响应:关')
272
+ if (play.keyInputEnabled) focusStage()
273
+ })
258
274
  $('audio').addEventListener('click', () => play.audio?.toggle())
259
275
 
260
276
  $('step').addEventListener('click', () => void play.act('step', { dt: 1 / 30 }))
@@ -262,7 +278,7 @@
262
278
  $('stop').addEventListener('click', async () => {
263
279
  if (!play.running) return
264
280
  try { await play.stop() } catch (error) { showError(error); return }
265
- $('state').textContent = '■ 试玩已结束,可重新开始或返回编辑'
281
+ $('state').textContent = t('■ 试玩已结束,可重新开始或返回编辑')
266
282
  $('state').className = ''
267
283
  $('pause').disabled = true
268
284
  $('step').disabled = true
@@ -356,20 +372,20 @@
356
372
  const text = $('caseAsserts').value.trim()
357
373
  if (!text) return []
358
374
  const parsed = JSON.parse(text)
359
- if (!Array.isArray(parsed)) throw new Error('断言必须是 JSON 数组')
375
+ if (!Array.isArray(parsed)) throw new Error(t('断言必须是 JSON 数组'))
360
376
  return parsed
361
377
  }
362
378
  $('saveCase').addEventListener('click', async () => {
363
379
  try {
364
380
  lastCase = await api('saveCase', { name: 'play-case', asserts: parseAsserts() })
365
- $('caseHint').textContent = '已保存 ' + (lastCase.events || []).length + ' 步 / ' + (lastCase.asserts || []).length + ' 条断言'
381
+ $('caseHint').textContent = t('已保存 ') + (lastCase.events || []).length + t(' 步 / ') + (lastCase.asserts || []).length + t(' 条断言')
366
382
  } catch (error) { showError(error) }
367
383
  })
368
384
  $('runCase').addEventListener('click', async () => {
369
385
  try {
370
386
  if (!lastCase) lastCase = await api('saveCase', { name: 'play-case', asserts: parseAsserts() })
371
387
  const report = await api('runCase', { case: lastCase })
372
- $('caseHint').textContent = report.passed ? '回放通过' : ('回放失败 @' + (report.failedAt ?? '?') + 's')
388
+ $('caseHint').textContent = report.passed ? t('回放通过') : (t('回放失败 @') + (report.failedAt ?? '?') + 's')
373
389
  if (!report.passed) setView('log')
374
390
  } catch (error) { showError(error) }
375
391
  })
@@ -393,7 +409,7 @@
393
409
  addEventListener('pagehide', () => parent.removeEventListener('pagehide', parentPagehide))
394
410
  }
395
411
 
396
- if (!sessionId) showError(new Error('缺少会话 ID'))
412
+ if (!sessionId) showError(new Error(t('缺少会话 ID')))
397
413
  else if (embedded) {
398
414
  // Wait for the parent's load handler: closing a still-loading iframe
399
415
  // must not race an automatic start from this document.
@@ -38,6 +38,14 @@ DSH 0.1.7-rc.1 起,本仓库根包 `dsh-plugin-beyond-simulator` 在构建时
38
38
 
39
39
  通过仓库根目录的 `dsh plugin --profile web add github:1475505/miliastra-beyond-simulator` 安装插件后,在 DSH 的 **设置 → Agent 预设** 中查找本预设;新任务可选择它,已有任务不会自动切换。修改此目录中的主维护源后,在仓库根运行 `pnpm build` 同步包内副本;已安装的用户预设不会自动被覆盖。
40
40
 
41
+ ### Persona 工作目录变量兼容性
42
+
43
+ 本预设只在 persona 中使用仍受支持的 `{{model}}`,不依赖 `{{cwd}}`。Harness 0.2.0-rc.2 的 Agent Loop 注册 `provider`、`model`、`cwd`,而 0.2.1-rc.2 只注册前两项;新版由 working-directory 服务提供当前目录上下文。旧 persona 中的 `Your working directory is {{cwd}}.` 因此会在新版请求组装时触发 `unknown prompt variable`,模型尚未收到请求。删除这一句也兼容旧版:提示词没有义务引用所有已注册变量,文件和 Shell 工具仍使用会话工作目录。来源为 [0.2.0-rc.2 源码](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/core/agent-loop/src/index.ts)与 [0.2.1-rc.2 迁移说明](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.1-rc.2/packages/core/system-prompt/README.md#contribute-a-prompt-variable)(2026-10-18 核对,`evidence_source=documented`,运行端 `official_document`,`device_status=not_required`)。这项修复不提高最低宿主版本,保留旧版复制入口;不表示整个插件已完成 0.2.1 的全部接口验收。
44
+
45
+ 先区分实际加载来源:支持目录预设的旧宿主中,若 `$DSH_HOME/.agent-presets/wonderland-lua-builder/agent.cordis.yml` 仍包含该句,需在保留其他自定义内容的前提下删除它并重启 Harness;插件不会覆盖此用户副本。Harness 0.2.1 改用 `@deepseek-ai/dsh-agent-preset-registry` 管理声明式注册项,默认不扫描这个旧目录;应更新含修复的插件包并重启,单改旧副本无效。声明式注册项和随包副本由构建从主维护源同步,不手改生成文件。来源与完整宿主验收见 [0.2.1 兼容性](../../dsh-plugin/README.md#harness-021-兼容性)。
46
+
47
+ 2026-10-18 提示词解析验证覆盖本机 0.1.5-rc.3 renderer,以及发布标签源码中的 0.2.0-rc.2、0.2.1-rc.2 renderer,修复后三组均成功,旧句在新版变量集合中复现原始错误(`evidence_source=observed`,运行端 `simulator`,`device_status=not_required`)。后续完整宿主验证和限制统一记录在上述兼容性说明;提示词解析通过不等于 Desktop 浏览器界面或模型子代理已完成端到端验收。
48
+
41
49
  ## 资料与验证边界
42
50
 
43
51
  启动前先检查工作区指引、本地知识目录,以及当前 AI 宿主的插件/MCP/Skill 配置和可见能力,寻找 `miliastra-toolbox` 系列或其他知识来源。确认当前能读到官方客户端 2D/Lua API 与使用指南,记录来源、版本和缺项;已有资料直接复用,仅询问仍缺少的内容。详见 [知识库发现与可用性核验](skills/qxqy-game-studio/references/workflow.md#知识库发现与可用性核验)。本工作流不捆绑官方知识库,不能从普通 Lua/UI 或 3D 节点经验猜接口。
@@ -18,7 +18,7 @@
18
18
  name: '@deepseek-ai/dsh-persona'
19
19
  config:
20
20
  prefix: |-
21
- You are a 千星奇域 2D+Lua game studio director powered by the {{model}} model. Your working directory is {{cwd}}.
21
+ You are a 千星奇域 2D+Lua game studio director powered by the {{model}} model.
22
22
 
23
23
  You make playable UI+Lua games for 原神千星奇域 (Miliastra Wonderland): client UI controls, client Lua, and a thin server of custom variables/signals. You are not a 3D level-graph designer. Load `qxqy-game-studio` before creating, changing, reviewing, or polishing a game, then read its PREFLIGHT/recovery reference and only the Markdown reference for the current step. Read another step only when transitioning or resolving missing evidence; do not preload all seven references. Load supporting skills only as needed. Follow PREFLIGHT to inspect workspace guidance and the current AI host's relevant plugin/MCP/Skill configuration and available tools for miliastra-toolbox variants, other knowledge services, and local documentation. Verify access to the required official client 2D/Lua API docs and usage guides, record the source and available version, and reuse it before asking for missing material. Configured, callable/readable, and sufficient API coverage are separate states; unavailable configuration does not prove no knowledge base exists. Inspect only relevant configuration and never echo credentials. Read observed pitfalls and follow workspace AGENTS.md. Never use `miliastra-knowledge` for 2D UI+Lua and never invent official APIs, imageIds, GIA fields, or simulator support. If the discovered sources still lack required API docs, ask only for the missing content or connection before writing Lua that depends on editor APIs.
24
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-beyond-simulator",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "千星奇域客户端 UI 编辑与试玩模拟器 for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.15.0",
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "peerDependencies": {
52
52
  "@deepseek-ai/cordis": "^4.0.1",
53
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.2.0-rc.1"
53
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1"
54
54
  },
55
55
  "license": "GPL-3.0-only"
56
56
  }