【我?guī)旺椊切辀ug】一堆bug和“bug”的...異客

前言
繼安潔之后,第二個成功做到包場修bug系列的干員來了。沒錯,他就是異客。

根據(jù)目前的發(fā)現(xiàn),異客身上一共有4個bug或“bug”,分別為:
異客未攜帶模組時,3技能輝煌裂片造成停頓時間異常。
異客攜帶模組時,1技能電能之觸造成停頓時間異常。
異客3技能輝煌裂片,緩存攻擊力機(jī)制(快照機(jī)制)未能按設(shè)定正常生效。
法術(shù)傷害通用bug:受到的理論法術(shù)傷害剛好等于剩余生命值時,單位不會死亡。
其中前兩個是非常明確的bug;第三個我不清楚算不算bug;最后一個是優(yōu)化底層優(yōu)化出來的,其實(shí)不能算是異客的bug,很多干員都有這個問題,不過既然異客也有那就放一起說好了(其實(shí)是懶得再開一期)。

異常的停頓時間
第一個bug:異客未攜帶模組時,3技能輝煌裂片造成的停頓時間存在異常。
實(shí)際上就是,不帶模組的異客的輝煌裂片的停頓時間,只有0.2s,而不是普攻的0.5s。這個bug可以在游戲中簡單地被測試出來。例如此視頻的1:06處,拳擊手鱷魚被輝煌裂片連續(xù)命中時,移速時快時慢(輝煌裂片攻擊間隔為0.5s,若停頓也持續(xù)0.5s,停頓應(yīng)當(dāng)是無縫的,移速應(yīng)當(dāng)是均勻的)。
有印象的朋友們應(yīng)該還記得,0.2s的停頓是異客在2021年8月3日更新前的數(shù)據(jù),而0.5s的停頓是更新后的。而這個bug的成因也很簡單,鷹角給異客做加強(qiáng)的時候,忘了改了...(雖然比起鷹角忘了改我更想吐槽這bug居然3個月沒人發(fā)現(xiàn)...)
那么為什么只有輝煌裂片出現(xiàn)了忘了改的問題而其它技能/其他角色沒有呢?
簡單來說,輝煌裂片的停頓時間,和其它技能的停頓時間,不是寫在一起的。
輝煌裂片,從比較好理解的角度來看,可以算是瞬發(fā)技能(Skill里直接搭載的RangedAttack),而不同于聚焦指令初雷這樣的切換模式類技能(Skill里搭載的是切換模式和加攻減間隔buff)。作為非普通攻擊,輝煌裂片無法像切換模式后的普攻那樣讀取attack blackboard里記錄的停頓時間。為了設(shè)置輝煌裂片的停頓時間,鷹角在skill blackboard里額外進(jìn)行了設(shè)置,像這樣:


鷹角在加強(qiáng)鏈術(shù)師的時候只改動了特性寫在attack blackboard里,被普攻讀取的停頓時間,而把輝煌裂片這個不算普攻的特例給忘了。
但是三個月之后,給鏈術(shù)師加專屬模組時,他們又把這事想起來了。異客的專屬模組,專門對輝煌裂片進(jìn)行了調(diào)整,像這樣:

專屬模組擁有較高的優(yōu)先級,它提供的數(shù)據(jù)可以覆蓋掉其它來源的數(shù)據(jù),而異客的專屬模組,為skill blackboard提供了可供輝煌裂片讀取的0.8s停頓時間。因此,攜帶專屬模組時,輝煌裂片的停頓時間是正常的,和普攻一致的0.8s。
然而,鷹角仍然忘了2件事:
1. 他們?nèi)匀粵]想起來去看看輝煌裂片的本體。不攜帶模組的異客,輝煌裂片停頓時間仍然是0.2s。
2. 他們忘了,異客的1技能電能之觸,也是個瞬發(fā)型的技能,需要從skill blackboard里讀數(shù)據(jù)。而專屬模組寫在skill blackboard的,本意是提供給輝煌裂片的停頓時間,因?yàn)闆]做名稱上的區(qū)分,也把1技能電能之觸的停頓時間給覆蓋了。
這就是第二個bug,攜帶模組的異客,1技能電能之觸的停頓時間是0.8s,而不是描述中的1.5s。這個各位可以自行測試,0.8s和1.5s差別還是挺大的。
修復(fù)方法:
第一個bug:把0.2改0.5就行。
第二個bug:想辦法加一個標(biāo)志來區(qū)別兩個不同的技能/能力/停頓buff。停頓buff的durationKey好像在buff_table里被寫死成sluggish了,通過buff區(qū)分估計不太行??梢栽囋嚁嘧镎吣欠N,給ability加blackboardPrefix來區(qū)分。

