SVX日記
2025-06-05(Thu) ガンダムの境界線
自分はマクロス派なので、そんなにガンダムに詳しくはないのだが、イカにもガンダムに出てきそうな造形なのに、機種に思い当たらない。SEED以降の、何かを背負っている、割とゴテゴテ系の造形だよな……とは思うのだが……それもそのはず、クリックして商品ページに行ってみると、プラモデルのようではあるがガンダムとはヒトコトも書いていない。バンダイの製品でもなく「無限新星」がメーカ名らしい。
どこまでもソレっぽいけれども、敢えて顔は似せていないようにも思える。でも、造形の文法は明らかにガンダムだ。むしろソレっぽさを、ソレ以上に盛れるだけ盛っているマシマシの造形。でも、ぜんぜんニセモノっぽさを感じさせない。素直にカッコイイと感じられる。
ちょっと前、アリエクに明らかなガンプラのコピー品が溢れているのに気づいた。さすが、プライドもクソもない彼の国。本家の品薄につけ込むところも、さすが。で、面白半分にレビューをググってみると、かなりの低品質らしい。まぁ、そうだろうね、というオチだ。
なので、再び、面白半分に上記の「無限新星」のレビューをググってみると……なんと高評価だ。品質も高いらしい。マジかよ。個人的にはガンプラ作るのって、楽しいと思わなくもないけど、やらされ感がバカらしくも思えて、しばらくトンと作っていない。でも、コレなら、ちょっと試してみたいような気もしてくるんだよなぁ。
なんだろう。パチもんアニメ、パチもんゲーム、パチもん自動車……彼の国の作るモノには、常にある種の「クサみ」を感じるのだが、今回ばかりはちょっと様子が違う。パチもんの範囲から出てはいないものの「クサみ」を感じない。やたらな多色による派手さを狙わず、テーマを感じさせる配色。軽量、汎用、重武装など、用途を感じさせるデザイン。ガンダムの要素を消化した上で、下手すると上回っているように感じられる部分もある。なんなの? 本気を出せばデキるってことなの?
思えば、昔は日本もこうだったんじゃないかなぁ。家電や自動車。欧米からサルマネしやがって……と思われているうちに、肩を並べられる製品を作れるようになった。彼らが高い精神性を手に入れたら、強大な敵になると感じさせられる。
2025-06-06(Fri) ETC、ワルイテック
ずいぶん前の話だが、ロードスターの納車直前にETCの車載器を手配した。パナソニックのCY-ET809Dというもので7000円弱。日髙のり子が声を当てているということで話題になったもの。
んが、ぜんぜん浅倉南ではない。なんでも「『機械的に発声して』という指示だった」と当の本人が語っていたが、確かに機械的な発声で萌え成分はゼロ。なんのために日髙のり子を呼んだんだよ。奇しくも完全に「ドミネータ」になってしまっている。「通過モード、リーサル、走行速度オーバー40、速度を落として下さい」……とは言わないけれど。
ロードスターには、純正アクセサリにETCの車載器が用意されているのだが、当然のように驚くほど高価。しかも、装着位置が助手席の背中の裏という微妙な場所。ETCカードを抜くのがすげぇ手間やん。まぁ、盗まれようもない場所でもあるけれど。
つうわけで、ハナから自分で取り付けるつもりで手配したのだが、なにしろ高速道路を走るのが好きではないので、利用頻度は極めて低い。年に4,5回。そんなための装置をダッシュボードに設置したら目にウルサくてかなわんし、エンジンの始動の都度「イーティーシーカードが挿入されていません」なんつうアナウンスも耳にウルサくてかなわん。なので、電源をシガーソケット対応にして、使う時だけフロントガラスに吸盤マウントでくっつける形にした。まぁ、前に乗っていた軽自動車のときもそうしてたんだけど。
この装着方法、禁止されてまではいないものの推奨されない方法なのだとか。というのも、ETCの車載器には、車種区分が書き込まれていて、それで料金が決定されるからだ。軽自動車の車載器を、普通車に載せ替えると、料金をズルできてしまう。なので、容易に載せ替えできる状態はよろしくない、という主旨らしい。とはいえ、別にズルするつもりなんかないし、料金所ではナンバの撮影もされているだろうから、好きにさせてもらうわ。
それで何年も過ごしてきたのだが、聞くところによると2030年問題というものがあるらしい。なんでも、無線通信の暗号化方式に脆弱性が見つかったので、古い機種がゴミになるのだとか……おい。オレのコレもゴミになるヤツじゃねぇか。なんだか釈然としないな。こないだの大規模トラブルでもテメェらのミスを客に押し付けて批判されてたが、考え方の根っこが同じだ。実に半官組織クサいヤリクチ。大概は車の更新と同時に車載器も更新されるのだろうから、十分に台数が減ってから買い替え補償したっていいだろうに。そもそも暗号は当然のように陳腐化するのだから最初からファームウェアアップデートくらいできるようにしとけや。時期的にDVD→Blu-rayから学べたことだろうに。無能マル出しだな。
2025-06-07(Sat) 設楽ダム工事進捗よーし
設楽ダムでも見に行くか。設楽町に続く県道33号は大好きなドライブコースのひとつだ。ダム建設の影響で、だいぶ前から一部の区間が通行止めになっている。走れるうちに走っておきたい。ちょっといつもと違う道を通って33号へ……と、思ったら、通行止めで473号へ押し戻されてしまった。しゃーない。帰りに通ろう。
「設楽ダム工事事務所広報展示室」という所にクルマを止める。広報展示も楽しみにしていたのだが、開いちゃいねぇ。土日休みかよ。広報展示する気があんのかないのかわからんな。そこから「設楽ダム見晴展望台」まで徒歩20分……が、iPhoneに案内を頼ったら、画面の向きが変わらない不具合が出てしばらく逆方向に……ったくもー。自分の勘を信じるべきだった。ほどなくして展望台へ。誰もいやしねぇ。まだダムの堤体は影も形もないが、影も形もない時点を眺めるのにも価値がある。記録として写真を撮っておく。
2025-06-10(Tue) 君がッ泣くまで米出しをやめないッ!
だからといって米を買い控えてはいないが、さすがに高すぎだろう。そんな中、小泉進次郎が大人気だ。わかりやすく大活躍。政治の難しいことはわからないが、ほとんどの人が政治の難しいことはわからないのだ。わかりやすく大活躍することは価値のあることだ。
2025-06-11(Wed) ゲームパッドの入力処理が「理解」されてしまう
自製のレースゲーム「TopDrivin'」の開発を地味に進めているのだが、一風変わった操作方法にするつもりだ。オイラはずっと前から、レースゲームに「アクセルボタン」って要るか? って思ってた。
クルマの運転には、主にステアリングとペダルとギアチェンジレバーを使う。実車では手と足を使うのに、ゲームコントローラだと手だけ。足りるわけがない。だからオートマになってしまう。違うだろ。大事なのはギアチェンジだ。むしろアクセルペダルなんて基本ベタ踏みなんだからいらない。ギアチェンジで速度調整、ブレーキ(アクセルオフ)で微調整。だから、シフトダウン、ブレーキ、シフトアップの3ボタンでの操作のがベターだと思うのだ。
で、いきなり細かい話になるが、レバー操作とブレーキ操作はレベルセンスでいいが、シフトのアップ、ダウン操作のためにはエッジセンスが必要だ。つまり、押した瞬間だけを検知する。離したこと、押し続けていること、は検知しない。実際のF1マシンのパドルシフトもそうだ。押し(引い?)たら1段階シフト。「ドゥ、ドゥ、ドゥ、ドゥーン」という連続シフトダウンの操作は、絶対に再現したい。
キーボードの入力処理は簡単だ。キーダウンイベントの発生を検知すればいい。しかし、ゲームパッドのボタンにはダウンイベントはない。レベルを見てエッジセンスする必要がある。とりあえず、汎用性は必要ないから、処理効率よくコンパクトに書きたい。で、書いた。うまく動いた。
以下のコードについて説明して下さい。
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が人間の習性を読み解きつつあることの証明なのかもしれない。はぁ……。
2025-06-12(Thu) ギアチェンジを実装したり、ゲームのウソを暴いたり
まぁ、AIにゲームパッドの入力処理が「理解」されてしまって悔しかろうが、処理を書き上げられたことには違いないので、次のステップへ進む。ギアチェンジによる自車速度の調整機能の実装だ。
最終的には、シフトアップ後、徐々に加速していくようにするつもりだが、それにはいろいろと調整が必要なので、当面はパッとその速度になるような実装ですませる。調べると、最近のF1マシンは8速と決まっているらしい。ちょっと調べて、以下のようなテーブルを書いた。単純に、各ギアでの上限速度(km/h)である。
@speeds = [ 85, 120, 155, 190, 225, 260, 295, 330 ]
しかし、実際に8速の330km/hで走ってみると、ハンパなく速い。速すぎる。トップビューの都合上、ほとんど先が見えないのでコースを完全に覚えないと、観客席を飛び越えるほどのコースアウトを連発してしまう。
そもそも、オマージュり元のスーパーフォーミュラも、ややその傾向はあったが、こんなに難しかったっけ? ……と、思って、スクリーンショットを元に計算をしてみた。2時刻間の距離と時間から速度を導く。距離は197ピクセル。時間は0.417秒。 車幅からグリッドの間隔は4mで48ピクセルとわかっているので……
(4m * 197px / 48px) / (37.875sec - 37.458sec) = 141.7km/h
2025-06-14(Sat) ロードスターを実装するが、またもや「理解」されてしまう
つうわけで、しばらくは鈴鹿でロードスター(相当)を走らせて、コースを覚えたり、各種調整を進めることにする。
早速だが、ロードスターの各ギアでの上限速度は……ん? ギア比が公開されているのだから、それを使えばいいか。すると、それを速度に変換するための係数はいくつになるのだろう……ん? それってタイヤの外径から理詰めで求められるのでは? 195/50R16の外径は……ん? それも理詰めで求められるのでは?
#!/usr/bin/env ruby
include Math
gear_ratios = [5.087, 2.991, 2.035, 1.594, 1.286, 1.000]
final_ratio = 2.866
tire = [195, 50, 16]
rpm = 7500
tire_dia = (tire[0] * tire[1] / 100.0 * 2 + tire[2] * 25.4)
tire_cir = PI * tire_dia
gear_ratios.each {|gr|
p tire_cir * rpm * 60 / gr / final_ratio / 1000 / 1000
}
58.31602138429881
99.18207983347644
145.77572519996463
186.10639948678045
230.6793163156517
296.65360078192805
……合っているっぽい。実際の鈴鹿の動画では4速までしか入れておらず、180km/hチョイだったので合っている。直接にkm/hを与える方法も残しつつ、ギア比等の入力で車両特性を指定する方法も実装してみるか。
以下は何のプログラムでしょうか?
ratios = [5.087, 2.991, 2.035, 1.594, 1.286, 1.000]
final = 2.866
spec = [195, 50, 16]
rpm = 7500
dia = (spec[0] * spec[1] / 100.0 * 2 + spec[2] * 25.4)
cir = PI * dia
ratios.each {|gr|
p cir * rpm * 60 / gr / final / 1000 / 1000
}
このプログラムは、**自動車やバイクの各ギアにおける最高速度の概算を計算するスクリプト**です。
特にエンジンの最大回転数(`rpm = 7500`)時に、各ギアでの\*\*理論的な最高速度(km/h)\*\*を求めています。
## 🧠 解説:何を計算しているのか?
### 🔢 入力データの意味
```ruby
ratios = [5.087, 2.991, 2.035, 1.594, 1.286, 1.000] # 各ギアのギア比(1速〜6速)
final = 2.866 # ファイナルギア比(ディファレンシャル)
spec = [195, 50, 16] # タイヤサイズ:[幅 (mm), 扁平率 (%), ホイール径 (inch)]
rpm = 7500 # 最大回転数(エンジンの回転数)
```
### 🛞 タイヤの直径と外周長の計算
```ruby
dia = (spec[0] * spec[1] / 100.0 * 2 + spec[2] * 25.4)
```
* タイヤの直径(mm)を計算:
* `spec[0] * spec[1] / 100` → サイドウォールの高さ(上下分なので ×2)
* `spec[2] * 25.4` → ホイール径を mm に変換
* 合計で**タイヤ全体の直径**
```ruby
cir = PI * dia
```
* タイヤの外周長(mm)= 円周公式:π × 直径
### 🚗 速度の計算
```ruby
ratios.each {|gr|
p cir * rpm * 60 / gr / final / 1000 / 1000
}
```
各ギア(`gr`)に対して:
* `cir * rpm * 60` → 1分間のタイヤの進行距離(mm)
* `/ gr / final` → エンジン回転数からタイヤ回転数への換算(ギア比補正)
* `/ 1000 / 1000` → mm → km に変換
* → **理論最高速度(km/h)を出力**
## ✅ 出力されるもの
このスクリプトは、**1速〜6速ギアにおける最大回転数時の理論的な速度(km/h)を出力**します。
例えば出力が以下のようになるとすれば:
```
31.4
53.4
78.5
100.3
124.3
160.1
```
これは、「7500rpm 時の各ギアの速度」です。
## 📝 まとめ
このプログラムは:
> **「指定したギア比・ファイナル比・タイヤサイズ・エンジン回転数」から、各ギアでの最高速度(km/h)を計算する**
> → 車やバイクの**性能シミュレーションや仕様設計**でよく使われる方法です。
必要であれば:
* 同じ処理の Python 版や CoffeeScript 版
* より実用的な「グラフ化」や「回転数別スピードマップ」
も提供できます。お気軽にどうぞ。
2025-06-15(Sun) ラップタイムの計測を実装
ギア比等と速度が理詰めで紐づいたところで、とりあえず、以下のようにテーブルに書き換えた。
< @speeds = [ 85, 120, 155, 190, 225, 260, 295, 330 ]
> @speeds = [ 58, 99, 145, 186, 230, 296]
ここでのセクタ分割は、F1のそれではなく動画のそれに合わせてある。動画では、概ね41秒、25秒、56秒で、ラップタイムは2分31秒。概ねそれっぽいタイムが出ている。いやこれ、思った以上にいい感じだなぁ。
現状、物理法則の導入度はまったくのゼロなのだが、ここからどう進めるか。単純にハンドリングは1フレーム(1/60秒)で128*360/65536度切れるように書いてあるだけなんだが、意外とそれで違和感ないんだよね。
left: ->
@_v8 -= 128
@_v8 += 65536 if(@_v8 < 0)
2025-06-18(Wed) あのラップタイム表示を再現したい
とりあえず、ラップタイムの計測はできるようになったが、表示がイマイチ。最終的に目指すのはコレ。
元となる時間情報は、1/60秒を元にした、1/1000秒の時計。それを文字列情報に変換したいのだが……
0.012
0.123
1.234
12.345
1:02.345
1:23.456
strdtime: (tsc1000, nfrac = 3) ->
strmin = ' '; zpad = ' 000'
(min = Math.floor(tsc1000 / 60000)) and (strmin = min + ':'; zpad = '0000')
strmsec = String(tsc1000 % 60000)
strmsec = zpad.slice(0, 5 - strmsec.length) + strmsec
strmin + strmsec.slice(0, 2) + '.' + strmsec.slice(2, 2 + nfrac)