Class NpcRolloutDifferTest

java.lang.Object
scripting.NpcRolloutDifferTest

public class NpcRolloutDifferTest extends Object
Track B(Nashorn → GraalJS)— C5 rollout③:npc 類別黃金參考 differ。

對應計畫 C5 的出口條件「Tier2+Tier3 differ 過關」。npc 是逐類別 rollout 第三棒 (portal=C3、reactor=C4),也是語料量最大的一類(924 個 scripts/npc/*.js)。本檔與 reactor/portal differ 同骨架,差異在進入點與並發模型:

  • 進入點:npc 線上以 engine.put("cm", ..) + invokeFunction("start")(或回退 action((byte)1,(byte)0,0))驅動,玩家點選後再 invokeFunction("action", mode, type, selection)(見 NPCScriptManager.start(MapleClient, int) / NPCScriptManager.action(MapleClient, byte, byte, int))。故 Tier2 轉錄走 start/action,Tier3 綁定 parity 以「typeof starttypeof action 逐檔跨引擎一致」驗證(線上實際呼叫路徑)。
  • 快取/執行緒模型(關鍵,異於 C3/C4):npc 引擎為 per-client 快取 (AbstractScriptManagerMapleClient.getScriptEngine(String)),且 NPCScriptManager 的全部六個進入點(start/action/startQuest/endQuest/onUserEnter/ onFirstUserEnter)皆先取 c.getNPCLock()(per-client npc_mutex)、並橫跨 getInvocable(含 compile+eval) + invokeFunction 全程持鎖。npc 引擎確實會被 off-Netty 執行緒進入(GMCommand CrucialTime 走 EventTimer;MapScriptMethods/ MapleMap 的 EtcTimer/EventTimer → onUserEnter/onFirstUserEnter),但每條路徑 都先取同一把 per-client npc_mutex不可能兩執行緒同時進同一 client 的 Context。 這正是 reactor(C4)缺、而 npc 天生具備的保護:reactor 的 act() 曾被 MapTimer 無鎖 進入 Context 才需補 per-(client,rid) 鎖;npc 對應的無鎖進入,故 不需新增任何鎖。 本檔下半的並發回歸組在引擎層證明「per-client ReentrantLock(即 npc_mutex)序列化即足」, 並文件化該鎖對 GraalJS 已成 load-bearing(防未來重構誤拔)。

同一 JVM 並存 nashorn-core(黃金參考)與 GraalJS(候選),由 ScriptEngines.fresh(ScriptEngines.Backend) 建立。不需 DB / wz / 網路 / Timer。

(語料偵察 + 對抗審查由 workflow 完成:全 924 檔零 E4X/Java.type/importClass/ JavaAdapter;唯一 interop 為 load('nashorn:mozilla_compat.js')(11 檔)+ importPackage(13 檔),兩引擎在 js.nashorn-compat=true 下走相同路徑、parity。 並發審查 3 lens × refute-by-default 皆未能反駁「npc 不需新鎖」。)

  • Constructor Details

    • NpcRolloutDifferTest

      public NpcRolloutDifferTest()
  • Method Details

    • corpusParseAndStartActionBindingAreEngineEquivalent

      public void corpusParseAndStartActionBindingAreEngineEquivalent() throws Exception
      scripts/npc/*.js 在 nashorn-core 與 GraalJS 必須等價(非「全部成功」):
      • 解析分歧(XOR)= 零:不得有任何腳本「一引擎解析成功、另一引擎失敗」。E4X/Nashorn-only 語法殘留會在此現形 —— rollout 硬門檻。(workflow 已掃全 924 檔:零真 E4X。)
      • start/action 綁定逐檔等價:eval(容忍頂層丟)後 typeof starttypeof action 兩引擎須各自一致 —— 驗 NPCScriptManager 線上實際走的 invokeFunction("start"|"action") 路徑。

      「兩引擎皆解析失敗」≠ 分歧(記錄到 build/npc-parse-broken-both.txt,不擋 rollout)。 註:小寫 importpackage(...) 打錯的兩檔(9270035.js/9900007.js)語法合法 → compile 成功,僅 eval 時兩引擎皆拋 ReferenceError(函式宣告已 hoist,故 typeof start 仍一致)→ 落在 parity 內、非 broken-both。

      Throws:
      Exception
    • tier2TranscriptsMatchAcrossEngines

      public void tier2TranscriptsMatchAcrossEngines() throws Exception
      Throws:
      Exception
    • gainMesoIntCoercionMatchesAcrossEngines

      public void gainMesoIntCoercionMatchesAcrossEngines() throws Exception
      合成微 fixture 補語料缺口:全 npc 語料中 「未鏈接 cm.getMeso() 的 flat cm.gainMeso(整數字面)」(每個 gainMeso 都閘在 getPlayer().getMeso() 之後),故以一行腳本 直驗 gainMeso(int) 的數值強制 parity(避免 Tier2 該維度空白)。
      Throws:
      Exception
    • sharedGraalNpcContextIsUnsafeUnderConcurrentEntry

      public void sharedGraalNpcContextIsUnsafeUnderConcurrentEntry() throws Exception
      重現通用危害:單一共用 GraalJS Context 被兩執行緒同時 invokeFunction("start") → 拋多執行緒例外(或一度重疊)。證明若無 per-client 序列化,npc 切 GraalJS 亦會中招。
      Throws:
      Exception
    • perClientNpcMutexSerializesConcurrentEntry

      public void perClientNpcMutexSerializesConcurrentEntry() throws Exception
      佐證:套上 per-client ReentrantLock(即線上 npc_mutex 對所有進入點的作法)→ 無例外、 maxInFlight<=1。此即「npc 不需新增鎖」的根據:既有 npc_mutex 已序列化 Context 進入。 亦文件化:該鎖對 GraalJS 已 load-bearing,未來重構不可拔。
      Throws:
      Exception