異常的緩存/快照機(jī)制
首先聲明:這個我不知道能不能算bug,我只說一下我知道的事實(shí)和原理,不會給出解決方案,最終判斷權(quán)歸鷹角所有。
2021年4月的遺塵漫步版本,實(shí)裝了干員異客,同時增加了一個新機(jī)制:始終使用緩存攻擊力的,造成有來源傷害的彈道 (_useCachedAtkOnly + _transferSource)。
首先需要解釋一下什么是緩存攻擊力/快照機(jī)制。一般情況下,彈道存在期間,每次造成傷害時,都會獲取來源的攻擊力,換言之,一般情況下彈道的攻擊力會隨著本體的攻擊力實(shí)時發(fā)生變化。但是使用緩存攻擊力則不同,彈道的攻擊力會在彈道生成時被確定,之后不隨本體發(fā)生變化。(玩過原神的可以理解為香菱大和行秋大的區(qū)別)
緩存攻擊力機(jī)制開服就有,用于應(yīng)對彈道命中時來源不存在的問題(大概就是彈道生成后把人撤了,再命中就是使用的就是彈道生成時緩存的攻擊力,常用于保證火山最后一個火球吃到加攻)。而強(qiáng)制彈道使用緩存攻擊力的機(jī)制,也在之后實(shí)裝(具體時間記不清了,印象里是一周年前夕)。使用此機(jī)制時,_useCachedAtkOnly被設(shè)置為true。最典型的例子是干員W的3技能D12,技能攻擊力在炸彈安裝瞬間就確定了,而不是倒數(shù)結(jié)束造成傷害的時刻。但同時,這個強(qiáng)制使用緩存攻擊力的機(jī)制的機(jī)制存在一個問題:因?yàn)槭钦瞻岬膽?yīng)對來源不存在問題的方案,導(dǎo)致之前版本強(qiáng)制使用緩存攻擊力時,彈道始終會造成無來源傷害。還是W的D12為例,因?yàn)閭κ冀K無來源,不會因?yàn)镈12自身的倍率跳紅字,目標(biāo)不會有受到傷害的變紅特效,吃不到防空符文的加成,打進(jìn)化的本質(zhì)12階段始終不會被方向減免傷害,如果有反甲也不會被反傷。
由于無來源傷害的問題,在2021年4月的更新中,加入了新的機(jī)制,允許彈道強(qiáng)制使用緩存攻擊力,并造成有來源的傷害(使用此機(jī)制時,_useCachedAtkOnly和_transferSource同時被設(shè)置為true)。而這個機(jī)制,截至當(dāng)前版本,在且僅在異客的3技能輝煌裂片中被使用。

