SVX日記

2004|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|04|05|06|07|08|09|10|11|12|
2025|01|02|03|04|05|06|07|

2004-06-11(Fri) ネコのお守り

  今日は、カミさんの実家が旅行で留守のため、ネコのお守りに出動。

  画像の説明

  一晩中、激しい雨が降っていたため、SVXが水没した夢を見る。気に入っている車が水没したら泣くよなぁ。

本日のデータ
走行+13.3km51,027km
備考ネコ迫力ありすぎ

2005-06-11(Sat) 「スバル3300」のオイルを交換する

  ノビノビになっていたが、ホームセンタでSVXのオイル交換を行う。フト気になって前回のオイル交換がいつだったか調べてみたら、正月に55,095kmで交換していた。半年弱ってトコか。しかし半年で2000kmチョイしか走っていない。通勤に使ってないし、最近は遠出もしていないとはいえ、もう少し、走ってあげたいトコロだな。

  画像の説明

  ……と、車を預けてしばらくしたら、放送で呼び出された……ハイハイ。ポンコツ好きのオイラですからね、慣れてますよ。どーせ、どっかが壊れてますとかいわれるんでしょ……と、思いつつピットに行ったらアンノジョーであった。「なんかのオイル」が漏れているらしい。たぶんパワステじゃないかというコト。それからATフルードがウスくなっているらしい。ウスいという表現もよくワカらんが、もーすぐ車検だし、その時にまとめて面倒見てもらうことにしよう。

  でもね、オイラはオイルがダダ漏れでなければ、継ぎ足し継ぎ足しして乗るコトに抵抗ないんだよね。実際、以前に乗っていた三菱のジープは、あろうことかブレーキフルードを毎日継ぎ足して乗っていたし……サボるとブレーキが効かなくなるので、その時はテクニックが必要だったケド。ブレーキのウォーニングランプが、パーキングブレーキの外し忘れ以外でも点灯するってコトに気づいたのは収穫だったな……。

  ブレーキでもそんな調子なんだから、極端な話、パワステオイルなんてナシでも構わんのだ。ちょっとハンドルが重かろうが、そんなの関係ないのである。オイラは駐車場で据え切りなんかしないし。ジープの細い重ステハンドルで手首は鍛えられてるし。

  余談はともかく、オイラのSVXは今までの経験だと、オイルの交換から2500km程度でアイドリング時のバタつき感が増してくるコトが多いので、今回はイチバンいいオイルを入れてみた……といっても、工賃込みで2890円なのだが。まぁオイラは高いオイルを長くというよりは、安いオイルをマメに交換する派なので、そんなので十分なんですわ。

  そんなこんなで再び交換作業を続けてもらいながら、ホームセンタを一回りしつつ、何かと工作に使いがちなアイテムを物色する……と、なんと電子工作コーナーを発見!! LEDとかジャック、各種プラケース程度の品揃えではあるが、秋葉までワザワザ買いに行くコトを考えたら……と、思ったのだが高ぇッ!!

  画像の説明

  おいおい、RCAジャックは秋月だったら1個35円だぞ。コレ3つ買っただけで、ほとんど交通費が出ちまうじゃねーかよ。まぁココで売ってもほとんど売れないだろうからこーゆー値付けになるんだろうが、コレを工作好きな子供が買うと考えたらちょっと痛々しいなぁ。もーすこしなんとかならんモノか。

  画像の説明

  オイル交換を終了し、エンジンがスムーズに回るようになったSVXでカミさんの実家へ。お茶飲んでPCのメンテ。このPC、以前に謎の症状を示したためにCDROMを外してあるので、先日買った100円のジャンクに入れ替える。よっしゃ、動くようになった。動くようになったのはいいが、なんだか全体にスッゴク重い。Celeron800MHzなのに、体感133MHz以下という重さだ。HDDのフラグメントを疑ったが、解消してもまだ重い。描画か? ベンチマークしてみると、DirectDrawの値が0。いくらなんでもコレはないだろ……ビデオドライバを入れ替えて、もう一度ベンチマーク。DirectDrawの値が19に向上し、かなり動きがスムーズになった。こんなのアリか? まったくもって謎のPCである。最後にWindowsUpdateをかけて、作業終了。

  画像の説明

  今日の収穫は、なにかと工作に利用しそうな、うすめ液と瞬間接着剤。しかし缶に入っていると、どーにもウマそうに見えてしまうのはよろしくないな。そのうち酔っ払ってグイッとあおる前に、食卓の上からは片付けておくことにしよう。うえっぷ。

  余談だが、あらためてオイル交換作業票の車種の欄を見たら「3300」って書いてあった。車を預ける時も「スバル車ですね」なんて言われたし……気のイイ兄ちゃんだったけど「SVX」に誇りを持っているオイラとしてはちょっと面白くないッ!! 「スバル360」や「三菱3000」じゃあるまいし、ウチのSVXを「スバル3300」として扱わないでちょーだいッ!! キーッ!!


