feat(cheap-gen): 阶段一B #10 juice 加 burst(x,y) 便利方法(向母语直觉)
附A #10:AI 母语直觉是 juice.burst(x,y),旧 API 只有 spawnEmitter('burst',x,y)、burst 仅预设名
——prompt 原本教"particles-juice 没有 burst"当幻觉拦。加 burst(x,y,overrides?) 便利方法委托
spawnEmitter('burst',...) 后,直觉名直接可用。全链路同改:
- particles-juice/impl.js:加 burst(x,y,overrides) 委托 plugin.spawnEmitter('burst',...)(引 plugin 自身不依赖 this)
- particles-juice/api.d.ts:实例接口加 burst 声明
- prompt.mjs + cheap_roles.py 双源:删"particles-juice 没有 burst"(它现在有了)
- skill line116:spawnEmitter 旁注 burst(x,y) 母语别名
验证:particles-juice 18/18(含 burst 别名 spec 等价测试)+ 确定性 burst≡spawnEmitter('burst')
+ [check] PASS(juice.burst 被 API 门认可)+ 双源一致。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8c361f4a5b
commit
bdcd7d472c
@ -113,7 +113,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
| **collision** | 碰撞查询(纯数学) | `circleVsCircle/circleVsAabb/aabbVsAabb/satVsPolygon`→`{hit,depth,nx,ny}`;`raycast*`;`SpatialHash`。**⚠️ Aabb=中心+半宽半高 `{x,y,hw,hh}`(非 {x,y,w,h});Circle `{x,y,r}`** |
|
||||
| **physics-lite** | 街机运动原语(非刚体) | `Projectile{step,reset}`/`integrateProjectile(state,dt,opts)`;`springStep`/`criticalDamping`;`moveWithConstraint`;`clampSpeed`/`applyFriction`。**⚠️ ProjectileState=`{px,py,vx,vy}`(位置是 px/py 非 x/y);重力经 opts.gravity={x,y}`** |
|
||||
| **gamefeel** | 手感 | `easing`(13 曲线 in/out/inOut);`InputBuffer{consume,peek}`;`CoyoteTimer{setGrounded,isAvailable,consume}`;`ComboWindow{hit,getCount}` |
|
||||
| **particles-juice** | 粒子 + 打击感 | `spawnEmitter(preset\|cfg,x,y)`;`hitStop(dur)`/`getTimeScale`;`shakeScreen`/`getShakeOffset`;`flashScreen`/`pulseScale`;`PRESETS`(burst/trail/drift)。**⚠️ `render(g)` 必须每帧手调(非自动);`step(dt)` 默认 autoStep 自驱(经 onFrame),要手调 step 须工厂传 `autoStep:false` 防双步进** |
|
||||
| **particles-juice** | 粒子 + 打击感 | `spawnEmitter(preset\|cfg,x,y)`(或 `burst(x,y)` 直发 burst 预设,#6/#10 母语别名);`hitStop(dur)`/`getTimeScale`;`shakeScreen`/`getShakeOffset`;`flashScreen`/`pulseScale`;`PRESETS`(burst/trail/drift)。**⚠️ `render(g)` 必须每帧手调(非自动);`step(dt)` 默认 autoStep 自驱(经 onFrame),要手调 step 须工厂传 `autoStep:false` 防双步进** |
|
||||
| **palette-post** | 调色 + 后处理 | `rgbToHsl/hslToRgb/applyPaletteMap/shiftHsl`;`setPost/renderPost`(vignette/dither/scanline)。**色板由你注入,插件不内置成品色** |
|
||||
| **audio-music** | 程序化音频 | `loadSong/play/stop/loop`;`setIntensity`/`setIntensityLayering`;`playSfx('blip'\|'thud'\|'chime')` |
|
||||
| **save-progress** | KV 持久化 | `get(key,default)/set/remove/clear`;`createMemoryAdapter`/`createLocalStorageAdapter` |
|
||||
|
||||
@ -46,7 +46,7 @@ A 读手册(1)+ 读你的起点 game-logic.js(4)→ B 按 brief 定玩
|
||||
- **入参是扁平的 { plugins, bundle, viewport }——没有 opts、没有 opts.runtime、没有 ctx**(L1 已替你摊平)。受控面 ctx 只在五法 **init(boot) 的 boot.ctx**;**调用形态钉死**:时间 **ctx.time.nowMs()**、随机 **ctx.random.next()/ctx.random.range(a,b)**、画布 ctx.getContext2d()、引擎 ctx.getEngine()、日志 ctx.log(tag,msg)。**time/random 是对象、不是函数——绝不写 ctx.nowMs()/ctx.random()**(实测翻车:当函数调 → 抛错 → 游戏空转不 spawn/不计分)。五法 = init(boot)/update(dt)/render(g)/destroy()(+必须 _forensicsView)+ **输入方法 handleTap(x,y)**(见下「输入契约」)。
|
||||
- **插件扁平注入:直接 const { sceneFsm, sessionScore, hudUi, timerScheduler } = plugins(用到才解构)**;**绝不写 opts.runtime.plugins**(那是 L1 内部的,你见不到 → 写了就 undefined → boot 崩 reading 'define')。游戏只用插件、不 new、不 register。
|
||||
- **可用插件键(全 11 已注入,用到才取)**:sceneFsm / sessionScore / hudUi / timerScheduler / save / gamefeel / juice / palettePost / audioMusic / collision / physics。
|
||||
- **插件方法名一律以 api.d.ts 为准、别臆测**(实测幻觉:scene-fsm 写 defineScenes〔应 define〕;particles-juice 没有 burst〔事件 API 名以其 api.d.ts 为准〕)。用到某插件先 read_file 它的 api.d.ts;skill「⚠️ 实测易犯的幻觉 API」逐条列 ❌→✅。
|
||||
- **插件方法名一律以 api.d.ts 为准、别臆测**(实测幻觉:scene-fsm 写 defineScenes〔应 define〕)。用到某插件先 read_file 它的 api.d.ts;skill「⚠️ 实测易犯的幻觉 API」逐条列 ❌→✅。
|
||||
- **hudUi.drawButton(g, rect, opts) 回吐它绘制的命中矩形 { x, y, w, h }**:✅ 直觉写法就对——const btn = hudUi.drawButton(g, { x, y, w, h }, { label }); if (hudUi.pointInRect(px, py, btn)) 命中处理(drawButton 返回命中矩形,直接接住判命中即可)。命中判定走 hudUi.pointInRect(px, py, rect)(**只在 hudUi,collision 插件没有 pointInRect 方法**)。**第二参是 rect 对象 { x, y, w, h }、不是 label/坐标位置参**——别写 drawButton(g, '开始', x, y, w, h) 这种位置参形态。
|
||||
- **sceneFsm.define(name, handlers) 逐场景调一次——绝不单对象批量注册**:❌ sceneFsm.define({ menu: {...}, play: {...}, over: {...} }) → 第一参当成了场景名「[object Object]」、menu/play/over 全没真注册 → 后续 transition('play') 找不到目标态被忽略 → 卡菜单(实测合成/2048 类翻车点)。✅ **每个场景各调一次**:sceneFsm.define('menu', { onEnter(){...} }); sceneFsm.define('play', { onEnter(){...}, update(dt){...} }); sceneFsm.define('over', { onEnter(){...} });(define 第一参恒为场景名字符串、第二参才是回调表 { onEnter, onExit, update, render };可链式 .define(...).define(...))。
|
||||
- **画面读 viewport.w/h**(viewport 是入参;别硬编码 390×844)。
|
||||
|
||||
@ -207,6 +207,8 @@ export interface ParticlesJuicePlugin extends Plugin {
|
||||
y: number,
|
||||
overrides?: Partial<EmitterConfig>
|
||||
): number;
|
||||
/** burst 预设的母语别名(#10:AI 直觉 juice.burst(x,y) 直接可用)= spawnEmitter('burst', x, y, overrides)。 */
|
||||
burst(x: number, y: number, overrides?: Partial<EmitterConfig>): number;
|
||||
/** 停止一个 continuous 发射器(不再补充新粒子)。幂等。 */
|
||||
stopEmitter(id: number): void;
|
||||
/** 手动推进一帧(autoStep:false 时用)。 */
|
||||
|
||||
@ -459,6 +459,18 @@ export function createParticlesJuicePlugin(opts) {
|
||||
return id;
|
||||
},
|
||||
|
||||
/**
|
||||
* burst 预设的母语别名(#10 阶段一B:AI 直觉 juice.burst(x,y) 直接可用)= spawnEmitter('burst', x, y, overrides)。
|
||||
* 引 plugin 自身委托、不依赖 this。
|
||||
* @param {number} x 喷发原点 x(像素)。
|
||||
* @param {number} y 喷发原点 y(像素)。
|
||||
* @param {Partial<EmitterConfig>} [overrides] 在 burst 预设上的局部覆盖(count/角度等)。
|
||||
* @returns {number} 发射器 id(可用它 stopEmitter)。
|
||||
*/
|
||||
burst(x, y, overrides) {
|
||||
return plugin.spawnEmitter('burst', x, y, overrides);
|
||||
},
|
||||
|
||||
/**
|
||||
* 停止一个发射器(转发引擎句柄 stop→销毁引擎发射器)。幂等(未知 id / 已停均静默)。
|
||||
* @param {number} id 发射器 id。
|
||||
|
||||
@ -129,6 +129,18 @@ test('曲线求值:四类曲线端点恒等、衰减/线性单调、t 越界
|
||||
* 再用「从预设配置 + evalCurve 独立算出的期望值」逐字段断言(不反查内部实现,钉契约不钉实现)。
|
||||
* ──────────────────────────────────────────────────────────────────────────── */
|
||||
|
||||
test('#10 burst(x,y) 是 spawnEmitter("burst",x,y) 的母语别名(同 spec 入引擎)', () => {
|
||||
let captured = null;
|
||||
const { context } = makeMockEngineContext((spec) => { captured = spec; return { isActive: () => true, stop() {} }; });
|
||||
const p = createParticlesJuicePlugin({ autoStep: false });
|
||||
p.init(context);
|
||||
const id = p.burst(100, 200);
|
||||
assert.equal(typeof id, 'number', 'burst 返发射器 id');
|
||||
assert.ok(captured, 'burst 应经 spawnEmitter 把 spec 交给引擎');
|
||||
assert.deepEqual(captured.pos, { x: 100, y: 200 }, 'burst 透传发射原点');
|
||||
assert.equal(captured.emitTime, 1 / 60, 'burst → emitTime=1/60(与 spawnEmitter("burst") 同)');
|
||||
});
|
||||
|
||||
test('toEngineSpec 纯映射(经 spy 捕获):burst 预设逐字段换算正确', () => {
|
||||
let captured = null;
|
||||
const { context } = makeMockEngineContext((spec) => { captured = spec; return { isActive: () => true, stop() {} }; });
|
||||
|
||||
@ -89,7 +89,7 @@ A 读手册(1)+ 读你的起点 game-logic.js(4)→ B 按 brief 定玩法(核心
|
||||
- **入参是扁平的 { plugins, bundle, viewport }——没有 opts、没有 opts.runtime、没有 ctx**(L1 已替你摊平)。受控面 ctx 只在五法 **init(boot) 的 boot.ctx**;**调用形态钉死**:时间 **ctx.time.nowMs()**、随机 **ctx.random.next()/ctx.random.range(a,b)**、画布 ctx.getContext2d()、引擎 ctx.getEngine()、日志 ctx.log(tag,msg)。**time/random 是对象、不是函数——绝不写 ctx.nowMs()/ctx.random()**(实测 M3 翻车:当函数调 → 抛错 → 游戏空转不 spawn/不计分)。五法 = init(boot)/update(dt)/render(g)/destroy()(+必须 _forensicsView)+ **输入方法 handleTap(x,y)**(见下「输入契约」)。
|
||||
- **插件扁平注入:直接 const { sceneFsm, sessionScore, hudUi, timerScheduler } = plugins(用到才解构)**;**绝不写 opts.runtime.plugins**(那是 L1 内部的,你见不到 → 写了就 undefined → boot 崩 reading 'define')。游戏只用插件、不 new、不 register。
|
||||
- **可用插件键(全 11 已注入,用到才取)**:sceneFsm / sessionScore / hudUi / timerScheduler / save / gamefeel / juice / palettePost / audioMusic / collision / physics。
|
||||
- **插件方法名一律以 api.d.ts 为准、别臆测**(M3 实测幻觉:scene-fsm 写 defineScenes〔应 define〕;particles-juice 没有 burst〔事件 API 名以其 api.d.ts 为准〕)。用到某插件先 read_file 它的 api.d.ts;skill「⚠️ M3 实测易犯的幻觉 API」逐条列 ❌→✅。
|
||||
- **插件方法名一律以 api.d.ts 为准、别臆测**(M3 实测幻觉:scene-fsm 写 defineScenes〔应 define〕)。用到某插件先 read_file 它的 api.d.ts;skill「⚠️ M3 实测易犯的幻觉 API」逐条列 ❌→✅。
|
||||
- **hudUi.drawButton(g, rect, opts) 回吐它绘制的命中矩形 { x, y, w, h }**:✅ 直觉写法就对——const btn = hudUi.drawButton(g, { x, y, w, h }, { label }); if (hudUi.pointInRect(px, py, btn)) 命中处理(drawButton 返回命中矩形,直接接住判命中即可)。命中判定走 hudUi.pointInRect(px, py, rect)(**只在 hudUi,collision 插件没有 pointInRect 方法**)。**第二参是 rect 对象 { x, y, w, h }、不是 label/坐标位置参**——别写 drawButton(g, '开始', x, y, w, h) 这种位置参形态。
|
||||
- **sceneFsm.define(name, handlers) 逐场景调一次——绝不单对象批量注册**:❌ sceneFsm.define({ menu: {...}, play: {...}, over: {...} }) → 第一参当成了场景名「[object Object]」、menu/play/over 全没真注册 → 后续 transition('play') 找不到目标态被忽略 → 卡菜单(实测合成/2048 类翻车点)。✅ **每个场景各调一次**:sceneFsm.define('menu', { onEnter(){...} }); sceneFsm.define('play', { onEnter(){...}, update(dt){...} }); sceneFsm.define('over', { onEnter(){...} });(define 第一参恒为场景名字符串、第二参才是回调表 { onEnter, onExit, update, render };可链式 .define(...).define(...))。
|
||||
- **画面读 viewport.w/h**(viewport 是入参;别硬编码 390×844)。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user