判定差のお話
2019.04.28
前回の続きっぽい別バナです。
って感じに前後関係が追いにくいからblogって嫌いなのよね。このあたりはそのうちまとめて別ページにします。

まず判定差(ここではプレイの度に挙動が違う場合のある動作すべての意味)について、ガチ勢な方から提供頂いた貴重な情報をまとめてみます。
Ringedge時代はさらに他にも要因があったようですがとりあえずそこは省略します。

・筐体差
筐体(Nu/IOボード)に依存する固有の挙動。基本的に固定。アップデートで変化することがある。

・日付差/起動差
divaアプリ自体の再起動(筐体再起動/一旦Nuのサービスモードに戻る等)をする度に判定が変化することがある。

・ステージ差
特定のステージのみで判定が変化することがある。

・PV分岐差
分岐する/しないにより、その後のノーツの判定が変化することがある。

・時間差
上記の要因によらず、判定が変化することがある。

ということらしいですが、もちろん体感さっぱりわからん。
判別方法というのもあるらしく、

とのことだけど
「○が広ければxxx台」
とか言われてもどうやって○が広いかどうかを判別しろと。いや、原理は解るけどできません。

ところで、ここの記事はあくまで判定差の原因を突き止めようというだけで、ハイスコアを出してランキングインを目指そう、とかそういうページではないですよ。
腕があればお立ち台とか載ってみたいがな!
というわけなので、情報ソースはランカーの人達が気にするノーツ、つまりHOLDの入出ノーツになるけど、そんなに偶然キーとなるノーツばかりが変化する訳はないので、スコアタに関係ないノーツでもこのような事象は発生しているはず。

ただ、何をもって、「変化した」と定義するかは難しい。
たとえば「C-C MAX」が限られた条件だけで入るという判定差で、もしその筐体のすべての動作が正確に2倍速で動いたとして、超人が居ればやはり「C-C MAX」は入る。明らかに実時間で言えばCOOL期間は半分だけど、それは今回の検証として「変化した」とは言えない。
やはり、指標としては、ホールドノーツのちょうど5秒後にノーツがあって、MAXを取った後に判定がどうなるか、かな。
前回の解析結果によると、SAD/SAFEが一番期間が短いので、SAFEでホールド入って、5秒後のノーツでMAX取ったあとにSAFEを取れるか、とか。
でもHOLDを絡めないで検証したいんだよね。
※HOLDを絡めると、PVをそこまで進めないと検証できない、という、めんどくさいだけの話です。

まあそれはそれとして、課題だった奴。HOLD点の増加基準。結果解れば単純ですが、処理は結構やっかいでした。

はい、またいきなりソースです。HOLD関連処理関数です。
・引数
rcx=HOLD処理構造体アドレス
r8=押されているボタン(0x40)、○(0x80)、×(0x100)、□(0x200)のor
xmm1=前回この関数が呼ばれてからの経過時間

ちなみにこの関数自体は、どうやらVSYNCに同期して呼び出されるようです。
わりと冗長な処理とかもあるので関係ないところはがっつり削ったり順序変えたりしてます

14012C9F3: mov         rdi,rcx
14012C9F6: mov         ecx,dword ptr [rcx+0Ch]         ;ホールド状態
14012CA15: dec         ecx
14012CA17: je          000000014012CA97                ;ホールド中はjmp
14012CA1C: dec         ecx
14012CA1E: je          000000014012CA73                ;MAX到達で飛ぶ


14012CA97: mov         ecx,dword ptr [rdi+10h]         ;ホールド中のボタン。
14012CA9A: test        ecx,ecx
14012CA9C: je          000000014012CA44


14012CA9E: mov         eax,ecx
14012CAA0: and         eax,r8d
14012CAA3: cmp         eax,ecx
14012CAA5: jne         000000014012CA84                ;押されているボタンが減った処理へ