2009-06-11(Thu) カバードコア撃破

  というワケで、ようやくカバードコアを撃破した。つーか、PCエンジン版グラディウスIIって、いろいろAC版より激しくないか?

  目立つのは、怒りモアイ。ボスのビックモアイともども、なにも、そこまで怒らんでも。AC版以上の数を吐いてきている気がする。とても、8ビット機の仕事とは思えんくらいのリング量だ。

  それから、ボスラッシュ面。のっけから、ザブはヤバいくらい出てくるし、ビックコアはフェイントかけてくるし、ゴーレムは微妙に速くて追い詰められそうになるし、テトランは腕の先から出す弾が避けづらいし、ガウは堅めで長期間レーザーの雨を避けなきゃならんし、イントルーダも分離してから今一歩ネバるからアセらされるし、カバードコアのミサイルに至っては当たり判定がデカい上に速度が速い……気がする。

  画像の説明

  なんにせよ、カバードコア撃破は喜ばしい……がッ!! タイトルがゴーファー撃破でないトコロを察してくれ。サボッてボタンのアサインを逆にしてたせいで、ポーズして写真撮ったこの直後、ミサイル発射するつもりで、オプションを装けちまった……

  「グラIIは20年経っても、まだまだ遊べるってことよッ!!」

  ……これは文化だ。国営マンガ喫茶の次(?)は、国営ゲームセンタを、ゼヒ。


2014-06-11(Wed) 帽子を被りたいなら、勝手にしやがれ

  画像の説明

  かなりイメージ通りの仕上がり。被り心地も悪くない。しかし、ちょっと派手かな。ホントはパナマを試したかったのだが、ボルサリーノを買えば数万するし、取り扱いが難しそうだし、ということで、これだ。

  しかし、職場にコレ被っていくのに、ちょっと躊躇している。見渡す限り、帽子を被っている人がいないんだよな。元来、スーツ姿は帽子なしでは完成しないもの、ともいうが。

  聞くと営業職では帽子はタブーらしい……とはいえ、ウチのドレスコードに抵触するわけでもないし、来週くらいから被っていってみようかしらん。


2022-06-11(Sat) タイヤのシュー理、スケルティウス製作中


2024-06-11(Tue) 太郎くんはサーキットを時速100kmで走ることにしました

  一段落してしまった気分でしばらく放置してあった自作のレースゲームだが、近所のサーキットを走ってみたくなったので開発を再開することにした。

  まずは画面切り替えを故意に遅延していたのを是正する。厄介なのは地図タイルをネットワーク越しに得る都合上、故意でなくても少なくない遅延があることだ。処理を集中することなく、段階的に処理が進むよう、こんなコードを書いてみた。

draw: ->
    n = 0; while(n < @tile_reqs.length)
        tile_req = @tile_reqs[n]
        unless(tile_req[3])
            tile_req[3] = new Image                     # Image インスタンスを生成
        else unless(tile_req[4])
            tile_req[3].src = tile_req[2]               # Image.src を設定
            tile_req[4] = true
        else if(tile_req[3].complete)                   # 読み込み完了?
            @constructor.pats[0] = tile_req[3]
            @put_source_plane(@constructor.pats, 0, tile_req[0] << 8, tile_req[1] << 8) # ソースプレーン(回転前)に転送
            tile_req[5] = true                          # キューから削除せよ
        if(tile_req[5]) then @tile_reqs.splice(n, 1) else ++n

  要するに1フレームで複数の処理をすることを避け、分散させるわけだ。まだ試作段階なので、分散のアルゴリズムにはチューニングの余地があるが、とりあえずそれほど大きくはツッカカることなく走り続けられるようになった。

 

  例によって、録画の都合で15FPSになっているが、実際は60FPSで動いている。マップの更新遅延と多少のガクつきは実際にあるが。

  そこで改めて表題の問題だ。現状スペースキーを押すと固定速度で前進走行するようになっていて、それは0.3という適当な値を与えている結果なのだが、いったい時速何kmで走っているのだろう。別に「ゲームなんだから考えません」という方向性もあろうが、以前に書いたように「実際に遊んだときに実際のF1のラップに近いタイムが出るようにしたいと思っている」のであれば、その辺をキッチリさせる必要があるのだ。

  まずは、0.3で走っている現状の速度を求めてみよう。どうやるか。実際にコースを一定距離走らせ、経過時間を求めて算出すればいい。そうなると長い直線を持つコースが望ましいが……そうだ、昔、自分が働いていたつくばの土木研究所の試験走路を走らせてみよう。

  それより、ぼちぼちコースを選択するインタフェイスも欲しくなってきたな。URIにクエリで与えられると実用的だが、CoffeeScriptの場合どう受け取るんだろう。こうやんのか。

