zizi e591e9b705 feat(t1b-beta): A1-A4 引擎能力接线落地——受控面工厂/粒子/音频/手感经 getEngine 薄包装(easing 改判)
承 A0 引擎掌帧地基,按创始人「引擎↔插件边界模型」(引擎有→薄包装·引擎无→自研补层·
同一职不留两条并行路)接线 A1-A4。host.js 注入真 engineFactory,getEngine() 在 host-dev 真活。

A1 受控面+引擎工厂地基+easing 门面:
- host.js makeEngineCaps 一处构造 EngineCapabilities{particles,audio.synth,math};
- 新 host-dev/engine-math.js 包装引擎 lerp/smoothStep + Ease 曲线(11 条等价曲线门面);
- api.d.ts additive 扩 EngineEasing + EngineMath.easing?(不破已冻 lerp/smoothStep)+ 块级降级措辞校准为 null no-op;
- 受控面 random/time 保确定性(引擎粒子用全局 rand 不可种子对齐→random 留插件自管,真缺件)。

A2 particles 薄包装+删 sim:
- EngineEmitterSpec additive 扩 10 可选字段(count/coneAngle/speed/gravityScale/sizeStart-End/colorStart-End/additive);
- spawnEmitter 改走 getEngine().particles 映射引擎 ParticleEmitter,删自研逐粒子积分 sim;null-engine→no-op 句柄;
- juice 全段(hitStop/屏震/闪白)补层保留;测试转向映射纯单测+真渲出现(逐像素确定性随 sim 退役,§1.4 记账)。

A3 audio 包装+删 vendored+补 ×0.3:
- 删 vendor/zzfx.js+zzfxm.js;audio.synth 包装引擎 zzfxG/zzfxM;
- 播放层补 ×0.3 主音量(引擎样本不烤 0.3,gain 层施)+ SAMPLE_RATE=44100 本地常量;
- impl.js 走 getEngine().audio.synth,null-engine→合成 no-op(不回退 vendored)。

A4 gamefeel easing 改判=门面包装(对抗门源码证伪纠偏):
- easing 改走 getEngine().math.easing 门面(quadIn→Ease.POWER(2) 等),内置降名 builtinEasing 作 null-engine fallback(纯数学族特例);
- backInOut/elasticInOut 引擎 IN_OUT 拼接差 6.6%/17%(另一条曲线)→门面刻意不暴,这二者恒走内置=真缺件补层;
- collision/physics-lite/palette-post 加边界裁定注释(真缺件补层,源码精校:引擎仅 isOverlapping/isIntersecting 返 boolean,无 Manifold/任意几何/RayHit)。

对抗门两逮(均前置于实现·零返工):
- A4 easing 误判 fatal:前轮按小写 quadIn grep 假阴性误判「引擎无 easing」;引擎实有 Ease(esm.js:15118)→创始人拍本波包装;
- collision 注释 under-claim:已精校为源码准措辞。新红线 §1.5:引擎能力普查禁按预期名 grep,须读 export 块/.d.ts。

本机门全绿(亲验):
- node --test 168/168 绿;all-plugins+host-bundle 双 esbuild rc=0(35594B/209516B);
- 真接线门①引擎真入产物 littlejsengine 163641B;门③负扫=0 插件直 import+vendor 已删+0 自研 sim+0 假注释;Q4 引擎 import 仅 entry.js+host.js。

A6(mini-desktop real 像素门 + 真接线门② runtime probe call-ID)待主会话驱动。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 11:48:28 +00:00

