Class QuestRolloutDifferTest

java.lang.Object
scripting.QuestRolloutDifferTest

public class QuestRolloutDifferTest extends Object
Track B(Nashorn → GraalJS)— C6 rollout④:quest 類別黃金參考 differ。

對應計畫 C6 的出口條件「Tier2+Tier3 differ 過關」。quest 是逐類別 rollout 第四棒 (portal=C3、reactor=C4、npc=C5),語料量 522 個 scripts/quest/*.js。本檔與 npc differ (C5)同骨架,因為 quest 與 npc 由同一個 NPCScriptManager 驅動:

  • 進入點:quest 線上以 engine.put("qm", ..) + invokeFunction("start", mode,type,selection)(初次接取)/ invokeFunction("end", mode,type,selection)(初次完成)驅動,玩家點選後再以相同函式名續傳 (見 NPCScriptManager.startQuest(MapleClient, int, int) / NPCScriptManager.endQuest(MapleClient, int, int, boolean),四個進入點)。 綁定的全域是 qm(仍是一個 NPCConversationManager 實例,只是換個名字),進入函式是 start / end(皆吃 (mode,type,selection)),路徑前綴是 quest/。故 Tier2 轉錄走 start/end,Tier3 綁定 parity 以「typeof starttypeof end 逐檔跨引擎 一致」驗證(線上實際呼叫路徑)。
  • 快取/執行緒模型(關鍵,結論同 C5、且安全):quest 引擎為 per-client 快取 (AbstractScriptManagerMapleClient.getScriptEngine(String),與 npc 共用同一個 per-client engines ConcurrentHashMap),且 NPCScriptManager 的四個 quest 進入點 (startQuest×2 / endQuest×2)皆先取 c.getNPCLock()(per-client npc_mutex)、並橫跨 getInvocable(含 compile+eval) + invokeFunction 全程持鎖 —— 與 npc 完全相同的機制。 對抗審查(workflow,3 lens × refute-by-default,3/3 皆未能反駁)確認:取得 quest 引擎的唯二處 (NPCScriptManager 的 getInvocable("quest/..") 於 startQuest/endQuest 之內)皆在 npc_mutex 鎖區內;且與 reactor(C4)不同,quest 未被任何 Timer 排程(reactor 的病是 MapTimer 無鎖呼叫 act()),亦無 portal(C3)的行程級共用 SAM。腳本內 qm.startQuest/completeQuest 的同執行緒 重入由 ReentrantLock(npc_mutex 可重入)+ !cms.containsKey(c) 守衛雙重擋下,不會巢狀進 Context。故 quest 不需新增任何鎖 —— 沿用既有 npc_mutex 即足。本檔下半的並發回歸組在引擎層 佐證「per-client ReentrantLock(即 npc_mutex)序列化即足」,並文件化該鎖對 quest 亦 load-bearing(防未來重構誤拔)。

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

(語料偵察 + 對抗審查由 workflow 完成:全 522 檔零 E4X / Java.type / importClass / JavaAdapter;interop 僅 load('nashorn:mozilla_compat.js') + importPackage(15 處,含 小寫 importpackage 打錯的 parity 邊角),兩引擎在 js.nashorn-compat=true 下走相同路徑、parity。 並發審查 3 lens × refute-by-default 皆未能反駁「quest 不需新鎖」。)

  • Constructor Details

    • QuestRolloutDifferTest

      public QuestRolloutDifferTest()
  • Method Details

    • corpusParseAndStartEndBindingAreEngineEquivalent

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

      「兩引擎皆解析失敗」≠ 分歧(記錄到 build/quest-parse-broken-both.txt,不擋 rollout)。 註:(1) 小寫 importpackage(...) 打錯的數檔(2317/2318/2320/2321.js)語法合法 → compile 成功,僅 eval 時兩引擎皆拋 ReferenceError(函式宣告已 hoist,故 typeof start/end 仍一致)→ 落在 parity 內、非 broken-both。(2) 22110-22119.js 呼叫 不存在qm.gainSp(int,int) —— 但只在 invoke 時拋(兩引擎同樣 TypeError),compile/eval 階段不觸發,故 Tier3 解析+綁定 parity 不受影響(本檔刻意在 stub 定義 gainSp,以免掩蓋該等價失敗)。

      Throws:
      Exception
    • tier2TranscriptsMatchAcrossEngines

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

      public void questOverloadAndCoercionFixtureMatchesAcrossEngines() throws Exception
      合成微 fixture(不依賴部署內容,scripts/ 缺席亦跑):把 quest 最具差異風險的「多載選擇 + 數值窄化」 集中於一行隔離直驗,並對確切轉錄下斷言。涵蓋:
      • forceStartQuest() 無參 vs forceStartQuest(int) vs forceStartQuest(int,String) —— 數字字面 21202 走 (int)、String "0" 走 (int,String)(不得被強制成 (int,int))。
      • forceCompleteQuest() 無參 vs forceCompleteQuest(int)
      • gainItem(int,short) 正/負窄化(5 / -3)。
      • gainExp(int)teachSkill(int,int,byte) 第三引數 1 → byte。
      Throws:
      Exception
    • sharedGraalQuestContextIsUnsafeUnderConcurrentEntry

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

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