query = new URLSearchParams(window.location.search)
switch query.get('course')
    when 'silverstone'
        lng_d1 =   -1.022485; lat_d1 =   52.069097; v =  91 # シルバーストン
    when 'marinabay'
        lng_d1 =  103.864217; lat_d1 =    1.291532; v =  58 # マリーナベイ
    when 'doken'
        lng_d1 =  140.072549; lat_d1 =   36.115692; v = 184 # 土木研究所
    else
        lng_d1 =  136.540617; lat_d1 =   34.843099; v = 164 # 鈴鹿

  なんか懐かしいなぁ、って思いながら車を走らせていると、視界が恐ろしく狭いことに気づいた。試験走路から一般道に出て、当時の寮に帰ってみようと思ったのだが、どこを走っているのかわからず、帰れない。仕方ないので、より小縮尺のグーグルマップをすぐ右に別に開いて、それを参照しながらドライブしてみた。つうか、これは「ラリーX」じゃないのかw? それにしても、これだけでもだいぶ楽しいな。気づくと土浦学園線、土浦ニューウェイを通って土浦駅まで走っていってしまっていた。何をやってんだオレわw。

  画像の説明

  試験走路に戻ろう。とりあえず、わかりやすい目印を決めてそこまで走ろう。スタート地点からそこまでの距離を、グーグルマップの「距離を測定」機能で求める。1.03km。で、ストップウォッチを片手に、車を走らせる。29秒。「1.03km / 29s x 3600s = 127.9km/h」。おぉ、おおよそ時速100kmだったんだな。

  んが、ここからが問題。時速100kmになる「値」はいくつなのか。0.3より少し小さいはずだが、ここは安直に比率で求めるのではなく、理詰めで求めたい。

  まずは0.3が127.9km/hになった理由を求める。0.3は256分率表現で76.8。60FPSなので4608/s。ズームレベル20なので4ビット右シフトして288ピクセル/s。24ピクセルが2mなので24m/s。時速にすると86.4km/h……あれ? いや、地図の側を緯度補正して24ピクセルを2mにしてるんだから、速度の方も補正しなければならないのか。「86.4km/h x 256 / 178 = 124.26km/h」。おぉ、だいたい同じになった。計算は合っていそうだ。

  これを逆にして100km/hから求めればいい。「100km/h * 178 / 256 =69.53km/h」。秒速にすると19.31m/s。24ピクセルが2mなので232ピクセル/s。ズームレベル20なので4ビット左シフトして3708/s。60FPSなので61.8。256分率表現なので0.241。ということになる。んが、毎回こんな計算をするのは無駄なので、計算順序を入れ替えて定数にまとめてしまう。「0.3472 * 178 *1.0 / 256 = 0.241」緯度が変わったら178を変更し、200km/hにしたければ1.0を2.0に変更すればいいわけだ。

  念のため、土木研究所の試験走路でやった速度の算出を、シルバーストン、鈴鹿、マリーナベイでやってみたが、ちゃんと100km/hで走っているという結果が出た。よっしゃよっしゃ。

  これができたら、あとは鈴鹿のF1の走行映像からコーナリング速度を読み取り、ハンドルの効きを調整。それができたら、予選のラップタイムに近くなるように加減速の性能を調整。という段階を踏むことになるだろう。

  んが、その前にCourseクラスとMyCarクラスで個々に計算している緯度補正値をWposクラスに持っていくリファクタリングが先だな。まだまだ先は長そうだ。