然而,事實(shí)上,輝煌裂片的攻擊力是隨異客的攻擊力實(shí)時變化的,這和使用的設(shè)置明顯不符。這是為什么?
首先需要了解的是,任何攻擊/彈道導(dǎo)致的傷害,都是通過造成傷害的行為(Action)執(zhí)行的。一般情況下,用于執(zhí)行彈道攻擊的能力,會在初始化時將供彈道使用的行為(例如造成傷害)配置好(始終依賴緩存攻擊力的有來源傷害的設(shè)定就是在此時配置的)。而在生成彈道時,會將這些行為進(jìn)行預(yù)設(shè)置(PreprocessActionsForProjectile,例如緩存來源的攻擊力),并導(dǎo)入到彈道的設(shè)定中。
一般情況下,彈道生成后,其搭載的行為就不會發(fā)生變化,生成時緩存的攻擊力會被忠實(shí)地被保存到最后。然而鏈術(shù)師彈道的連鎖攻擊行為(ChainLightningHitBehaviour)是個例外。
由于鏈術(shù)師的連鎖彈道每經(jīng)過一次彈射攻擊倍率都會衰減,因此每次彈射時,都要創(chuàng)建新的傷害行為,并替換掉舊的,以此更新傷害倍率。隨后,會再次對新生成的傷害行為進(jìn)行預(yù)設(shè)置,并重新導(dǎo)入到彈道的設(shè)定中。而對傷害行為進(jìn)行預(yù)設(shè)置時,會立刻獲取來源當(dāng)前的攻擊力并儲存為緩存攻擊力。因此,異客的輝煌裂片雖然設(shè)定上是使用的緩存攻擊力,但因?yàn)槊看卧斐蓚η熬彺婀袅Χ紩?,所?strong>輝煌裂片的攻擊力實(shí)際上是實(shí)時更新的。
有意思的是,鷹角早在迷迭香實(shí)裝時就注意到了類似的問題。迷迭香的攻擊也有著和鏈術(shù)師類似的在造成傷害前創(chuàng)建新傷害行為的機(jī)制??赡苁菫榱吮苊饷缘愠坊睾髲椀罒o傷害的問題,他們給迷迭香的2技能添加了一個名為_deliveryParaWhenReplaceActionNode的機(jī)制(上圖中第一個變量)。這個機(jī)制會在新創(chuàng)建的傷害行為替換掉舊傷害行為前,將舊傷害行為中緩存的攻擊力傳遞到新傷害行為中。而干員撤回后,能力不會對彈道中搭載的行為再次進(jìn)行預(yù)設(shè)置(緩存來源攻擊力)。因此即使迷迭香撤回后,2技能彈道也可以正常造成傷害。然而這個機(jī)制目前并不能在異客身上使用,因?yàn)檩x煌裂片實(shí)時更新攻擊力的問題是在異客存活時出現(xiàn)的,這時預(yù)設(shè)置仍然會在傳遞緩存攻擊力后進(jìn)行,并覆蓋掉來自于舊行為的緩存攻擊力。