14012CAAC: xorps       xmm0,xmm0
14012CAA7: addss       xmm6,dword ptr [rdi+14h]        ;ホールド開始からの時間
14012CAB8: movss       dword ptr [rdi+14h],xmm6        ;ホールド時間を更新
14012CAB4: mov         r15d,dword ptr [rdi+18h]        ;ホールド点(ゲーム中のHOLDボーナスバー表示)
14012CABD: subss       xmm6,dword ptr [rdi+2Ch]        ;最後にボタンが追加されてからの時間
14012CACC: comiss      xmm6,dword ptr [140999A70h]     ;5.000
14012CAD3: jb          000000014012CADD                ;最後にボタン追加されてから5秒経ってないとjmp
14012CAD5: movss       xmm6,dword ptr [140999A70h]     ;5.000を超過したら5.000に丸め込み
14012CADD: comiss      xmm6,dword ptr [rdi+1Ch]        ;0.167。ただしボタン追加されたら0になる。
14012CAE1: jbe         000000014012CB2D                ;ホールド開始から0.167s経過していないとjmp


14012CAE3: mov         r8d,dword ptr [rdi+24h]         ;1フレームあたりのホールド点。ボタン数で変化
14012CAE7: mov         eax,r8d
14012CAEA: cdq
14012CAEB: sub         eax,edx
14012CAED: sar         eax,1
14012CAEF: movd        xmm0,r8d
14012CAF4: cvtdq2ps    xmm0,xmm0
14012CAF7: mulss       xmm0,xmm6
14012CAFB: divss       xmm0,dword ptr [rdi+20h]        ;[rdi+20h]=常に0.017 xmm0=xmm0/0.017
14012CB00: cvttss2si   ecx,xmm0
14012CB04: add         eax,ecx
14012CB06: cdq
14012CB07: idiv        eax,r8d
14012CB0A: mov         ebx,eax
14012CB0C: imul        ebx,r8d                         ;ebx=round([rdi+24h] * xmm6 / [rdi+20h])
14012CB10: cmp         dword ptr [rdi],esi
14012CB12: jne         000000014012CB23


14012CB2D: mov         ecx,dword ptr [rdi+30h]         ;ホールド追加した時点の点数
14012CB30: add         ecx,ebx
14012CB36: mov         ebp,ecx
14012CB38: mov         dword ptr [rdi+18h],ecx
;ebp=現時点の総ホールド点
;r15d=加算済みホールド点


14012CB41: sub         ebp,r15d                        ;今回の加算点
14012CB52: comiss      xmm6,dword ptr [140999A70h]     ;5.000
14012CB59: mov         dword ptr [rax+74h],3
14012CB60: mov         dword ptr [rax+78h],ebx
14012CB63: mov         dword ptr [rax+7Ch],r12d
14012CB67: jb          000000014012CA4C                ;5.000未満


;MAX到達処理
14012CB6D: mov         eax,dword ptr [rdi+34h]         ;同時押しボタン数
14012CB7B: imul        eax,eax,5DCh                    ;eax*1500=MAXボーナス
14012CB81: mov         dword ptr [rdi+3Ch],eax         ;MAX点を保持、この時点では加算されない
14012CB74: mov         dword ptr [rdi+0Ch],2
14012CB84: jmp         000000014012CA4C


;ホールドMAXでここにくる
14012CA79: mov         ebp,dword ptr [rdi+3Ch]
14012CA95: jmp         000000014012CA4C


14012CA4C:
14012CA60: mov         eax,ebp                 ;戻り値代入
14012CA72: ret


つまりどういうことかというと、
・ホールドは開始から0.167秒は加算されない(知らなかった・・・)
・得点(MAX BONUS除く)はround(基準点(10~40) × ホールド秒 ÷ 0.017)
・ホールドMAX BONUSは、ホールドMAXの次のフレームで加算
ですね。

ところで、単精度浮動小数点の丸め込み誤差っていうのに気づいて、上記0.017とかは正確ではありません。

0.167=(float)0x3E2AAAAB
=(bin)0.00101010101010101010101011
=1/8+・・・+1/67108864
=11453246464/68719476736
=10/60+1/(67108864*3)≒10/60

0.017 =(float)0x3c888889
=(bin)0.00000100010001000100010001001
=1/64+・・・+1/536870912
=8947849/536870912
=1/60+7/(536870912*15)≒1/60

まあ実質1フレ単位ですがフレーム数とは非同期で計算はされていますね。




