Class QuestRolloutDifferTest
對應計畫 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 start與typeof end逐檔跨引擎 一致」驗證(線上實際呼叫路徑)。 - 快取/執行緒模型(關鍵,結論同 C5、且更安全):quest 引擎為 per-client 快取
(
AbstractScriptManager→MapleClient.getScriptEngine(String),與 npc 共用同一個 per-clientenginesConcurrentHashMap),且NPCScriptManager的四個 quest 進入點 (startQuest×2 / endQuest×2)皆先取c.getNPCLock()(per-clientnpc_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-clientReentrantLock(即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 不需新鎖」。)
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final classstatic final class受信任第一方腳本以HostAccess.ALL直呼本物件的 public 方法 —— 與線上qm(NPCConversationManager,extends AbstractPlayerInteraction)同樣的 host-interop 路徑。 -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionvoid全scripts/quest/*.js在 nashorn-core 與 GraalJS 必須等價(非「全部成功」): 解析分歧(XOR)= 零:不得有任何腳本「一引擎解析成功、另一引擎失敗」。voidvoid合成微 fixture(不依賴部署內容,scripts/ 缺席亦跑):把 quest 最具差異風險的「多載選擇 + 數值窄化」 集中於一行隔離直驗,並對確切轉錄下斷言。void重現通用危害:單一共用 GraalJS Context 被兩執行緒同時invokeFunction("start")→ 拋多執行緒例外(或一度重疊)。void
-
Constructor Details
-
QuestRolloutDifferTest
public QuestRolloutDifferTest()
-
-
Method Details
-
corpusParseAndStartEndBindingAreEngineEquivalent
全scripts/quest/*.js在 nashorn-core 與 GraalJS 必須等價(非「全部成功」):- 解析分歧(XOR)= 零:不得有任何腳本「一引擎解析成功、另一引擎失敗」。E4X/Nashorn-only 語法殘留會在此現形 —— rollout 硬門檻。(workflow 已掃全 522 檔:零真 E4X。)
- start/end 綁定逐檔等價:eval(容忍頂層丟)後
typeof start與typeof 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
-
questOverloadAndCoercionFixtureMatchesAcrossEngines
合成微 fixture(不依賴部署內容,scripts/ 缺席亦跑):把 quest 最具差異風險的「多載選擇 + 數值窄化」 集中於一行隔離直驗,並對確切轉錄下斷言。涵蓋:forceStartQuest()無參 vsforceStartQuest(int)vsforceStartQuest(int,String)—— 數字字面21202走 (int)、String"0"走 (int,String)(不得被強制成 (int,int))。forceCompleteQuest()無參 vsforceCompleteQuest(int)。gainItem(int,short)正/負窄化(5/-3)。gainExp(int)。teachSkill(int,int,byte)第三引數1→ byte。
- Throws:
Exception
-
perClientNpcMutexSerializesConcurrentQuestEntry
佐證:套上 per-clientReentrantLock(即線上npc_mutex對所有 quest 進入點的作法)→ 無例外、maxInFlight<=1。此即「quest 不需新增鎖」的根據:既有 npc_mutex 已序列化 quest 的 Context 進入。 亦文件化:該鎖對 GraalJS 的 quest 路徑已 load-bearing,未來重構不可拔/不可改為不可重入鎖。- Throws:
Exception
-