2025-06-11(Wed) ゲームパッドの入力処理が「理解」されてしまう

  自製のレースゲーム「TopDrivin'」の開発を地味に進めているのだが、一風変わった操作方法にするつもりだ。オイラはずっと前から、レースゲームに「アクセルボタン」って要るか? って思ってた。

  クルマの運転には、主にステアリングとペダルとギアチェンジレバーを使う。実車では手と足を使うのに、ゲームコントローラだと手だけ。足りるわけがない。だからオートマになってしまう。違うだろ。大事なのはギアチェンジだ。むしろアクセルペダルなんて基本ベタ踏みなんだからいらない。ギアチェンジで速度調整、ブレーキ(アクセルオフ)で微調整。だから、シフトダウン、ブレーキ、シフトアップの3ボタンでの操作のがベターだと思うのだ。

  で、いきなり細かい話になるが、レバー操作とブレーキ操作はレベルセンスでいいが、シフトのアップ、ダウン操作のためにはエッジセンスが必要だ。つまり、押した瞬間だけを検知する。離したこと、押し続けていること、は検知しない。実際のF1マシンのパドルシフトもそうだ。押し(引い?)たら1段階シフト。「ドゥ、ドゥ、ドゥ、ドゥーン」という連続シフトダウンの操作は、絶対に再現したい。

  キーボードの入力処理は簡単だ。キーダウンイベントの発生を検知すればいい。しかし、ゲームパッドのボタンにはダウンイベントはない。レベルを見てエッジセンスする必要がある。とりあえず、汎用性は必要ないから、処理効率よくコンパクトに書きたい。で、書いた。うまく動いた。

  書き上げたところで、ふと、AIを試してみたくなった。これ、何の処理を書いたのか、わかる? って。

以下のコードについて説明して下さい。
p_dirs = ['PLEFT', 'PRIGHT', 'PUP', 'PDOWN']; p_ndirs = p_dirs.length
p_trgs = ['PTRGA', 'PTRGB', 'PTRGC']; p_ntrgs = p_trgs.length
padsense = ->                                                   # ゲームパッドからの入力
    pads = navigator.getGamepads()
    (il = inputs['LEVEL'])['PLEFT'] = il['PUP'] = il['PRIGHT'] = il['PDOWN'] = il['PTRGA'] = il['PTRGB'] = il['PTRGC'] = false; ies = inputs['EDGE_SENSE']
    for n in [0...pads.length]
        continue unless(pad = pads[n])
        dn = 0; for an in [0..pad.axes.length - 1]
            pad.axes[an] < -0.5 and il[p_dirs[dn % p_ndirs]] = true; dn++
            pad.axes[an] >  0.5 and il[p_dirs[dn % p_ndirs]] = true; dn++
        tn = 0; for bn in [0..pad.buttons.length - 1]
            pad.buttons[bn].pressed and il[p_trgs[tn % p_ntrgs]] = true and ies[p_trgs[tn % p_ntrgs]] |= 1; tn++
    for trg in p_trgs
        (ies[trg] &= 0b11) == 0b01 and inputs['EDGE'].push(trg)
        ies[trg] <<= 1
このコードは、**ゲームパッドからの入力を取得して、入力状態やエッジ(押
された瞬間)を検出する**処理をしています。CoffeeScript風の構文で書かれ
ているため、JavaScriptに似ていますが、少し簡潔な書き方になっています。
 
### 🔧 **全体概要**
 
* ゲームパッドの入力(十字キーやボタン)を取得し、
* どの方向やボタンが押されているかを判定し、
* 「押された瞬間」(立ち上がりエッジ)を検出して `inputs['EDGE']` に追加しています。
 
### 🔹 **定義と初期化**
 
```coffeescript
p_dirs = ['PLEFT', 'PRIGHT', 'PUP', 'PDOWN']
p_ndirs = p_dirs.length
 
p_trgs = ['PTRGA', 'PTRGB', 'PTRGC']
p_ntrgs = p_trgs.length
```
 
* `p_dirs`: アナログスティックや十字キーの入力方向を定義。
* `p_trgs`: 押しボタンに対応する識別子(トリガー)。
* `p_ndirs`, `p_ntrgs`: それぞれの数。
 
### 🔹 **関数定義 `padsense`**
 
```coffeescript
padsense = ->
```
 
* ゲームパッドの状態を調べて入力情報を更新する無名関数です。
 