後日に続く
2019.04.28 03:37 | 固定リンク | ミク | コメント (1)
ビデオキャプチャの信頼性
2019.04.23
突然ですがビデオキャプチャの話題です。
なんかNTSC時代に似たような話題(http://d4.princess.ne.jp/multimedia/ntsc/)を掘り下げたことがありますが、今度はハイビジョン(HDMI)時代のお話です。

直球に言うと、「ゲームをキャプチャしたときに、それは100%正しく録画されているか」という話ですね。
結論から言うと、よほど気にしてキャプチャしない限りは、たいていの場合は「NO」となります。

さてそれではどういう現象が起きるかというと、
・フレームが欠落する
・フレームが増える(同じ画像が2フレーム記録される)
のどちらかとなります。場合によっては両方発生します。

■その1:ビデオキャプチャが59.94fpsと60.00fpsを正確に認識できない場合
そりゃあ辻褄あわせるのにどこかごまかさないとダメだよね。以上!

■その2:HDMI(映像+オーディオ)
とりあえず計算しやすくするために60.00fpsとします。数字が解りにくいだけで59.94fpsでも同じです。
ところでどうしてテレビは59.94fpsなんて中途半端な値なんだ、って思った人は、地上波アナログカラー放送が始まった頃の話をぐぐってください。どっかに書いてるかもしれません。
で、オーディオの方はとりあえず48KHzとしましょう。

つまりオーディオデータは1秒間に48000個あるわけですね。(ステレオだから96000個だろとかめんどくさいことは言わない。左右ペアのデータが48000個)
なので、60.00fpsだと、1フレームあたり48000÷60=800個のオーディオデータがあるはずです。

しかしこれが実際には、多少の誤差があります。なぜかというと、映像機器とオーディオ機器が別々の場合、それぞれの基準で時間を計るため、そこに誤差が出てきてしまうためです。PCやゲーム機から出力する場合、GPUとサウンドチップが別処理だったりするのでこうなりますね。

HDMIのデータとしては、1フレーム分の画像のあとに、「これがこのフレームと一緒になる音データ」というふうに、「約」800個のオーディオデータが送られてきます。なので、厳密に言うと、画像データとオーディオデータは同時には送信されていません。

さてこれを録画してデータ化するというのは、画像データとオーディオデータをひたすら溜めていくだけです。
するとどうでしょう。オーディオデータの個数が毎フレーム若干変動するので、いつのまにかどちらかが足りなくなってきます。
ここで、オーディオデータが足りなくなった場合、映像を1フレーム捨てることで辻褄を合わせます。
逆に、映像データが足りなくなった場合、同じ画像を2フレーム連続で保存することで辻褄を合わせます。

たとえばキャプチャソフトとして有名どころであるアマレコTVでは、その動作がヘルプに明確に記載されています。
アマレコTV ヘルプ
http://www.amarectv.com/amarectv4/manual/statusbar.html

4.録画情報
の (+) と (-)の説明です。
AVIで保存する場合、1フレーム毎にオーディオデータは何個という情報は持てないため、どうしてもこのような処理が必要になります。

また、mp4形式で直接録画する場合も、ps形式のmp4(AverMediaのほとんどの機種など大体はこの形式)であれば、同様になります。
ただし、ts形式のmp4であれば、1フレーム毎のオーディオデータの個数を保持できるため、このような調整はなく、確実に全フレーム・全オーディオデータを録画できます。
※厳密に言うとこれはps/tsの差分では無いのですが、事実上こうなっています。

■その3:HDMI(映像)+アナログオーディオ
結局のところ、その2と同じです。
アナログオーディオをA/Dコンバーターでデジタル化したあと、その2と同じように映像とオーディオを別々に溜めていきます。
すると、やはり同じ現象が発生するので、AVIもしくはps-mp4の場合、調整が必要になります。







というわけで、AverMediaの単体録画機や、アマレコTVなどのAVI系キャプチャでは、ほとんどの場合において、
フレーム欠落や追加などが起きていることになります。
つまり、世の中に大量にあるだろうそのような機材で録画された大半の動画はこういう観点では信用ならない、ということになります。
どこのノーツからどこのノーツまでが300フレームあるか、とかそういう確認には使えない、ということになりますね。
つまり新札猫にあったDIVAに繋がってたPLANTECの録画機はtsで撮れる貴重な録画機だったのだ・・・。

なので、2回同じPVを録画しても微妙にタイミングがずれている、というのはむしろこういう可能性の方が高いですね。
分配器で2分岐して同時にキャプチャ2台で録画しても、同じ物は撮れません。

という、筐体差を検証する上で、ネット上に転がっている動画は当てにならないかもしれない、というお話でした。
まあそれはそれとして、現役超ガチ勢(たぶん)の方から筐体差に関する貴重な情報を多々頂いた(しかもこの辺の話題で説明が付くような内容では無い)ので、それはそれで近いうちにまとめます。前回書いた内容を遙かに超えるレベルで筐体差は存在するようです。まあ自分で解らないからなぁ。
ゴールデンウィーク中になんか進展できるかな。。。用事あるから無理かな。。。


・メモ(というか課題)
ホールド点の増加基準
遅COOL/早COOLの判定基準
そもそもポーリングは一定間隔なのか
2019.04.23 23:29 | 固定リンク | ミク | コメント (0)
台差と時間差
2019.04.13
さてわりといきなりガチなDIVA Arcadeのお話になりますが、

このゲーム、

「台差」 と 「時間差」 

と言われるものの存在が(ほぼ確定事項として)噂されています。

詳しくはこの辺の超プロい人の説明参照。
Project DIVA Arcadeの正しい遊び方と間違った遊び方 Part 2 - こすでぃ~さんと音の出るゲーム
http://cosdivalove.hatenablog.com/entry/2017/12/20/085017


まあつまりどういうことかというと、
・台差=筐体によって点数が変わる
・時間差=なんと、同じ筐体でもやるたびに点数が変わる
というような話です。
っていうか条件が同じだからってプレイするたびに同じ点数出る方が人としておかしいだろ!

これを突き詰めるためにDIVAのコードを見たかった・・・。

で、いきなりコードですが、
14012D7DB: movss    xmm8,dword ptr [rbx+012A6Ch]   ;[rbx+012A6Ch]=0.03
14012D7E4: comiss   xmm6,xmm8
14012D7E8: ja       014012DAAB                     ;xmm6が0.03より大きかったらjmp
14012D7EE: comiss   xmm6,dword ptr [rbx+012A70h]   ;[rbx+012A70h]=-0.03
14012D7F5: jb       014012DAAB                     ;xmm6が-0.03より小さかったらjmp
14012D7FB: mov      r14d,ecx                       ;COOL(r14d=ecx=0)
14012D7FE: jmp      014012DAF8                     ;判定確定
-----
14012DAAB: comiss   xmm6,dword ptr [rbx+012A74h]   ;[rbx+12a74]=0.07
14012DAB2: ja       014012DAC5                     ;xmm6が0.07より大きかったらjmp
14012DAB4: comiss   xmm6,dword ptr [rbx+012A78h]   ;[rbx+12a78]=-0.07
14012DABB: jb       014012DAC5                     ;xmm6が-0.07より小さかったらjmp
14012DABD: mov      r14d,1                         ;FINE(1)
14012DAC3: jmp      014012DAF8                     ;判定確定
-----
14012DAC5: comiss   xmm6,dword ptr [rbx+012A7Ch]   ;[rbx+012A7Ch]=0.1
14012DACC: ja       014012DADF                     ;xmm6が0.1より大きかったらjmp
14012DACE: comiss   xmm6,dword ptr [rbx+012A80h]   ;[rbx+012A80h]=-0.1
14012DAD5: jb       014012DADF                     ;xmm6が-0.1より小さかったらjmp
14012DAD7: mov      r14d,2                         ;SAFE(2)
14012DADD: jmp      014012DAF8                     ;判定確定
-----
14012DADF: comiss   xmm6,dword ptr [rbx+012A84h]   ;[rbx+012A84h]=0.13
14012DAE6: ja       014012DAF8                     ;xmm6が0.13より大きかったらjmp
14012DAE8: comiss   xmm6,dword ptr [rbx+012A88h]   ;[rbx+012A88h]=-0.13
14012DAEF: mov      eax,3                          ;SAD(3)
14012DAF4: cmovae   r14d,eax                       ;xmm6が-0.13以上なら代入
14012DAF8: 判定確定


と、なっています。
ここの処理に来る前段階として、xmm6にジャストタイミングとボタンを押したタイミングの差分が秒単位で入っているようです。
いや、単位が秒ってのを間違いなく確定できるほど解析してませんがまあおそらくあってるでしょう。


つまり、こう。
COOL:  -0.03≦xmm6≦0.03
FINE:  -0.07≦xmm6≦0.07
SAFE:  -0.1 ≦xmm6≦0.1
SAD:   -0.13≦xmm6≦0.13
※スライドは別判定


とりあえず重要なことは、VSYNCは判定には一切関係ないということ。
うむ、それは解った。ではなぜ台差とか時間差なんて物が発生するのか。


(1)ポーリングのタイミング
判定の基準クロックとポーリングの基準クロックが同じクロック源の場合に起こりえる。

どういうことかというと、ジャストタイミングに対して、いつポーリングするかにより、
筐体Aの場合、緑矢印で示された区間(1ポーリングタイム)にボタンを押した場合にしかCOOL判定にしかならない。
それに対し、筐体Bの場合、2ポーリングタイム分の間にボタンを押すと、COOLとなる。
この図であれば、COOL判定時間は2倍となる。実際にはポーリングはもっと細かいはず。
これはあくまでジャストタイミングに対してのポーリングタイミングのずれなので、曲の開始とポーリング開始が同期していれば、毎回同じ結果になる、
なので、「この曲のここは広い」というような現象が毎回確定で発生する可能性が高い。
※あくまで「曲の開始とポーリング開始が同期していれば」である。ここが同期しているかどうかは解析できていない。

(2)ポーリング周期
判定の基準クロックとポーリングの基準クロックが別クロック源の場合、そこには多少なりとも誤差が発生する。

これもどういう事かというと、筐体AではCOOL期間にどういうタイミングでもポーリングが3回行われないが、
筐体Bでは3回行われる場合がある。ということ。
なので、微妙な誤差でCOOL判定となる時間が1ポーリングタイム分変動する可能性がある。
この現象は(1)で説明した事象も考慮すると、必ず3回になるわけでもない。
つまり、「この曲のここは広い」というような現象が発生しうる、ということ。
どこが広いか、というのは、2つのクロックの誤差に依存する。
原発振クロックはだいたいマザーボードとかIOボードとかの水晶発振子なので、
こういう情報が参考になるかと。
[水晶振動子の基礎講座] 水晶振動子の電気的特性に関する用語説明
マクニカオンラインサービス
https://service.macnica.co.jp/library/127525

これもつまり何が言いたいかというと、
一般的な製造誤差は±30ppmとか。
それに対し、温度などでの特性変化は常温なら±3ppm。つまり、一桁違う。
なので、この誤差の挙動は、筐体(ボード)製造時に決定的に決まり、温特などでの変化は少ない。
この辺が筐体差の原因可能性が高そう。
温特での変化が時間差といわれる挙動になるんだろうか。

というわけで、ポーリング周期は誰が主導権を持っているのか。Nu側であればどのクロック基準なのか。
IOボード側に主導権がある可能性もある。
あと、プレイ開始とポーリングタイミングの開始は固定なのか。アドバタイズでもボタンの読み込みはしているが、プレイ開始時に一旦リセットされるのだろうか。
とか。
うん、その辺をもうちょっと詰めたい。

まあ正直言うと、ここを気にするほどのプレイヤースキルはありません。
ってか★10クリアできてない曲とかあるしね。
DIVAの1フレ以下の挙動を体感できて、ミドルとかファームレベルのソフトの心得があって、こういうローレベルな挙動とか理論的に考えらる人材とかどっかに居ませんかね・・・。(そんな奴イネーヨ



後日に続く
2019.04.13 00:46 | 固定リンク | ミク | コメント (0)

- CafeNote -