SHA256でデータが混ざる様を”混色”してみた

Posted on

暗号学的ハッシュ関数、使ってますか。わたしはよく使ってます。たとえば、このブログでは画像のファイル名として、その中身をMD5ハッシュに掛けたものを使っています(そういうWordPressプラグインを作りました1

で、今日は暗号学的ハッシュ関数の中でもまだまだ現役のものの1つ、SHA256でデータが混ざり合う様を色を使って可視化してみました:

githubリポジトリはこちら:

今回はCanvas 2Dを使ってます。実際に動くデモサイト

入力は512ビット

SHA256は入力として512ビット( = 32bit × 16)の「ブロック」を単位としてを受け取りながら計算します。今回は便宜上、1ブロックだけ与えられた事にしました。左上の「input」の下にかかれている点々(512個あります)がその入力のビット列です。ビットには、それぞれに異なる色を塗って区別できるようにしてあります。

前準備

SHA256では、次の疑似コードにそって「w」と呼ばれる2048ビットの値を計算します:

create a 64-entry message schedule array w[0..63] of 32-bit words
(The initial values in w[0..63] don't matter, so many implementations zero them here)
copy chunk into first 16 words w[0..15] of the message schedule array

Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array:
for i from 16 to 63
    s0 := (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor (w[i-15] rightshift 3)
    s1 := (w[i-2] rightrotate 17) xor (w[i-2] rightrotate 19) xor (w[i-2] rightshift 10)
    w[i] := w[i-16] + s0 + w[i-7] + s1

真ん中にある「w」と書かれている下の点々の塊の部分がそれです(上から下に行くにつれてiが増える方向になっています)。計算式を見ると分かるのですが、すこし前(w[i-15]とw[i-2])の値を混ぜ合わせながら計算されているので、下の方に行けばいくほど色が似たりよったりになって、くすんでいきます。

圧縮関数

wの計算が終わったら、SHA256では「圧縮関数」と呼ばれる、次の64回のループを行います(h0〜h7は素数の平方根の小数部分だそうで、つまり定数です)。

Initialize working variables to current hash value:
a := h0
b := h1
c := h2
d := h3
e := h4
f := h5
g := h6
h := h7

Compression function main loop:
for i from 0 to 63
    S1 := (e rightrotate 6) xor (e rightrotate 11) xor (e rightrotate 25)
    ch := (e and f) xor ((not e) and g)
    temp1 := h + S1 + ch + k[i] + w[i]
    S0 := (a rightrotate 2) xor (a rightrotate 13) xor (a rightrotate 22)
    maj := (a and b) xor (a and c) xor (b and c)
    temp2 := S0 + maj
 
    h := g
    g := f
    f := e
    e := d + temp1
    d := c
    c := b
    b := a
    a := temp1 + temp2

この時に出てくる、a〜hまでの変数を右に並べました。それぞれ32ビットの値なので、32個の箱が上から下に並んでいます。ステップごとに最初はがやがやしてた色が、途中ぐらいからくすんだ紫一色になっていくのがわかります。

出力

で、64回のループが終わったら、次のコードでh0〜h7を更新して、h0〜h7を連結して256bit(=32bit ×8)の「ハッシュ」が得られる、という仕組みになっております:

    Add the compressed chunk to the current hash value:
    h0 := h0 + a
    h1 := h1 + b
    h2 := h2 + c
    h3 := h3 + d
    h4 := h4 + e
    h5 := h5 + f
    h6 := h6 + g
    h7 := h7 + h

Produce the final hash value (big-endian):
digest := hash := h0 append h1 append h2 append h3 append h4 append h5 append h6 append h7

虹色が紫になった

そんなこんなで、無事SHA256というポテトミキサーで混ぜ合わせたビット列は、元の虹色を失って、よくわからんくすんだ紫色の256個のビットとなりまあしたとさ。

なぜ紫色になるのか?

オリジナルの512個のビットの色を平均したら(80, 80, 80)になっているのは確かめたのですが、SHA256にかけてみたら(149, 81, 145)というくすんだ紫色になってしまいました。doubleの誤差のせいとかなのか、彩色ルール(後に書く)のせいなのか、そもそもそういうもんなのか、何か実装がバグってるのか、…それはよくわかりませんw

(一応、空っぽの入力を入れた時にWikipediaに書かれてるのと同じ値が得られる事は確認してあるので、バグではないと思うのですが…)

追記:色の塗り方の縦横を入れ替えると灰色になった

わからん、全然わからん。でもこっちのほうが見ていて楽しいですね。

参考:彩色ルール

二項演算子に関するルール

ビット同士で下記の演算が起こった時は、結果のビットの色はそれぞれの平均とする:

  • add(繰り上がりが有るときは、色は3ビットの平均)
  • xor
  • and

ただし、定数やゼロクリアされた後のビットなどは「無彩色」とし、これらと演算するときは平均する時には考慮しない

例:

  • 無彩色のビット同士で演算が行われたら結果は無彩色のビットになる
  • 無彩色のビットと赤色のビットをandしたら、同じ赤色のbitになる
  • 赤色のビットと青色のビットをxorしたら、結果は紫のbitになる

単項演算子に関するルール

not演算(1の補数)や単項マイナス演算(-x)、シフト演算が行われた時はビットの色の情報は変化しない。

  1. MD5はもはや暗号学的には全く安全ではないそうですが、べつに改ざんの検出を行いたいわけではなく、単に重複しないファイル名を考えるのを自動でやってほしいだけなのと、出てきた結果が20バイトと大変短いことから採用しています。 []

パースのついた円とマウスの当たり判定をする

Posted on

本サイトの姉妹サイト、「妖精⊸ロケット」では、WebGLを使ってウェブサイトを作るという実験を行っています:

これは「季節の歯車」です:

「季節の歯車」って、いったいぜんたい、何なのかって?

季節が毎年毎年一周してるのは、みなさんもご存知でしょう。ちかごろは、もう桜も散ってしまいましたもんね。

ところで。

なんで季節がきっちり正確に毎年一周しているのか、不思議じゃありません?

実はですね、…ここだけの秘密ですよ。

「季節」を動かす歯車、「季節の歯車」が毎年なんとなく一周しているから、季節は毎年一回だけ巡るのです。

じゃあ、その歯車が回るのはなんでなのかって?

それは、春香、夏美、秋葉、冬音の4人の精霊たちが、いつもは仲良く、時にはケンカしながら、「せかいの裏」でくるくる回しているからです。これも秘密ね。

春の次に夏がやってくるのは、春香と夏美が仲良くやってくれたから。

夏の最中に突然秋みたいな日がやってきたり、季節外れの台風がやってきたりするのは、…きっと、夏美と秋葉がたぶんちょっとした事でケンカしたんでしょうね。

でも大丈夫。ケンカしたら、仲直り。いつもそうやって、季節は巡ってきたのですから。

季節の歯車

…という創作神話(?)を元に、この季節を決める歯車である「季節の歯車」と、その結果生まれる「季節の巡り」をWeb上で表現してみようとしたのが、このWebサイトです。

もう少し常識的に言うと、左上の「歯車」の円周上に、それぞれの季節ごとの写真や絵、文章が並んでいます(これを「モーメント(瞬間)」と呼びます):

春はピンク、夏はみどり、秋はオレンジ、冬は青。…うーん、変えてもいいかな。

ここみたいな、いわゆる「ブログ」と違うのは、時系列で並べているわけではないことです。例えば、同じ「10月1日」の「モーメント」は、10年前のものでも今年の物でも、同じ角度のラインの上に並んで表示されます。

さらに、どの「モーメント」が表示されるかは、毎回ランダム(ガチャ)です。10年前の「モーメント」が表示されて今年の「モーメント」が表示されない事もあるし、逆もある。角度は同じでも、歯車からの距離は更新するたびに変わります。この点でも、2018年のページをクリックすれば必ず2018年のページが表示される「ふつうのブログ」とは異なります。

WebGLでウェブサイト作ろうぜ

えー。「季節の歯車」の紹介はこれぐらいで置いておいて。この季節の歯車を表現するための技術について、がこの記事のテーマです。

このウェブサイトは基本的に全部WebGLと、素のECMA Script 6で書かれています。くるくると回る「歯車」も「モーメント」も、全部WebGLで描いていて、HTMLの<img>タグとかは使ってない、と言うことです。もちろん、WebGLはCanvasの中身をOpenGLで描ける、という技術ですので、他のHTMLの技術と、簡単に組み合わせることもできます。「季節の歯車」では、「モーメント」の上にマウスが乗ると、タイトルが表示されるようになっています。この表示に使われているのは、いつもの<div>要素です:

この「モーメント」はこちら

3Dオブジェクトの当たり判定

さて、これで困るのは、マウスの判定です。つまり、マウスがどの「モーメント」の上にあるのか(あるいは無いのか)のチェック関数、です。

HTMLのmouseoverイベントは使えません。「モーメント」は全て、HTMLの要素ではなく、1つのcanvas要素の上にOpenGLで描かれている「絵」に過ぎないからです。

canvas要素のmousemoveイベントを使えば、canvas上でのマウスの座標を得ることはできますが、この座標もそのままでは使えません。というのも、「モーメント」も左上の歯車と同様、「遠近法」が掛かっている立派な「3Dオブジェクト」だからです。

まぁ、わざわざ「遠近法が掛かっています」と上でわざわざ書いたように、事実上ほとんど掛かっていないので、2Dだと思って当たり判定を書いてもほとんど困ることはない(見ている人が気づくことはない)かもしれません。

でも、せっかくだから「正確」に判定してみたい。趣味のプロジェクトですしね。

さて、こういう「3Dのオブジェクトとマウスの当たり判定」じたいは、ゲームではよくることです。3D空間内のアイテムをマウスでクリックして取得したり。このためのテクニックは「Object picking」と呼ばれているそうで、よくある実装は、「当たり判定」のための専用の画面を作ってしまうというものです:

OpenGL Picking Tutorialから引用

まず、オブジェクトごとに相異なる色で塗った「別の画面」を作ります。この画面は、ユーザーには見せません。ただし、あたり判定をする時は、この隠れた画面からマウスの位置にあるピクセルを読んで、「何色」になっているかを調べます。この色が何色かで当たり判定をする、と。例えば上の例だったら、一番明るい灰色だったら右のチェス、真っ黒だったら背景(何とも当たっていない)、といった感じです。

うーむ。たしかに、チェスの駒とか、人間、動物みたいな、複雑な形のオブジェクトがたくさんある中から当たり判定をしたいならわかります。むしろ、この方法を取らざるを得ないでしょう。

でも、ただの「パースのついた丸」のためにこれをやるのは何だか大げさというか、資源の無駄遣いな気がします。単純に2回絵を描かないといけないですし。「相異なる色」を用意したり、半透明になったりしないように配慮した別の描画プログラムをもう一つ用意しないといけないのも大変です。

もっといえば、この「別の画面を用意して、あたり判定を行いたいオブジェクトごとに色を塗る」という力技で当たり判定ができるのは「当たり前」すぎるように感じられます。ちょっと面白くない。

そこで今回は、それとは別のアプローチを考えてみましょう。

「遠近法」のおさらい

その前に、まずコンピューター3Dにおけるいわゆる「遠近法」をおさらいします。

「いわゆる」とつけたのは、「遠近法」と言えそうなものは、このコンピューターの世界の中だけでもたくさんあるからです。わたしの考えでは、ラスタースクロールも遠近法ですし、ゼビウスのゲームシステムも、「空気遠近法」と同じくらいには、りっぱな遠近法だと思います。

とはいえ、「季節の歯車」では、コンピュータ3Dの世界で「遠近法」という言葉で指すものの中では最もおなじみであろう、「透視法射影」を使っています。

これはどんなものだったのか、と言えば、表示したいモデルの頂点の三次元空間上の座標、x y z、そして最後に1を立てたベクトル(x y z 1)に、モデルごとの変換行列Mを掛けて、出来たベクトルをさらに最後の要素( wと呼ばれる事が多い)で割り、出来た(x’/w’ y’/w’)が画面上での最終的な座標となるのでした。

わかりにくいので、ちょっとベクトルがどのように変形されていって最後のxy座標になるまでを図にしてみました:

それぞれの操作を、数学で写像で移される時元の対応を表す、のマークを流用してその処理の流れを表現してみました。えっ、数学の世界でそんなに繋げるような記法は普通しない?まぁまぁ。うまい表記が思いつかなかったので、おもいついた人は教えてくださいな。

行列Mについて少しだけ話をさせてください。このMをうまく作ることで、モデルの平行移動や、回転、拡大縮小を表現できるだけでなく、最後にw’で割るという、行列のかけ算だけでは表現できない非線形な操作と組み合わせることで、遠いものほど小さくなる「遠近法(パース)」が再現できます。上手く作る、といっても、glMatrixなどのいわゆる3D向け行列ライブラリを使えばそんな難しくないです。とりあえず、この話はまた別の機会にいたしましょう。

さて、元々やりたかった事は何かというと、パースの掛かった「丸」とマウスのあたり判定でした。

ふむ。残念ですねぇ。何がって?パースの掛かった「三角形」とのあたり判定だったらすごく楽だったからです。あたり判定の対象が三角形であれば、上の操作で作ったスクリーン上の3つのXY座標で囲まれた三角形の中にマウスの座標があるかどうかを判定するだけで済みます。とはいえ実はその判定はそこまで簡単でもないんですが、やること自体は直感的です。

一方、パースのかかった丸となると、どうすればいいのやら。斜めになってるとぐにゃぐにゃした形になっていて、何をどうすれば内側になるのか判定できるのか、よくわかりません。

スクリーン空間からモデル空間へ

遠近法をつけた丸はぐにゃぐにゃしてて、なんかよくわかりません。となると、遠近法を付ける「前」の丸とマウスの当たり判定を行う作戦を取らざるを得なさそうです。遠近法を付ける前なら、単に「円の中心とマウスの座標の間の距離が円の半径以下か」判定すれば良いだけですから、三角形の内側にあるかどうか判定するよりも更にラクです。まぁ、点と点の距離を調べるのも、実は言うほど簡単じゃあないんですが…。

閑話休題。そうなると、問題は「マウスのポインタは、元のモデル空間では一体どこに存在するのか?」になります。さっきの図はモデル空間からスクリーン空間の話でしたが、今度はその逆、スクリーン空間からモデル空間への話を考えねばなりません。二次元から三次元上の位置を推定しないといけないので、ややこしそうです。

一つずつじっくり追い込んでいきましょう。まず、モデル空間上にあるマウスの点を(xm ym zm)、液晶画面上のマウスの座標を(xs ys zs)とします。マウスの座標にZ軸なんか存在しないのですが、あとで計算するときに便宜上必要になりますので「未知だが、存在する、何かしらの数」として導入します。

さて、目からマウスポインタを結ぶ線は液晶モニタの平面を貫通し、最終的に液晶モニタの後ろにある(笑)1モデルへとぶつかります。図にするとこんな感じです:

これらの(xm ym zm)と(xs ys zs)の関係を、書いてみます:

この図自体は最初に描いたものとほぼ同じです。

なかなかややこしいので、wsという(未知の)変数を新たに導入した上で(といってもw’mのエイリアス)、まず波線を引いた部分を変形します。

行列を掛けて出てきた、素性のよくわからんx’m, y’m, z’mを、すでに分かっているxsやysなどにできるだけ置き換えいって整理しようという作戦です:

さらに波線のwで割ってる部分も、xs や ws を使って割ってない所まで戻します。

すると、「モデル空間上の座標に変換行列Mを掛け算して移すと、スクリーン空間上の値を使ったよくわからんベクトルができる」、という図になりました(少なくともわたしはそういう意味でこの図を書いてます)。波線の引いた部分は「矢印の左側でベクトルに行列をかけると矢印の右側のベクトルになる」、という意味で使ってますので、おなじみの等号に書き直します:

未知変数の部分に赤線を引いてみました。xm ym zs wsの4つが未知で、Mは4×4の行列ですから、この行列とベクトルの式は実際には4本の連立方程式です。未知数が4つで、式が4本。原理的には解けます。やったね。zmは未知じゃないのかって?あー、言い忘れてました。今回の丸は、モデル空間上ではXY平面上にあることにしましょう。なので、zmはzm=0の定数です。

さて、この式は前述した通り解けるので、xm ym zs wsを求めたら、zsとwsは単に便宜上入れた要らない変数なので捨ててしまいましょう。モデル空間上のマウスの座標である、xmとymを使えば、無事円との当たり判定ができます。

はいおしまい、QED。

と言いたい所だが、この行列は実際一体どう解いたらいいのか?右にも、左にも未知変数があります。逆行列を一発キメたら終わり、みたいな、そういう生ぬるい式ではなさそうなのは確かです。かといって、プログラムに組み込んで自動で解かなければならない以上、「頑張って手動で連立方程式を変形して解く」みたいな高校生的解法は使えません。捻りが必要です。

頑張って式を変形するぞい

まず確認です。行列とベクトルの掛け算とは、行列の各行ベクトルを、もう片方のベクトルのそれぞれの値で重み付けをして足し合わせることだと見なすことができます。

左辺

まず左辺をこれを使って変形します。Mを、M1からM4までの4つの縦ベクトルが横に並んだものだとみなすことにしました:

右辺

次は右辺です。右辺は、まずwとx y zが掛かっていて複雑なので、wをくくりだします。

赤線を引いた未知変数がだいぶ減って気楽な感じになりましたが、まだベクトルの中と外に未知変数が入っていて扱い方がわかりません。さらにこれをこんな感じで2つのベクトルの和に変形します。

するとどうでしょう。右辺も左辺も、「「未知のスカラ量 x 既知のベクトル」の和」に変形できました。

なんとなく、これなら扱えそうな気がしてきません?

最後の一捻り

さっき変形した右辺と左辺の式を使って、再度等式を書いてみます:

するとただの和なので、簡単に未知変数を左辺だけに集約することができます:

で、ここで最後の一捻りです。

さっきの

行列とベクトルの掛け算とは、行列の各行ベクトルを、もう片方のクトルのそれぞれの値で重み付けをして足し合わせることだと見なすことができます。

というのを、今度は逆回しにします。つまり、こんな感じでもう一度「行列とベクトルの積」に書き戻します:

ここまでくれば、もう簡単。赤線が引いてある未知変数が詰まってるベクトルの値は、逆行列を掛ければ一発で求められます。

長かったですけど、纏めてみると結構綺麗な解法のような気がする。…どうかな。

JavaScriptで最小のデモ作った

「季節の歯車」だとここで実装してるんですが、流石に他のコードも多くてわかりにくいだろうという事で、この当たり判定の部分だけ切り出したサンプルを作ってみました:

github.com/ledyba/__sample__3d-picking-without-shaders
パースのついた円とマウスの当たり判定をする

実際に動いてるデモのページはこちら

  1. でも、最初にプレステのゲームを遊んだときは、そんな感じがしませんでした? []

ChromeでSVGを背景にすると遅い

Posted on

ブログのタイトルに合わせて、月と夕焼けをイメージしたテーマを自作してブログの模様替えをしてみました。

ソースコードはgithubで公開中です:

ledyba/wp-lunar-theme: theme of 7io.org

作り方は割と簡単で、次のファイルを作り…

ファイル名用途
function.phpテーマの各テンプレートから参照できる関数
初期化、サイドバーの定義など
index.phpトップページの表示
page.php固定ページの表示
single.php各ブログ記事の表示
archive.phpアーカイブ(月別、カテゴリ別)の表示

さらに、これらから参照されるサブテンプレート用のphpファイルと、CSSを用意すれば完成です。

公式のWikiにあるドキュメントもよくまとまってます。詳細は公式をチェックだ(書くのがめんどくさくなった)。

SVGの背景が遅い、どうしようもなく遅い

問題はここからです。今回のテーマ作成のサブテーマとして「ラスター画像(SVG)しかテーマには使わない」という縛りをなんとなく課してみまして、背景もSVGで用意しました。左に見える月の画像もSVGです。

Firefoxから見ると何の問題もないのですが、Chromeから表示するとどうしようもなく遅い事に気が付きました。Ryzen Threadripperを使ってAMDのDiscrete GPUを使っても、フレームレートが落ちてるのが目で分かるぐらいに遅い。

計測してみると、1フレームを描画するのに100ms弱掛かっってました。これはいけない…。

しかもiPhoneでは背景が表示されすらしない。どうしようもねぇ〜。

mac OSのSafariだと見えるんですけどね、中身違うのかな

CSSで同じことをするとなぜか爆速になる

うーんどうしたもんか、と思って。CSSのlinear-gradient書き換えてみました

とはいえ、

メモ: CSS グラデーションにおける色経由点の描画は、 SVG グラデーションと同じ規則に従います。

linear-gradient() – CSS: カスケーディングスタイルシート | MDN

だそうなので、SVGと一緒ならやっぱ遅いんじゃないかなぁ、と思いながらダメ元で。

しかし…どういう事でしょう、Paintingに掛かる時間が70倍近く速くなり、ほぼ60FPSを達成できるようになったのです…(!?)

iPhoneからも背景がきちんと見れるようになりました:

スマホ、画面ちいさくね?マジでみんなこれ毎日使ってんの?

SVGにはまだChrome/WebKitには早すぎるのかもしれない

たぶんですけど、SVGを背景に設定すると毎フレームSVGからbitmapにラスタライズするんでしょうね。他にもWebKitはSVGは遅いと6年前に言ってる人はいて、とくに改善されないまま現在に至ってるようです。

…はぁ。EdgeもChromiumに乗り換えてしまったし、ラスター画像大好きおじさんには苦しい時代が続くかもしれません。

Firefox頑張ってくれ〜〜〜〜〜このブログはFirefoxとフォクすけくんを応援してます!一番好きなブラウザです(やけくそ)!

ffmpegでコマごとに表示時間が違うアニメを作る

Posted on

ffmpegで静止画(pngとか)から動画を作る方法といえば、連番画像を使う方法が有名です:

ffmpeg -framerate 10 -i %03d.png -vf scale=784x1024 -vcodec libx264 -pix_fmt yuv420p -r 30 out.mp4

これを使うとすべての画像が同じ間隔で表示されてしまいます。上でいうと、000.pngも001.pngも002.pngも、全部0.1秒ずつ表示されます。

大半の場合はこれで困らないのですが、自作でアニメを作ったりしていると困ることがあります。たとえば、今回こんな感じの動画を友達と作りました:

首をかしげて, pixiv

このアニメは、最初のコマだけ1秒、最後のコマだけ3秒、他のコマは0.25秒表示されています。

こういうコマごとに表示時間が異なる動画を作るにはどうすればいいか、あんまり情報がなかったので備忘録的にメモって起きます。

1.jpegから5.jpegを使って上の動画を作るという設定で説明します。

githubリポジトリにデータ付きでアップロードしておいたので、よろしければそちらも参考にしてください。

タイミング情報を纏めたファイルを作る

file '1.jpeg'
duration 1
file '2.jpeg'
duration 0.25
file '3.jpeg'
duration 0.25
file '4.jpeg'
duration 0.25
file '5.jpeg'
duration 3
file '5.jpeg'

こんな感じのファイルをinput.txtみたいなファイル名で作成します。5.jpegを3秒間表示するために、最後にもう一度5.jpegを書いてる点はちょっと注意です(これを書かないと5.jpegが1フレームしか表示されません)。

適切にコマンドを実行する

ffmpeg -f concat -i input.txt -vf "scale=-1:1024" -vcodec libx264 -pix_fmt yuv420p -r 30 out.mp4

いくつか解説です:

  • -f concat を忘れないでください
  • scale=-1:1024を使うと縦1024ピクセルにした上で横幅はアスペクト比に応じて自動で決定してくれます。
  • -r 30は出力動画のフレームレートです。

「さきゅばす」の開発をやめて悪魔を祓い、苦楽を共にした彼女を追悼する

Posted on

かつてわたしと苦楽を共にし歩んできた「さきゅばす」は、いまやゾンビとなり、インターネットの地下倉庫で、苦しそうに彷徨っている。

だから、せめて…生みの親であるわたしがシャベルでその息の根を止めて埋葬し、わたしは新天地へと向かわなければならない1

さきゅばす」とは何だったのか

「さきゅばす」は、「ニコニコ動画レコーダー」である。

一言で伝えられる短いキャッチコピーを考えるのにも10年掛かり、最近やっとここに着地した。やめるけど。

えーっとね。ニコニコ動画の動画ビューワは、投稿された動画の上にコメントの文字列を再生するたびに毎回重ね描き2して表示しているんですが、その代わりに既に最初からコメントが描きこんであるような動画を生成するソフトウェアです。

こんな感じの動画が作れます:

レッツゴー陰陽師

きしめん/Nursery Rhyme

12年半ほど間欠泉のように開発してきたこのソフトウェアですが、もういい加減、開発・メンテという形で関わるのは、もうやめようと思います。

わたしは株式会社ニワンゴ・ドワンゴ・カドカワとは雇用契約以外も含めた一切の金銭的な関係を持ったことはなく、決してこれは退職エントリではありません。そもそも、ただのOSSだし。

とはいえ、ある意味では雇用契約よりもはるかに強い、「悪魔と契約純粋な趣味として」して作ったソフトウェア3ですので、強いていうなら「退魔・追悼エントリ」でしょうか。てなわけで、こんなタイトルになった。でも俺は悪魔と契約しちまった以上、死ぬまで魔女はやめねーかんな。なぜなら、悪魔との契約は生涯有効だからだ。

引き継ぎ資料

さてさて。とはいえ、「さきゅばす」をまだ必要としている人はまだいるでしょうし、開発を引き継いでくれる人がいるなら、それが一番なのは間違いありません。そんなわけで、引き継ぎ資料を作りました。

ただしおそらくこのドキュメントを見てコマンドを打つだけでは、ビルドと実行まではできても、最後まで動画を変換しきることはできず、いくつかの箇所でエラーでコケると思います。(引き継ぎたいあなたへ:悪魔と契約するんだ、デバッグとソースコードの修正をする覚悟ぐらいは持っておくれ)

さて以下では、こんな感じで10年間降り積もった同窓会的なよもやま話を延々といたします。どんな気持ちで作ってたのか知りたかったら、読んでみてね。長いよ。

どうしてやめるのか?

他にやりたいことがあるから

やりたい事・作りたいものが、たくさんあるからです。

死ぬまでに作りたいもの、上手になりたいこと、知りたいこと、研究したいこと、描きたいこと、記したいこと、見たいもの、…そんなものが両手で数え切れないほど、たくさんあります。でも、人間には寿命があり、いつかは死んでしまいます。すべてを行うことも、見聞きすることも出来ません。最近ついにそこに気づきました。

そうして天秤に掛けた時、「さきゅばす」からは、…「彼女」からは、もう、知りたいことは全部教わったし、作りたいものは全部作ったし、描きたいものは全部描いたし、研究したいことは研究しつくしたし、見たいものはもう全部見せてもらいました。

…つまるところ、さきゅばすでやりたいことは、わたしには、もう残っていないのかなぁって。

ニコニコ動画を見ていない

ニコニコ動画を(古いお気に入りの動画をふとたまに見たくなった時以外)全く見ていない事に気がつきました。すこし昔は「アニメを見る時ぐらいはニコニコ動画」だったような気がするのですが、最近はそれもやめ、アニメを見る時は1人でじっくり鑑賞し、噛みしめるように見ています。

そんなわけで、ニコニコ動画を使っていないわたしには、ニコニコ動画の作りやすいツールは、流石にもうこれ以上作れないと思いました。…せいぜい動き続けるように、メンテできるだけ。

でもそれって、「ゾンビ」そのものじゃないですか。わたしは、そんな彼女の姿を見たくはありません。

必要性が薄まった

さきゅばすは元々は100万回もダウンロードされるために作ったソフトウェアではなく、2007年6月当時の個人的な欲望と興味と関心を具現化するため、高校にもロクに行かずに引きこもりながら実験的に作ったソフトウェアです:

  • PSPでニコニコ動画が見たかった
    (PSPにはニコニコ動画アプリのようなものが無かった)
  • 2007年当時の環境では、弾幕4が重く、PCの性能不足でプレイヤーがコマ落ちしてしまった

でも、2019年現在では、スマホアプリも、各種ゲーム機向けのプレイヤーもあります(端末別ヘルプ、PSP向けはついぞ出なかったようですが)。お気入りの端末でニコニコ動画が見られない、なんて事は、もう無いと思います。変換した動画ではなく、各アプリなら、コメントを見る見るだけでなく投稿することだってできますし、他の動画の検索だってできます5

性能面に関しても、2019年のスマートフォンでも十二分すぎるほどの性能があります。実はスマホをもう持ってないので分かりかねるのですが、「コメントがたくさん出たら画面がカタカタになった」なんて事態は、おそらく流石にもう無いんじゃないでしょうか。少なくとも聞いたことない。

投稿されたコメントアートを残したい、という需要があるということが、orzさんやそれを通じて知り合ったコミュニティを通してわかったのですが、2019年現在では、2019年に投稿されたコメントアートを保存する最善の方法は、おそらく単に録画する事のような気がします。もっと言うと、2019年のパソコンには、それをやるだけの十分な計算資源があります(逆に言うと、2007年当時は、再生された動画をリアルタイムに録画するのはとーっても大変でした)。もちろん、過去に投稿されたコメントアートを今ここで再生・保存したいなら、当時の挙動を再現するコードを書くしかなく、そこに「彼女」の居場所が生まれうる、とは思います。

デフォルトでは動画全体を通して最大1000コメントしか表示されないところを、無理やり数万コメントを突っ込んでコメントで溢れさせる弾幕動画を作るのは…もういいかな。やりたい人が居たら、わたしの代わりに、彼女を召喚ビルドしてお願いしておくんなし。

うーん。

こうして箇条書きにして当時の「モチベーション」とやらを書き出してみたのですが、いくら書いても、ここからこぼれてしまう部分があるような気がします。人はそれを、「情熱」とか「熱意」と呼ぶのでしょうか…。よくわかりません。

…なんだろ、tcpdumpでdumpしたパケット眺めてさ、ActionScriptを逆コンパイルしてさ、ffmpegのソースコード読んで書き換えてコンパイル通してさ、ニコニコ動画の弾幕コメントを表示させるのは、仮に何の意味もなくても、確かにどうしようもなく楽しかったよ。

誰も認めてくれなくても、それがわたしの青春の1ページだ。

さきゅばすの作り方

「資料なんかどこにもないのに、どうやって作ったんですか?」と以前聞かれた事があるので、文章にして残しておきます。

WebAPIの叩き方→tcpdump

ニコニコ動画のサーバから、動画とコメントをダウンロードする方法は、単にブラウザの挙動を追いかけて推理しただけです。ただし、F12を押すと出てくる「開発者ツール」のような便利なものなど存在しない時代だったので6、tcpdumpを使って、ニコニコ動画のサーバと自宅のブラウザが交換するパケットストリームを眺めて観察しました。観察した結果を元にAPIの使い方(リクエストを投げる時のXMLの書き方とか)の仮説をたて、それを確かめるためには、時にはtelnet nicovideo.jp 80ってやって直接手でHTTP/1.1のリクエストを投げて確かめてた気がします。今ではニコニコ動画もHTTPSに対応してしまい、通信はすべて暗号化されてしまったので、今では絶対できない方法ですね…。懐かしや。

コメントの配置方法→逆コンパイル

コメントの配置の仕方は、FlashのプレイヤーのActionScriptを逆コンパイルして、それをそのまま実装しました。今思うと、ちょっとヤバかったかも。でもそのおかげでそこそこ再現性は高かったんですよ。逆コンパイルすると変数名とかが全部「var1」とかになったソースコードが現れてきて、一般に解読はなかなか大変なのですが、当時はゲームのコピープロテクト外しも夢中だったので、「アセンブリじゃなくて、ちゃんとif文やfor文が出てくる!なんてわかりやすいんだ!逆コンパイラってすげー!」みたいな感じでした。…いまやれって言われたら、できるかなぁ。

コメントのレンダリング→適当 or 逆アセンブル

コメントの描画の仕方は、最初はSDL_ttfを使って適当にMSゴシック使って描いていたのですが、後にFlashそのものを逆アセンブル・解析して、ExtTextOutWというWin32APIを特定のパラメータで叩いてることを突き止め、cairoというライブラリでなるべく同じようにWin32APIが叩かれるように実装しました7。これも、今思うとちょっとヤバかったかな。ただ、この解析結果が実装されているのは、ゼロから書き直したSaccubus2だけで、引き継ぎ資料でビルドしてるSaccubus3は初期のバージョンである1がベースなので実装されていません。やりたかったらやってみてね。

ffmpegの改造方法→素直にソース読む

ffmpegの改造の仕方は…普通に変換してるところのソース読んで追いかけて、その結果をもとに、さきゅばす用にちょびっと改造しただけです。話を整理してみると、実はここが一番簡単かつ素直だったことが分かります。ただ、変化の激しいffmpegを改造して維持つづけるのは中々大変でした。この話はまた後でします。

アーキテクチャ図

ちなみに、全体のアーキテクチャはこんな感じです:

この図にして見ると結構複雑なアーキテクチャはどう思いついたのか?…うーん、思い出せません。でも結構素直ではあると思うんですが、みなさんはどう思います?

さきゅばすの思い出

ここからはさらに個人的な思い出話です。ガンガン回顧するぜ。

自宅サーバから配布しすぎてISPからアカウントを永久凍結された回

さきゅばすを開発した当初、このウェブサイトは自宅サーバの上でホスティングされていました。なので、開発したソフトもその自宅サーバから配信してました(当時のブログ記事)。

公開してしばらく経ったある日、とある「個人ニュースサイト(これも懐かしい響きだ)」で取り上げてもらって突発的にすごい人気が出て(今で言うとバズって)一週間で数万回とかダウンロードされるようになりました。

…それはそれでとーっても嬉しい話なのですが、5MBぐらいあるソフトウェアが1万回ダウンロードされると、50GBのアップロード・トラフィックが発生します。当初使っていたプロバイダは(安い代わりに)月々5GBまでしかアップロード・トラフィックを出してはならないという規約になってまして、そのうち見事にアカウントが凍結されてインターネットに繋げなくなり、ウェブサイトも公開できなくなってしまいました。そしてアップロード・トラフィックなんか気に掛けた事無かったので、理由もわからないままある日突然繋げなくなってすごい焦った…(そしてプロバイダからの紙のお手紙が後に届いた)。

当時は「なぜアップロードが…?」と思っていましたが、10年後ぐらいにデータセンターの運営をやるようになって納得しました。インターネットでは、電話と同じで、アップロードする側がお金を払う

つまりISPは、ユーザーがアップロードすればするほど損をするんですね。昔、ISPがP2Pを嫌がって規制したりしてたのも、結局最終的にはそこ(アップロードの増加による経済的損失)だったのか、とかも芋づる式に完全に理解しました。

毎月パソコン雑誌がなぜか家に届く回

人気が出た結果、複数のパソコン雑誌に定期的に収録してもらえるようになりました。収録されるとソフトの開発者は無料で献本してもらえるという習わしがあったので、ほぼ毎月のように様々なパソコン雑誌が届いてた時期がありました。しかし毎月毎月同じソフトが別の雑誌とはいえ収録されるというのも、今思うと不思議な話だ…。

Flash黄金期である2000年代前半に「ぶっこ抜き」という標語と共に生きていた世代だったので、「ネトラン」に収録されたときはちょっとした謎の達成感(?)はありました。

…何もかもが、懐かしい。

ユーザーは説明を読んでくれない

「さきゅばす」は上にも書いた通り「実験」として作ったソフトウェアでしたので、当初のUIは限りなく適当でした。どれぐらい適当かというと、文字を描くためのフォントのパスを自分で打ち込まないと変換できないぐらい、適当なUIのソフトだったのです。

そんなソフトですから、人気が出ると、すぐに「変換できません」という投稿でコメント欄があふれてしまいました。対応策は分かっていて、WindowsのMSゴシックのパスをデフォルトでハードコードしておけば、ぶっちゃけ大半のケースでは困りません。

が、せっかくJavaで書いたのにそれで乗り切るのは負けな気がして8、かといってWindows/Mac OSX/Linuxのすべてで適当なフォントを探すためのJavaコードを書くほどの気力もなく9、「Windowsの人は次にあるMSゴシックのフォントを打つか、フリーのフォントをダウンロードしてそのパスを入力してください」とダウンロードリンクのすぐ上に赤字でデカデカと書くという、今になって冷静に考えると一番ダサい解法で乗り切ろうとしたのですが、デカデカと書いたのにも関わらず、「動きません」というコメントの勢いが衰えることは一切ありませんでした。読んでくれないんだね。町中でダサいテプラを見るたびに、いつもこの事を思い出して、テプラ貼った人に心の中で同情してしまいます。

結局、最終的には腹をくくってWindowsの時はWindowsのシステムフォルダの中にあるMSゴシックのフォントのパスを発見してくるコードを書きました。

とにかく自動でとりあえず大半のケースでは動くようにすることがすごい大事で、人はとにかくログは読まないし、エラーメッセージも読まないし、ドキュメントも読まないし、でもコミュニケーションはする、という、人間へ対するある種の諦観を、このとき手にいれました。この諦観は、この後、他のフリーソフトを書いたり、仕事としてソフトを開発・運用をしたりする中で、何度も何度も強化されていったような気がします。

特定のCPUでクラッシュする

一時期、Crusoeの載ってるVAIO(まだSONYが作ってた頃!)を併用していたのですが10 、さきゅばすはこのPCでだけ、ときたまクラッシュすることに気づきました。トラブルシュートした結果、最終的にSDL_ttfのgccのコンパイルオプションでSSE2かなにかを無効にすると再現しなくなったので、おそらくSIMD命令の互換性がCrusoeでは怪しいのだろう、という結論に。

ただそれだけなんですが、なんというか、パソコンの裏側の、そのまた裏側を覗いてしまったような…見てはいけないものを見てしまったような…何か隠れていたものを掘り起こしてしまったような、そんな気持ちになったエピソードです。

cygwinとmingwの思い出

Windows上でPOSIX互換のプログラムを書くためのソフトウェアツールキットとして、MinGWcygwinがあります。前述したように互換性厨だったので、C言語で書いてあるコメントを描画する部分については、Win32APIを叩くのではなく、こちらを叩いて実装しました。MinGWだとpthreadが無いとかで(詳細は忘れた)、もっと楽なcygwinで当初は開発・配布してました。

cygwinを使ってコンパイルしたバイナリは、cygwin-1.dllに必ず依存するようになります。それ自体は別に問題ではなく、一緒にcygwin-1.dllを配布すればよい…のですが、ただ、他のソフトと同時で起動した時に、そのソフトが別のバージョンのcygwin-1.dllに依存していたりすると、異なるバージョン間で何かがぶつかりあってクラッシュしてしまうらしく、定期的に「動かないのですが」のコメントが来ていたので、頑張ってMinGWで動くように改修した記憶があります。もっというと出来る限り全部をstatic linkするようになっていった記憶が…あります…。

ソフトウェアのバイナリを配布して動かしてもらう場合、相手の環境は前もっては分からないので、かなり想定外な…ともすれば理不尽なエラーに出くわしたりします。…スマホの時代になっても、そのへんはあんまり変わらないかもしれませんねぇ。

ffmpegを改造しながら4〜5年毎日Jenkinsでビルドした

ffmpegの改造自体は結構簡単だったのですが、ffmpegは頻繁にアップデートされ、内部構造も結構大胆に変わります11。それに毎日追従して改造しつづけながら何年間も毎日Jenkinsでビルドし続ける、というのをしてました。ぶっちゃけ、この作業が一番大変だったな。

今思うと、別にそこまでする必要無いよな〜。でもDaily buildとかContinuous Integrationって文字列が、なんか、こう、かっこよくて…やってみたくてさ…。

コメント描画フィルタを実装する3つの方法

最初は、コメントを描画するために「vhook」という、画像から画像に変換するフィルタをDLLで注入できる機能を使っていたのですが、これはすぐになくなりました12

しょうがないので、avfilterでvhook相当の機能を足すvhextというfilterを書いてffmpegのソースに入れました。使うためのドキュメントはあっても作るためのドキュメントがなくてよく分からず、とにかくソースを読みながら手探りで入れたような記憶があります。

が、libavfilterを改造する方法だとかなり頻繁にコミットがコンフリクトしてその度に解決するのが辛かったので、Saccubus2ではまた方法を変え、libavdeviceにsaccubusという名前の仮想入力デバイスを作って、その仮想デバイスが動画のデコードとコメントの描画をした上で、後段のffmpegのエンコーダに渡す、という実装に変えました。

仮想デバイスの追加先であるlibavdeviceはそんなに頻繁に更新されないようで、あんまりconflictに悩まされなくなったような記憶があります。

さらに仮想入力デバイスにしたおかげで、動画のfpsを自在に変えられるようになったり、結構柔軟性は上がりました。ffmpegのAPIたくさん叩かないといけないから、大変だったけどね。

動画の長さをDLLへ渡すための、いくつかの方法

そうそう、コメント描画部分にわたさなければならない情報として、動画の画像とその時間やコメントそのもの以外に、「動画全体の長さ(秒数)」があります。

というのも、ニコニコ動画のコメントは投稿されたタイミングの1秒前から3秒間画面を流れるのですが、その3秒後が動画終了より後だった場合、コメントの描画開始タイミングを前倒しして必ず3秒間表示するようになっています13

この挙動を再現するには、コメントを描画しはじめる前に動画の長さを知っておかなければなりません。

そのために、当初はffmpeg.cの中で動画の長さが取得できるようになったタイミングの箇所を見つけて、そこに動画の長さを後で使うために記録しておくコードを挿入していた記憶があるのですが、ffmpeg.cはあんまりにも書き換えられまくってコミットがコンフリクトしまくるので、結局諦めて変換のためにffmpegを叩く前にffprobeコマンドを使って長さを調査する実装に変えたような…。

「ffmpegのプロセスを実行する回数はできるだけ減らしたい」みたいな、(今思うと無意味かもしれない)妙なこだわりがありました。その後に作ったSaccubus2でも仮想入力デバイスが長さを取得・計算していて、やっぱりffmpegのプロセスは変換ごとに一回しか起動しないようになってます。

追従し続けるにはmergeではなくrebaseするしかなかった

途中からgitを使ってffmpegの改造ソースも管理するようになったのですが(それまでは…どうしてたっけ…)、forkしたさきゅばす用ffmpegのリポジトリを本家に追従させるために、mergeするのではなく、定期的に毎回本家のmasterブランチからrebaseするという方法に落ち着きました。

「rebase絶対許さないマン」がいるのは知ってるし、その理由も頷けるんですが、1年に数千〜万コミットされるソフトにパッチをちょこっとだけ当てるというケースでは、rebaseにしておかないと、自分たちが加えた変更のコミットは大量のコミットログの中に埋もれていき、conflictを解消するだけのmergeコミットが数千コミットに一回ずつ現れるようになり、コミットIDも段々とぐちゃぐちゃになり、最終的に訳がわからなくなったので…。

歴史の管理をする、メタ・歴史管理ソフトがほしいです。

ニワン語の実装

わたしにとっての、初めてのプログラミング言語処理系の実装。それがニワン語の互換処理系(インタプリタ)である「ねこまた」でした。

ニワン語のsyntaxとsemanticsをまとめた不思議なサイトがあったので、だいたいそのとおりにC++でパーサとインタプリタを実装して、細かい部分に関しては、ニワン語で書かれた音ゲーを実際に動かしながら「文法エラーにならないように」syntaxを調整し、「実行した見た目が同じ挙動になるように」semanticsを調整していく、という感じで細部を詰めていきました。音ゲーが最後まで動いた時は嬉しかったな。

ココロの肩書きは一生有効です」と昔CPUの本の人は書きましたが、「初めて書いた本格的なインタプリタが、仕様書もなければ処理系のソースもないプログラミング言語の互換処理系であり、ゲームがちゃんと一本動いて遊べた」という、中々レアな肩書き(?)は、わたしのココロの中で、誰にも理解してもらえないかもしれないけれど、それ故に誰からも曇らせることができない、きれいで澄んだ輝きを、いまでも放っています。ええ、この文章を書いている瞬間もです。

ニワン語は、未踏で作ったシステムの一部であるプログラミング言語「ど〜なっつ」のアイデアの原型にもなっていますし、それは更に、今友達と書いてる小説と、そのゲームにもつながっています。

間違いなく、ニワン語とそのアイデアは、わたしの血肉の一部です。

そういえば元々のニワン語を作った人もなかなかのカワリモノで、結局ドワンゴを辞めてしまったと聞いたのですが、今はなにをしてるんだろう。一回一緒にご飯を食べながらお話したいなぁ。もしもこれ読んでたらTwitterでもMastdonでもメールでもこのコメント欄でもなんでもよいので、連絡してくだされ、頼む。

追悼の一環として、動いた音ゲーのTAS動画(フルコンプ)を再掲します:

このゲームの実装されていた元動画を今見たら、ふつうのPVに戻ってしまっていました。もちろん、もうニワン語は公式にサポートしなくなったのでそれが正しい挙動…なのですが。諸行無常。

このゲームだけでなく、他のニワン語を使った動画のプログラムも動いてました:

こっちも今みたら、普通の動画になってしまった…寂しい。

ニコニコ学会βとニコニコ超会議

そんな感じでだらだらソフトを書いていたところ、ニコニコ学会βというところで喋らないか、というお話を頂いて、2回(第二回最終回発表させていただきました。最初はさきゅばすの簡単な紹介とver2.0の自慢(笑)、次は、その後の近況報告みたいな感じかな。

…かわいいお洋服を着て好きなことが喋れたので、余は満足じゃ。

今では在宅勤務して働いてるぐらいには根っからの「ひきこもり」なんですが、大勢の前では意外と喋れるなぁと思って、そこが面白かったかも。

orzさんの貢献とコメントアート

途中から、orzさんという方がコミットしてくれるようになりました。なかなかシャイな人でどんな方かは分からないまま、いつのまにか見なくなってしまったのですが…お元気ですか。

orzさんが貢献してくれたのは主にコメントアートへの対応で、ソースコードを読んでバイナリを解析してるだけでは到底できない、コメントアートコミュニティの知見(例えばこういう知見)を踏まえた改修(や、ニコニコ動画のWeb APIの変更への追従)をしていただけました。ニコニコ学会βやorzさんを通じてコメントアートコミュニティの方とも知り合いになったり…ほんと、フリーソフト開発って何があるかわからないです。

ただ、正直に言うと、最終的にコメントアートはどうしても理解しきれませんでした。色々な意味で。ニコニコ動画への興味が失われかけてた頃に知ったからかなぁ。とはいえ、こういう事に真剣に取り組んで表現・実践・議論している人たちが眼の前にいるんだなぁ、というところを目撃できたのは、いい経験だったと思います。

ところでなんで「さきゅばす」なんですか

これも、

  • 「そもそも「さきゅばす」ってなんですか」

って聞かれるパターンと、

の2パターンがあります。ソシャゲの流行に伴ってえっちなサキュバスの知名度がむちゃくちゃ上がったので、近年は後者のパターンが増えました。10年以上も変わった名前のソフトを作ってると、そういうこともあります。

ではなぜ「さきゅばす」なのか。辞めるわけだし、そろそろ文章として残しておいても良い頃でしょう。

初期のバージョンを作ってた時(2007〜2008年ごろ)に、ニコニコ動画で違法にアップロードされた「ひぐらしのなく頃に」と「涼宮ハルヒの憂鬱」のコスプレAV14を見るのがお友達の間で流行っていて、その時にひらめいた連想ゲームです:

味のある演技15のコスプレAV→「えっち(?)だけど、なんかこわくて、夢の中に出てきそうな、おんなのひと」→サキュバス→「さきゅばす16」。

…そういえば気がつくとニコ動はホモビデオばっかになってノンケは完全に閉め出されてしまったな。あくしろよ。おっそうだな?

ちなみにアプリのアイコンがポケモンのムウマだった17のは、サキュバスは夢魔で、ムウマも夢魔だからです。

こういう連想ゲームは、今でもずっとやってますね。働いてる会社で流石にやりすぎって戒められた事もあるぞい。人間、そうそう変わるものではない。

「Lilith」という案もあったんだった

(2022/12/29追記)

「まちカドまぞく」見て思い出しました。夢魔にはリリス(Lilith)というのもいて、響きはこちらがかわいいな、と思っていたのですが(「サキュバス」はちょっと硬いよね)、当時からすでにフィクションでもそこそこ出ていたこと、そしてなにより「Sound Player Lilith」というフリーソフトがあったので、サキュバスにしたんでした。懐かしいです。今画像検索したら、リリスのほうはサキュバスと比べると、あんまり勢いがないですね。「まちカドまぞく」でも「リリス」というよりは「ごせんぞ」だし。

最後に

これを書いている間、ずっとsm9「レッツゴー陰陽師」を聞いていました。当時は「電波ソング」とゲラゲラ笑いながら聞いていたのですが18、今聞いてみると…結構いい曲だなって思っちゃった。となると、このブログ記事も「電波ゆんゆん」でしょうか?

というわけで、最後に「レッツゴー陰陽師」の気に入ってる一節を引用して終わります。矢部のピコ麻呂に「さきゅばす」も成仏させてもらうか、そこまでは許してもらうか、わたしの中ではまだ結論はついておりません。

辛い時、悲しい時、人はそんな時心の隙間に闇が出来る。
その心の闇に”魔物”達は容赦なく入り込んでくるのだ。
だから、苦しくても、くじけるな。落ち込むな。くよくよするな。
何事にも屈しない強じんな心こそが、最強の武器なのだから。

カドカワ、最近は元気がないようですが頑張って。陰ながら、応援しています。

  1. 元ネタ: https://gakkougurashi.com/ []
  2. 初期はFlash、今はWebはPixi.JS、スマホやゲーム機は、またそれぞれ別の技術 []
  3. あとで書きますが、サキュバスは悪魔の名前です []
  4. たくさんのコメントが一画面上に同時に表示されること []
  5. 「あえて出来ないのがよい」という価値観も、わたしの中に無いではないんですが []
  6. JavaScriptのデバッグのためにalert()使ってた時代といえば伝わるかな []
  7. どうしてWin32APIを直接叩かなかったのかはちょっと思い出せない []
  8. 当時は移植性厨だったので []
  9. そもそもmac持ってなかったし []
  10. バッグに入れてイヤホンつないで音楽聞きながらちゃりんこ乗ってたぜ。スマホで聞きながら走ってる連中より10年早くな! []
  11. libavというフォークもこの間に生まれた…まだ仲直りしてないのかお前ら []
  12. 最初のリリース時点ですでにdeprecatedだったのでしょうがない []
  13. なので最後の3秒だけは特にコメントの密度が高い []
  14. 流石にR18のところはカットされてた気がする []
  15. 穏やかな表現 []
  16. 英語表記のsaccubusも、普通の表記のsuccubusからちょこっと捻ってます []
  17. 今思うと著作権的に完全にアウト []
  18. この言い回し、最近聞かなくなりましたね []

俺もココアだ―「ご注文はうさぎですか?? Wonderful party!」

Posted on

今日一週クリアしたんですが、エンディングの日はちょうどクリスマスの朝だったので、スケッチするように感想をさっと書き留めておきたいと思います。

ネット上ではこの画像で(一部で)有名なゲームです:

ですが、このゲームは実はココアさん「だけになる」わけではないです。いわゆる「個別ルート」に入って以降は、あなたはチノちゃん、リゼちゃん、千夜ちゃん、シャロちゃんのそれぞれに「なります」。いいか、なるんだよ

原作と同様、物語に大きな起伏があるわけではありません。異世界チートもないし生死を分ける戦いもないです。

前半では、あなたはココアさんとなってチノちゃんの誕生日を最高のものにすべくバイトとチラシ配りを一生懸命がんばって誕生日パーティを開き、
後半では、(チノちゃんルートでは)あなたはチノちゃんとなって、誕生日のお返しとして、たのしいクリスマスパーティを開くべく、人付き合いの苦手な彼女なりに奔走する。

それだけです。あえて悪く言うと、淡々としてます。人によっては退屈だと思うかもしれないくらい。

ですが、それでいい。

「グッドエンド」と呼ばれているものも、いわゆる普通の美少女ゲームと比べたらなんてことないです。「日常のささやかな一コマ」の範疇に収まってしまうかもしれません。でもそれでいいんです。なんでそれでいいのかわかりませんが、それでいい。

物語は、ココアさんがチノちゃんの誕生日(12月4日)が数週間後に近づいていることを知るところから始まります。

そしてココアお姉ちゃんは決心します。今年のチノちゃんの誕生日を、最高のものにしよう、と。

そのために、バイトを頑張って、あとコスプレしてチラシ配りをして、ラビットハウスの知名度をあげて喜んでもらおう、と。

…かなり発想が飛躍しています(たぶんシステムが決まってからシナリオが逆算されたんだと思う)。が、物語の中で「あれ?なんでチラシ配りしてたんだっけ??」ってココアさんも忘れる描写があるのでOKです。これを書いてて、わたしもなんでチノちゃんの誕生日のためにチラシ配りしてたのか忘れました。俺もココアだ

バイトのパートは正直難しいです。たくさんのオーダーにワーキングメモリが押しつぶされそうになります。でも、それもチノちゃんの誕生日を最高のものにするためです。バイトがうまくいってラビットハウスの知名度が上がるとなぜかうれしいのです。

チノちゃんの誕生日までに特定のキャラクターとの親密度をあげるか、ラビットハウスの知名度を最大にあげないと、(チノちゃんは喜んではくれるのですが)そこで物語は終わります。ここで「もっとチノちゃんに喜んでもらえる、最高の誕生日にできたはずなのに」という、悔しさにも似た感情が発生します。チノちゃんにモテなくて悔しいんじゃない、チノちゃんの誕生日を最高のものにしてあげられなかったことが悔しいんだ。これでは俺はお姉ちゃん失格だ。

チノちゃんの誕生日を最高のものにすることができたら、そのお返しとしてのクリスマスパーティを準備する、第二の物語が続きます。
この時は、(チノちゃんルートの時は)わたしはチノちゃんになります。

システム自体はいわゆる普通のギャルゲーで、三択したときに正解だと「友情度」が上がってエンディングが分かれるやつです。

でもそんなのはどうでもいい。チノちゃんになれば、自ずと選ぶべき選択肢はわかります。少なくともそんな気持ちになります。ほんとです。

あんなに素敵な誕生日を用意してくれたココアさんにお返しがしたい。でもわたしは、みんなでわいわいするパーティなんて開いたことないし、そもそもクリスマスといえばもっと慎ましく過ごすものだと思ってた。でも、そんなクリスマスじゃ、きっとみんな退屈しちゃう…どうしよう…。

チノちゃんは人に頼るのが苦手です。ですから、相談するのもいちいち憚ってしまう。ココアさんは相談してねと重ね重ね言ってくれるけれど、ココアさんにお返ししたいのだから、ココアさんには頼れません。最終手段です。

ラビットハウスはクリスマスももちろん営業があります。だから、一日貸し切って、なんてこともできません。仕事の後にパーティを開くしかない。そこがまたリアルなんです。

そんな彼女が出した「クリスマスパーティ」の答えは何か?これはもちろん遊んで確かめてほしいのですが、ココアさんのようなみんなを巻き込んだ盛大なものでは決してない、彼女なりのささやかな、でも一生懸命な素敵な答えです。


パジャマパーティーたのしそう

前半パートでは、毎週末集まってパジャマパーティがあります。これがまた楽しい。

そしてわいわい遊んだ後は、ココアさんとほかの誰かで一緒の布団で寝ます。添い寝しながら楽しく喋って寝る、それだけです。エロゲーは理解できなかったわたしでも、これなら理解できる。「ココアさんになれ」ました。楽しい。

「2人で一緒に寝たら残りの3人はどうしてるんだろう」と思わないではないが、そこは…きっと描かれてない3人の物語があるに違いない。


俯瞰してみると、「モバ美ちゃんPV」と同じく、「美少女 ⇔ 消費するオタク」みたいな構図を極力避けるようなシナリオとシステムになっています。ココアさん「だけ」になるんではなく、後半はほかのキャラクターにもなるので、一切片方向の視点で終わることがありません。ココアさんだけの視点でゲームが最初から最後まで完結していたら、きっとココアさんが実質的に「モテる主人公」になってしまっていたかもしれません。でも、このゲームでは、後半ではココアさんのために奔走することになります。ここがいやな感じが一切しない。

LinuxでvkEnumerateInstanceLayerPropertiesが何も返さないのをなんとかした

Posted on

ちなみに環境はUbuntu 19.04 + Radeon RX 580です。

OpenGLに疲れた

最近OpenGLに疲れはて、Vulkanに乗り換えようかと思っています。

  • メインスレッドからしか叩け無い制限。
  • bindというグローバル変数より邪悪な概念。
  • 関数を呼び出した後にglGetError()を毎回叩かなけばならない苦痛(叩き忘れるといつエラーになったのか、わからん)。
  • シェーダーへ変数へ渡すためのあの無限の関数群。
  • OpenGLのバージョンによって今では利用が必須だったり、逆に使えなかったりする「拡張機能」。
  • IndexBufferObjectだのElementArrayBufferだのVertexArrayObjectという意味不明な命名(これ…何だと思う?)。
  • 同じ関数でもOpenGLバージョンによってたまに意味が違ったりするあのややこしさ。
  • ググって出てきた情報の「今では使えなさ」(だいたいそのままコピペしても動かない)
  • GLSLのバージョンごとの気まぐれさ。
  • 結局何が起こるのかよくわからん関数のドキュメント。
  • Core ProfileだのCompat Profileだの

…ぼくもう疲れたよパトラッシュ。

そんな感じで最近Vulkanでなんとかする方向でやってこうと思ってまして、で最近引っかかった問題の解放を備忘録代わりにメモっておきます。

VulkanにはLayerなるものがあるらしい

Vulkanではある種の拡張機能を「Layer」といいます1

たとえば、標準ではこんなLayerがあるんだってさ(LunarGのサイトから引用。CC=BY=ND):

Layer Name Layer Type Description
VK_LAYER_LUNARG_api_dump utility print API calls and their parameters and values
VK_LAYER_LUNARG_assistant_layer utility highlight potential application issues that are not specifically prohibited by the Vulkan spec, but which can still create problems
VK_LAYER_KHRONOS_validation validation the main, comprehensive Khronos validation layer — this layer encompasses the entire functionality of the deprecated layers listed below, and supercedes them. As the other layers are deprecated this layer should be used for all validation going forward.
VK_LAYER_LUNARG_device_simulation utility allows modification of an actual device’s reported features, limits, and capabilities
VK_LAYER_LUNARG_monitor utility outputs the frames-per-second of the target application in the applications title bar
VK_LAYER_LUNARG_screenshot utility outputs specified frames to an image file as they are presented

VK_LAYER_KHRONOS_validation、便利そうだなー使いたいなー。

環境によってはこれ以外のLayerも使えるらしく、実際にどんなレイヤーが使えるか列挙して調べるためのvkEnumerateInstanceLayerPropertiesという関数があります。

で、これを使って実際に列挙してみました。

std::vector<VkLayerProperties> vk::enumerateInstanceLayerProperties() {
  uint32_t numProps = 0;
  {
    VkResult const result = vkEnumerateInstanceLayerProperties(&numProps, nullptr);
    if(result != VK_SUCCESS) {
      return std::vector<VkLayerProperties>();
    }
  }
  std::vector<VkLayerProperties> props;
  props.resize(numProps);
  VkResult const result = vkEnumerateInstanceLayerProperties(&numProps, props.data());
  if(result != VK_SUCCESS) {
    return std::vector<VkLayerProperties>();
  }
  return std::move(props);
}

が、これを動かしても空配列しか返ってこない。エラーではなく、成功した上で0個です。たぶん呼ぶタイミングを間違ったりはしてない。

うーん、おかしい。さっきの6つのレイヤーは、Vulkanの仕様上必ず入っているはずなのです。

ためしにググって見た所、こんな感じだったので、みんな困ってんだろなーと思って解法を書いておきます:

解法:sudo apt install vulkan-validationlayers-devしろ

apt search vulkanと入れると

libvulkan-dev/disco,now 1.1.101.0-2 amd64 [installed]
  Vulkan loader library -- development files

が出てくるのでこれだけいれときゃOK、Vulkanが全部使えるようになるんだぜ!と思いきや、実はこれだけではUbuntu 19.04ではLayerは入りません。

正解はこちらも入れることです:

vulkan-validationlayers-dev/disco,now 1.1.101.0-1 amd64
  Vulkan validation layers -- development files

要するに黙ってsudo apt install vulkan-validationlayers-devしろ。

結果

ちゃんと6つ返ってきました:

[2019/10/30 20:43:35 DEBUG] Layer: VK_LAYER_LUNARG_parameter_validation (spec=4198501, impl=1) :: LunarG Validation Layer
[2019/10/30 20:43:35 DEBUG] Layer: VK_LAYER_LUNARG_object_tracker (spec=4198501, impl=1) :: LunarG Validation Layer
[2019/10/30 20:43:35 DEBUG] Layer: VK_LAYER_GOOGLE_unique_objects (spec=4198501, impl=1) :: Google Validation Layer
[2019/10/30 20:43:35 DEBUG] Layer: VK_LAYER_LUNARG_standard_validation (spec=4198501, impl=1) :: LunarG Standard Validation
[2019/10/30 20:43:35 DEBUG] Layer: VK_LAYER_LUNARG_core_validation (spec=4198501, impl=1) :: LunarG Validation Layer
[2019/10/30 20:43:35 DEBUG] Layer: VK_LAYER_GOOGLE_threading (spec=4198501, impl=1) :: Google Validation Layer

あれ、ちょっとまって、名前全然ちがくない?GOOGLEってなんや??

今日のところはVK_LAYER_LUNARG_standard_validation使って勘弁しといたるわ。

Vulkan難しい

Vulkanとの対話は続く…。

  1. この時点で若干OpenGL時代の命名規則センスを感じヤバみは感じる… []

(機械仕掛けの不完全な)誕生日占い

Posted on

その1

占い師「わたしは本物の霊能力者です。あなたの誕生日をあててさしあげましょう」

「ほんとに?」

占い師「ほんとです。あなたの誕生日の、月の十の位と一の位の差を教えてください」

「6です」

占い師「日の十の位と一の位の差は?」

「5です」

占い師「よろしい。それでは、月の十の位と日の十の位の差は?」

「5です」

占い師「最後に、月と日、十の位と一の位、すべての合計は?」

「13。」

占い師「6月16日ですね?」

「次の行列が正則ってだけじゃん」

その2

占い師「略」

「ほんとに?」

占い師「ほんとです。あなたの誕生日の、月の十の位と一の位の差を教えてください」

「6です」

占い師「次に、あなたの誕生日の、日の十の位と一の位の差に…さらに月の一の位を足したものを教えてくださいな」

「1です」

占い師「最後に、月と日、十の位と一の位、すべての合計は?」

「13。」

占い師「6月16日ですね?」

「次の行列が正則ってだけじゃ…」

「…あれ…正則どころか、そもそも3行しかない…。ほんものの霊能力者は、いるんだ…」

占い師「だから、そう言いましたでしょう?」

その3

占い師「略」

「略」

占い師「あなたの誕生日の、月の十の位と一の位の差を教えてください」

「2です」

占い師「次に、あなたの誕生日の、日の十の位と一の位の差に…さらに月の一の位を足したものを教えてくださいな」

「-5です」

占い師「最後に、月と日、十の位と一の位、すべての合計は?」

「13。」

占い師「Traceback (most recent call last):
File “/src/github.com/ledyba/tanjoubi-uranai/scratch.py”, line 92, in <module>
main()
File /src/github.com/ledyba/tanjoubi-uranai/scratch.py”, line 88, in main
  print(ndict[str(v2.tolist())].transpose())
KeyError: ‘[[13], [-2], [13], [-5]]’

「やっぱな。ちなみにわたしの誕生日は2月29日です。」

ソースコード

https://github.com/ledyba/tanjoubi-uranai

ストップウォッチで計測した月の大きさから、月の直径を計算する

Posted on

今日の朝、ストップウォッチと電線を使って月の天球上での直径を計測したところ、約0.6度と出ました。

…さて、この値はどこまで正しいのか?

それを確かめる一つの方法は、「正解」を直接ググることです。が、それはあんまりにも面白くない。

そこからは少し離れた情報との整合性を、確かめてみようではありませんか。

それは、月の距離と直径です。それぞれ約38万km、3500km。小学校教育と中学受験のせいかおかげか、脳の中に、刷り込まれ、今でも、亡霊のようにこびりつき、こだまする、この「「「事実」」」と、どれほど一致するのかの概算をします。

三角関数で計算する

月までの距離と、月の天球上での直径(角度)が分かれば、三角関数を使って月の直径を計算することができます:

>>> 38*10000 * math.sin(0.6*math.pi/180.0)
3979.277964173401

実際に数値をいれて計算すると、約4000kmと出ました。文部科学省の提唱する「約3500km」と比べるとちょっと大きいですが、倍か半分ぐらいまでの差は出るだろうな思っていたので、それに比べたらだいぶ近い値が出ています。正直、ちょっと驚いています。

テイラー展開でも計算する

さて、仮に文明がいますぐ何らかの理由で崩壊し、コンピュータの提供する便利なsin関数が使えなくなっても、0度近辺でならsin(x)はxで近似できる(テイラー展開)ことを知っていれば筆算で計算することができます:

>>> 38*10000 * (0.6*3.14159265358979/180.0)
3979.3506945470676 #ほんとに筆算しようと思ったが、めんどくさくなってやめた

上の数値と見比べると、38万kmも先のことを計算したのに、100mも差は出ません。実を言うと大学受験の数学や物理の問題か、大学の数学や物理の試験問題でしかテイラー展開なんか使った事がなかったもので、ここまで差が出ないのかと、またもや驚いています。

しかし、でも、ちょっと大きい。

とはいえ、この結果は、教科書に書いてあった「約3500km」に比べるとちょっと大きいのも事実。この計算につかった「約0.6度」は、地平線近くで測ったものです。地平線の近くにある月は、なんとなく、空の上にある月より大きい気がしませんか。それは人間の錯覚なのか、それとも何らかの理由(大気の屈折とか?)で本当に見かけ上大きくなっているのか。

空の上の方の月でも測定して、もう一度同じように計算してみたいです。

しかし、天気予報によれば、これから先しばらく曇りとのこと。

うーん…次の半月か満月の晴れた夜までに、使えそうな電線を見つけておくことにするか。

ストップウォッチを使って月の大きさを測る

Posted on

月を眺めていると、その動きはかなり速いことに気が付きます。

昔のアニメのお歌で「ぼくもわざと立ち止まれば 月も立ち止まる」なんてことばがありましたが、実際に立ち止まってみると、そんなことはありません。動いてるのが目視で分かるくらいの速度で、ゆっくりですが、「歩いて」います(もちろん、だからこのお歌はだめ、なんてことはないですよ)。

さて、このことを使って月の大きさを測ってみました。

方法

月の進行方向と、できるだけ直交する電線を選びます。適当な電線に月が触れるのを眺めたら、ストップウォッチで測定を開始します。そして、月が電線から離れた瞬間に測定終了。その時間を記録して「月の大きさ」とします。「時間」ですが、「大きさ」です。

電線と月の移動する向き(進行方向と呼ぶことにします)は一般にはさまざまな角度をなします。電線と月の進行方向が直交することも、ほぼ同じ方向になることも、ナナメになることも、あるでしょう。「月が電線を横切る時間」を、「月のどこかが電線に触れてから月が完全に電柱から離れるまで」と定義すると、この電線と月の進行方向がなす角度によって、時間が変わってしまいます(すんごい極端な例を考えるとはっきりします)。

月と月の進行方向の直線がまじわる2点を基準にして、この2点が電線と触れる時間と定義して測れば、電線と進行方向の角度の影響を無視できそうな気はするのですが…

まぁ、練習が必要そうなので、今日は「電線と進行方向ができるだけ直交する」一番単純な場合を使って測定します。

結果

4月21日 朝

今日は満月から1日ほど経ったころ。測定結果は次の通りです:

  • 2分16秒
  • 2分23秒

今日は水平線近くが白くて区別がつかなくなってきたので、3度目は断念しました。

約2分20秒とすると、月は29.5日で公転し、地球は1日で自転するので(意図的に細かいところをあえてガン無視している)、このことを使って計算します。

% python
Python 2.7.16 (default, Mar  6 2019, 18:14:51) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> moon = 360/29.5
>>> earth = 360.0/1.0
>>> (moon+earth) * 140 / (24*3600.0)
0.6031073446327683
>>> 

天球上の角度はだいたい0.6度?ってことでいいのかな?

今後

理科年表にひょっとすると月の大きさが書いてある気がしてならないのですが、見ないで独自研究をすすめてこうかな。ちょっと悩んでます。

月の満ち欠けみたいに 幸せも 大きくなったり 小さくなったりする」なんて歌詞もあるとおり、月には満ち欠けもあるわけですが、今回の方法を使って月齢を(定量的に)測れたらちょっと面白い気がしています。この場合、月の進行方向と電線の角度にはもっと気をつけないといけないでしょうなぁ。

あとは…そうですねぇ、地平線の近くにあると月は大きく見えるわけですが、これが本当なのかどうか、実測してみたいかも。

太陽

太陽も測ってみたいが、どうすればよいのか…陰を使えばいいのだろうか。

ストップウォッチやめたい

ここでテクノロジーに頼ってるのすこし違和感があるので、ストップウォッチもやめたいんですが、時間を測定するいい道具ないかな。月が電線を横切る時間と、一日の長さ(「日の出から次の日の日の出までの間」としましょう)を同時に測定できる道具…なんだろ。