### 🔹 **ゲームパッドの取得と初期化**
 
```coffeescript
pads = navigator.getGamepads()
(il = inputs['LEVEL'])['PLEFT'] = il['PUP'] = il['PRIGHT'] = il['PDOWN'] = il['PTRGA'] = il['PTRGB'] = il['PTRGC'] = false
ies = inputs['EDGE_SENSE']
```
 
* `pads`: ブラウザから接続中のゲームパッド配列を取得。
* `inputs['LEVEL']`: 現在の入力状態を保持(どの方向やボタンが押されているか)。
* `inputs['EDGE_SENSE']`: 押された直後を検出するためのバッファ。
 
### 🔹 **アナログスティック/方向キーの処理**
 
```coffeescript
for n in [0...pads.length]
  continue unless(pad = pads[n])
  dn = 0
  for an in [0..pad.axes.length - 1]
    pad.axes[an] < -0.5 and il[p_dirs[dn % p_ndirs]] = true; dn++
    pad.axes[an] >  0.5 and il[p_dirs[dn % p_ndirs]] = true; dn++
```
 
* 各ゲームパッドを確認し、
* スティックの軸(axes)ごとに、
 
  * -0.5 未満 → 方向の一つ(例: 左)を押されたとみなす
  * 0.5 超 → 別の方向(例: 右)を押されたとみなす
 
※ `dn % p_ndirs` によって 4方向にうまく割り振っている。
 
### 🔹 **ボタンの処理**
 
```coffeescript
tn = 0
for bn in [0..pad.buttons.length - 1]
  pad.buttons[bn].pressed and il[p_trgs[tn % p_ntrgs]] = true and ies[p_trgs[tn % p_ntrgs]] |= 1; tn++
```
 
* 押されているボタンに対応する `PTRGA`, `PTRGB`, `PTRGC` に `true` をセット。
* 同時に `EDGE_SENSE` ビットを立ててエッジ検出に備える(OR 1)。
 
### 🔹 **エッジ検出処理**
 
```coffeescript
for trg in p_trgs
  (ies[trg] &= 0b11) == 0b01 and inputs['EDGE'].push(trg)
  ies[trg] <<= 1
```
 
* 直近2フレーム分の押下状態を 2bit で追跡。
* `0b01`(前回は押されていなかった、今回押された)=立ち上がりエッジ → `inputs['EDGE']` に追加。
* 次のチェックのために1ビット左シフト。
 
### 🧠 **補足:ビットシフトでのエッジ検出**
 
`EDGE_SENSE` を2bitとして使っており、
 
* 過去2回分の押下状態を記録:
  例)
 
  * `00`: 押されていない
  * `01`: 今回押された → エッジ(立ち上がり)
  * `11`: 押され続けている
  * `10`: 離された(エッジ検出なし)
 
### ✅ **まとめ**
 
このコードは、ゲームパッド入力を毎フレーム更新して:
 
1. スティックやボタンの現在の入力状態を `inputs['LEVEL']` に反映
2. 立ち上がりエッジ(押された瞬間)を `inputs['EDGE']` に追加
 
する仕組みです。リアルタイムなゲーム操作などに使える基本的な入力処理パターンになっています。
 
もし `inputs` の構造や、この関数が使われる文脈についても知りたい場合は、補足のコードを共有していただければ、より詳しく解説できます。

  ……おぃおぃ……完璧な説明じゃないか、これは。特に、見りゃわかるレベルセンスの処理への言及は軽く、ちょっとわかりづらいエッジセンスの処理について丁寧に正しく説明している。そもそも「エッジセンス」を正しく理解しているということ自体が驚愕だわ。

  そりゃ「EDGE_SENSE」とは書いちゃってるけど……変数名も隠しておけばよかったかなぁ……なんだかくやしい。つうか、上で「理解している」なんて書いたけど、AIに「理解」を感じてしまっている時点で負けてんじゃねぇか。だって、AIに「理解」なんて概念はないだろう?

  以前に「多項式からその回答が導き出される理由がわからない」と書いたけど、逆に大層に感じているオイラが頭の中の「理解」こそ、たかがその程度の「現象」に過ぎないのだろう。AIが数行の指示だけでそれっぽいものを作り出すという事実は、人間が虫の習性を読み解くかのように、AIが人間の習性を読み解きつつあることの証明なのかもしれない。はぁ……。