88 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* host-dev/engine-math.js — 引擎数学库门面构造(纯函数,零引擎 import
* ownerT1b-β A1 消费方host-dev/host.jsreal 通道工厂 makeEngineCaps 调它)+ A1 node 单测
*
* ════════════════════════════════════════════════════════════════════════════
* 【为什么单列本模块(不埋进 host.js
* A1 须在 real 通道把 EngineCapabilities.mathlerp/smoothStep + easing 门面)真活包装引擎。
* 把门面构造抽为「吃 ljs 参数」的纯函数,且放在**零引擎 import 的独立模块**,是 testability 刚需:
* · F7node 实跑亲验):`import * as LJS from 'littlejsengine'` 在 node 顶层抛 `window is not defined`
* → host.js 因顶层 import 引擎node 根本 import 不了 host.js实测同样抛 window
* · 故门面纯函数必须住在**不 import 引擎**的模块里node 测才能 import 它、注入「内联引擎 Ease 曲线的
* mock ljs」逐字节复制 esm.js:15118验门面映射正确性——全程不碰真引擎。
* blast radius仅 host.js 内部组织host.js 改为从本模块 import clampUnit/buildEngineMath
* 不改契约形状api.d.ts、不改 plugin.js、不改任何插件 impl。
*
* 【引擎↔插件 边界(创始人 2026-06-13 拍)】
* 纯通用数学族特例lerp/smoothStep/easing 曲线):引擎与门面公式逐字节/ULP 等价、无状态、无语义冲突
* → math façade=getEngine().math。本模块只构造门面包装传入的 ljs不持引擎引用、不做降级 sim。
* ════════════════════════════════════════════════════════════════════════════
*/
'use strict';
/**
* 把 t 钳到 [0,1](与 gamefeel clamp01 同口径)。
* 为何必须钳gamefeel 内置每条 easing 都以 clamp01(t) 打头gamefeel/impl.js:59 等),且其测断越界钳制
* gamefeel.test.mjs 断 fn(-0.5)===fn(0) / fn(1.5)===fn(1));门面是「替换 gamefeel easing 的等价件」,
* 须与被替换者越界行为一致。而引擎 Ease.POWER/LINEAR 等**不自钳**POWER:(n)=>(x)=>x**n越界 x 直接幂运算)
* → 门面不钳则对越界 t 行为 ≠ 内置 fallback → A4 改门面后行为回归。
* @param {number} t
* @returns {number} 钳制到 [0,1] 的值
*/
export function clampUnit(t) {
return t < 0 ? 0 : t > 1 ? 1 : t;
}
/**
* 【A1】构造引擎数学库门面EngineMathlerp/smoothStep + easing 11 条等价曲线)。
* 纯函数:仅依赖传入的 ljs引擎名字空间含 lerp/smoothStep/Ease。host real 通道传真 LJSnode 测传 mock。
*
* ── math.lerp / math.smoothStep ──逐字节包装引擎(口径已对齐,引擎 lerp 自带 percent 钳制 esm.js:1444
* ── math.easing ──门面包装引擎 LJS.Ease 曲线族esm.js:15118**仅暴已 node 逐点亲验等价的 11 条曲线**
* quadIn=POWER(2)(逐字节恒等)/ quadOut=OUT(POWER(2))(逐字节恒等)/ quadInOut=IN_OUT(POWER(2))ULP/
* cubicIn=POWER(3) / cubicOut=OUT(POWER(3)) / cubicInOut=IN_OUT(POWER(3))ULP/
* backIn=BACK / backOut=OUT(BACK) / elasticIn=ELASTIC / elasticOut=OUT(ELASTIC)(均 ULP <1e-15
* 每条调引擎曲线前先 clampUnit(t)(引擎 Ease 不自钳,须钳齐保端点恒等+越界一致)。
* **刻意不暴 backInOut/elasticInOut**:引擎 IN_OUT(BACK/ELASTIC) 经 PIECEWISE 拼接是**另一条曲线**
* back 差 6.6%、elastic 差 17%,非 ULP→ gamefeel 对这二者恒走内置(引擎无此精确曲线=补层,非 fallback
*
* ── 性能POWER(2)/OUT(POWER(2))/IN_OUT(...) 每次调用会新建闭包easing 门面在 juice 热路径每帧调,
* 故在此**预构造一次**这些曲线函数_p2/_p3/_qOut/... 缓存),各 easing 方法只做 clampUnit + 调缓存曲线。
*
* @param {any} ljs 引擎名字空间(须含 lerp/smoothStep/Easereal=LJS测=内联引擎曲线的 mock
* @returns {import('../src/core/api.d.ts').EngineMath} 引擎数学门面lerp/smoothStep/easing
*/
export function buildEngineMath(ljs) {
const Ease = ljs.Ease;
// 预构造曲线一次(避免每次 easing 调用重建 POWER/OUT/IN_OUT 闭包;功能等价,纯性能优化)。
const _p2 = Ease.POWER(2); // 二次基曲线 x**2
const _p3 = Ease.POWER(3); // 三次基曲线 x**3
const _qOut = Ease.OUT(_p2); // 二次缓出 1-(1-x)**2
const _qInOut = Ease.IN_OUT(_p2); // 二次缓入缓出PIECEWISE 拼接)
const _cOut = Ease.OUT(_p3); // 三次缓出
const _cInOut = Ease.IN_OUT(_p3); // 三次缓入缓出
const _backOut = Ease.OUT(Ease.BACK); // 回弹缓出
const _elasticOut = Ease.OUT(Ease.ELASTIC); // 弹性缓出
return {
// ── lerp/smoothStep逐字节包装引擎F4/F5──
lerp: (a, b, p) => ljs.lerp(a, b, p),
smoothStep: (p) => ljs.smoothStep(p),
// ── easing 门面11 条等价曲线,每条钳 t∈[0,1] 后调预构造的引擎曲线 ──
easing: {
linear: (t) => Ease.LINEAR(clampUnit(t)),
quadIn: (t) => _p2(clampUnit(t)),
quadOut: (t) => _qOut(clampUnit(t)),
quadInOut: (t) => _qInOut(clampUnit(t)),
cubicIn: (t) => _p3(clampUnit(t)),
cubicOut: (t) => _cOut(clampUnit(t)),
cubicInOut: (t) => _cInOut(clampUnit(t)),
backIn: (t) => Ease.BACK(clampUnit(t)),
backOut: (t) => _backOut(clampUnit(t)),
elasticIn: (t) => Ease.ELASTIC(clampUnit(t)),
elasticOut: (t) => _elasticOut(clampUnit(t)),
// backInOut/elasticInOut 刻意不暴(引擎 IN_OUT 拼接≠gamefeel 闭式,真分歧 6.6%/17%gamefeel 对其恒走内置。
},
};
}