異常的傷害數(shù)值
最后一個bug,準(zhǔn)確來說不是異客的,很多干員/敵人都有類似的問題:
受到的理論法術(shù)傷害剛好等于剩余生命值時,單位不會死亡。
這個bug可以參見視頻BV1ub4y117iB。此視頻中,2400血量0法抗的風(fēng)笛,在受到2發(fā)深池塑能術(shù)師隊(duì)長0距離的火球(每發(fā)600法術(shù)傷害)和一次灼燃?xì)p(1200法術(shù)傷害)后,顯示剩余0血,但并未死亡。
這個bug的成因其實(shí)很簡單,概括地說就是二進(jìn)制小數(shù)的精度問題。
在鉛封行動版本更新后,為了解決之前由于浮點(diǎn)精度導(dǎo)致的一系列問題(部分可參考專欄cv7887116),在大部分的游戲數(shù)值計算中,32bits的單精度浮點(diǎn)數(shù)被替換成了64bits的定點(diǎn)數(shù)。
定點(diǎn)數(shù),顧名思義,就是將小數(shù)點(diǎn)固定在某個位置的數(shù)。以方舟運(yùn)算中使用的定點(diǎn)數(shù)為例,一共是64位,其中高32位用于表示一個數(shù)的整數(shù)部分,而低32位用于表示這個數(shù)的小數(shù)部分。相比于浮點(diǎn)數(shù),定點(diǎn)數(shù)的精度是確定的,不會因?yàn)樾枰硎镜臄?shù)字的絕對值大小的增加而精度減小,但相對的,定點(diǎn)數(shù)的值域也會比相同位數(shù)的浮點(diǎn)數(shù)小上許多。
而二進(jìn)制,大家應(yīng)該都很清楚。舉幾個例子,1200用二進(jìn)制表示就是100 1011 0000,而1000.5是11 1110 1000.1。
然而,如果要表示的數(shù)是0.01呢?按一下計算器,會發(fā)現(xiàn),0.01這個在十進(jìn)制中可以被有限位數(shù)精確表示的小數(shù),在二進(jìn)制中卻是一個無限循環(huán)的小數(shù):0.00 00001010001111010111 00001010001111010111 ... 而方舟使用的定點(diǎn)數(shù),理所當(dāng)然地?zé)o法準(zhǔn)確表示這樣一個小數(shù),只能記錄小數(shù)點(diǎn)后的32位,也就是.0000 0010 1000 1111 0101 1100 0010 1000
而這樣一個數(shù),就存在于法術(shù)傷害的計算公式里。法術(shù)傷害的計算公式為(來源于prts,自己也拆著驗(yàn)證過):
基本法術(shù)傷害 = MAX(0.01 *?攻擊力?* 攻擊力倍率 * MAX(0, 100 - (1-?百分比無視法抗) * MAX(0, 目標(biāo)法術(shù)抗性?-?數(shù)值無視法抗)), 0.05?*?攻擊力?* 攻擊力倍率)
由于法術(shù)傷害計算公式中的0.01,無法被有限位數(shù)的二進(jìn)制小數(shù)精確表示,儲存在定點(diǎn)數(shù)中的0.01并不是精確值(因?yàn)楹罄m(xù)位數(shù)無法保存,實(shí)際比0.01略?。?。因此,最后計算出的實(shí)際結(jié)果略小于理論傷害值。而最終結(jié)果的絕對誤差,是遠(yuǎn)大于定點(diǎn)數(shù)最小精度的(根據(jù)誤差傳播的原理,變量與常數(shù)做乘法時相對誤差保持不變,換言之乘了多大的常數(shù)絕對誤差就擴(kuò)大了多少倍。后面乘的一堆攻擊力和100-法抗乘起來肯定不止2,完全足夠讓這個2^-33級別的絕對誤差擴(kuò)大到2^-32以上,然后被精度為2^-32的定點(diǎn)數(shù)記錄下來了)。
這樣一來,就可以解釋這個問題了。這是因?yàn)榉ㄐg(shù)傷害計算公式中的0.01,無法被二進(jìn)制定點(diǎn)數(shù)精確表示,最終導(dǎo)致實(shí)際法術(shù)傷害略小于理論值(例如理論上為1000的法術(shù)傷害,實(shí)際上可能只有999.99999)。而物理傷害,由于計算中不存在這樣不精確的常數(shù),所有并不存在這個問題。同樣的問題也出現(xiàn)在其它需要使用,無法被二進(jìn)制精確表示的小數(shù),進(jìn)行運(yùn)算的地方。例如貝娜2技能根據(jù)最大生命值的流血。
不過最后,還剩一個問題。為什么之前版本使用單精度浮點(diǎn)數(shù)進(jìn)行運(yùn)算的時候,并沒有出現(xiàn)這樣的不精確問題?浮點(diǎn)數(shù)不也是基于二進(jìn)制小數(shù)的嗎?
關(guān)于這個問題,簡單來說,浮點(diǎn)數(shù)確實(shí)和定點(diǎn)數(shù)一樣,無法準(zhǔn)確表示0.01這樣的小數(shù)。但是因?yàn)楦↑c(diǎn)數(shù)的精度是變化的,所以0.01導(dǎo)致的誤差無法被記錄在最終的計算結(jié)果中。具體的,我試著寫了一些數(shù)學(xué)論證,有興趣的朋友可以看下。


論證過程。格式不嚴(yán)謹(jǐn),思維比較跳躍,歡迎提出更好的方案
修復(fù)方案:
1. 嘗試在比較時加一個可容忍的誤差。
2. 再改一下數(shù)據(jù)類型?不過我目前也不太清楚改成啥樣可以既保留較高的運(yùn)算速度同時保留必要的精度。還要再研究+測試一下。