2024年3月30日土曜日

天神風の道公園で春の訪れを感じながらバイクツーリング


最近,技術的な話題はともかく,日記的な話題の blog を上げるのがだるくなってきて,めっきり更新頻度が減っている当 blog.
ここで,AI に blog 記事書かせてみたらいいんじゃね? と思ってやってみた.

入力した文章はこれ↓
====================================
次の文章を元にblogの記事を書いてください.

冬は寒かったので全然バイクに乗っていない.
今日は暖かかったので,本年初のバイクのり.
多くのライダーが集まる湖畔の里つきがせはスルーして天神風の道公園に行った.
ここは見晴らしが良く人も少ないのでお気に入り.
桜はあまり咲いてなかった.梅らしきものは咲きかけ.
====================================

出力された記事
===== ここから =================================

天神風の道公園で春の訪れを感じながらバイクツーリング

冬の間は寒くてバイクに乗る機会が減っていましたが、今日は暖かかったので今年初のバイクツーリングに行ってきました。多くのライダーが集まる湖畔の里つきがせはスルーして、見晴らしが良く人も少ない天神風の道公園を目指しました。

公園に到着すると、眼下に広がる景色に感動しました。遠くの山々まで見渡せる絶景は、まさに春の訪れを感じさせてくれます。桜はまだあまり咲いていませんでしたが、梅の花が咲き始めており、春の訪れを告げていました。

公園内を散策していると、気持ち良い風を受けてリフレッシュできました。人も少なく、静かな環境で自然を満喫することができました。

今年のツーリングシーズンは、天神風の道公園からスタートすることができて、とても良い思い出になりました。これから暖かくなるにつれて、色々な場所に出かけたいと思います。

公園情報

  • 天神風の道公園
  • 奈良県奈良市月ヶ瀬梅林
  • 駐車場あり
  • 無料

おすすめポイント

  • 見晴らしが良く、遠くの山々まで見渡せる
  • 人が少なく、静かな環境
  • 春は桜や梅の花が楽しめる

まとめ

天神風の道公園は、見晴らしが良く、静かな環境で自然を満喫できる公園です。春は桜や梅の花が楽しめるので、ツーリングの目的地におすすめです。

===== ここまで =================================

すげえよwww 「春の訪れ」とか自分では絶対書かないしそんな文才無いwww
しかも自己満足な記事ではではなく,公園情報や見どころまでまとめられて,ちゃんと他の人にとって役に立つ記事になっている.しょうもない記事を書くライターは廃業だなこれ.
これ見たら,ソースコード書くだけの人もそのうち淘汰されるだろうなぁ,というのも実感が湧いてくるね.

2024年3月24日日曜日

C++ で構造化アセンブリプログラミング

動機

原始的なアセンプラしか無い原始的な CPU のプログラミングをすることになった.
でアセンブリプログラミングの一番の萎えポイントとして,例えば C 言語だと
if(a == b){
  c = d;
}else{
  e = f;
}
みたいに書けるところが,構造化記述できないアセンブラだと
  cmp a, b
  jnz label0
  mov c, d
  jmp label1
label0:
  mov e, f
label1:
とラベルやら分岐命令やら,自分で生成しないといけないのでめんどくさいし,if がネストすると可読性も悪い.真面目にやるなら bison/flex とかでまともな構造化アセンブリ言語を設計するところだけど,そこまでやるのはなぁ... と思ったところで,C++ のクラス / 演算子オーバーロードをうまいこと使えば,構造化アセンブリプログラミングもどきが出来るのでは? と思った.
例えば,C++ で "r0 = r1;" と書いてコンパイル・実行すれば,"mov r0, r1" というテキストが得られる,みたいなイメージ.

まずは代入演算

//////////////////////////////////////////////////////////////////////////////
// Register

class RegisterObject {
public:
  RegisterObject(const char* szName) : m_szName(szName){}
  const char *Name(void) const {return m_szName;}
  
private:
  const char* m_szName;
};

class GpReg : public RegisterObject {
public:
  GpReg(const char* szName) : RegisterObject(szName){}
  
  GpReg& operator=(const GpReg& src){
    printf("\tmov\t%s, %s\n", Name(), src.Name());
    return *this;
  }
};

//////////////////////////////////////////////////////////////////////////////
// Register インスタンス

GpReg r0("r0");
GpReg r1("r1");
GpReg r2("r2");
GpReg r3("r3");

//////////////////////////////////////////////////////////////////////////////
// アセンブリプログラム

int main(int argc, char **argv){
  r0 = r1 = r2;
  return 0;
}
GpReg class は汎用レジスタをイメージしていて,特殊なレジスタがあれば RegisterObject か GpReg を継承する感じ.C++ ソースコード上の変数名 (r0 とか) は実行時には失われてしまうので,m_szName に変数名をセットしておく.
代入演算のキモは言うまでもなく "operator=" で,= が呼ばれたら mov 命令のテキストを出力する.
で実行結果:
        mov     r1, r2
        mov     r0, r1
おお,いい感じ.アセンブラだと r0 = r2 が直接代入できないので一旦 r1 を経由する,みたいなケースが多々あるが,それが 1行で書けるのはありがたい.

比較演算

次に,if-else-endif の構造化をやる前に比較演算子を定義する.
対象 CPU は,== なら cmpeq みたいに比較演算子毎に比較命令があり,その結果をフラグレジスタ f0 にセットする.条件分岐命令は f0 の値をみて分岐するかどうか決める.
class FlagReg : public RegisterObject {
public:
  FlagReg(const char* szName) : RegisterObject(szName){}
};

//////////////////////////////////////////////////////////////////////////////
// Register インスタンス

FlagReg f0("f0");

//////////////////////////////////////////////////////////////////////////////
// global な operator

FlagReg& operator==(const GpReg& a, const GpReg& b){
  printf("\tcmpeq\t%s, %s, f0\n", a.Name(), b.Name());
  return f0;
}

//////////////////////////////////////////////////////////////////////////////
// アセンブリプログラム

int main(int argc, char **argv){
  r0 == r1;
  return 0;
}
RegisterObject を継承して FlagReg を定義する. operater== で,== が呼ばれたら cmpeq 命令を出力して,== の返り値として f0 を返す.
で実行結果:
        cmpeq   r0, r1, f0
これはなんの問題もない.

if-else-endif

そしてこの取り組みの一番の目的である,if-else-endif の構造化をやってみる.
//////////////////////////////////////////////////////////////////////////////
// 構造化構文

int g_LabelCnt = 0;
std::vector<int> g_Label;

void _if(FlagReg& f){
  printf("\tjnset\t%s, _L%d\n", f.Name(), g_LabelCnt);
  g_Label.push_back(g_LabelCnt);
  ++g_LabelCnt;
}

void _else(void){
  printf("\tjmp\t_L%d\n", g_LabelCnt);
  printf("_L%d:\n", g_Label[g_Label.size() - 1]);
  
  g_Label.pop_back();
  g_Label.push_back(g_LabelCnt);
  ++g_LabelCnt;
}

void _endif(void){
  printf("_L%d:\n", g_Label[g_Label.size() - 1]);
  g_Label.pop_back();
}

//////////////////////////////////////////////////////////////////////////////
// アセンブリプログラム

int main(int argc, char **argv){
  _if(r0 == r1);
    r0 = r2;
  _else();
    r1 = r3;
  _endif();
  return 0;
}
_if ではフラグレジスタを受取り,必要な分岐命令を生成する.また if-else-endif はネストするので,分岐先ラベルの情報はスタックに push / pop する必要がある.
で実行結果:
        cmpeq   r0, r1, f0
        jnset   f0, _L0
        mov     r0, r2
        jmp     _L1
_L0:
        mov     r1, r3
_L1:
おおぉ,これこれ! これがやりたかったんだよ.この時点でこのやり方はかなりうまくいく感触を得ていたが,念の為 if がネストするケースをテストしてみたら,
コード:
//////////////////////////////////////////////////////////////////////////////
// アセンブリプログラム

int main(int argc, char **argv){
  _if(r0 == r1);
    _if(r1 == r2);
      r2 = r0;
    _else();
      r3 = r1;
    _endif();
  _else();
    r1 = r3;
  _endif();
  return 0;
}
実行結果:
        cmpeq   r0, r1, f0
        jnset   f0, _L0
        cmpeq   r1, r2, f0
        jnset   f0, _L1
        mov     r2, r0
        jmp     _L2 ←※ここ
_L1:
        mov     r3, r1
_L2:
        jmp     _L3
_L0:
        mov     r1, r3
_L3:
んー,間違いではないんだけど,jmp _L2 の飛び先は jmp _L3 しか無いので,最適化の観点では「ここ」で jmp _L3 にすべき.
この最適化をやるためには,直接アセンブリテキストを出力するのではなく,一旦中間言語とかでメモリ上に溜めておき,最後に最適化フェーズを流す,等しないといけないということがわかった.
それを解決して,あとはメモリアクセスとかラベルへのサブルーチンコールとかを実装すれば,普通に使えそう.

2024年2月12日月曜日

DualShock3 アナログ修理

DualShock3 (PS3 コントローラ,以下 DS3) のアナログ入力が変 (入力してなくても,入力がプルプルぶれる) になってきたので,ジョイスティックセンサを交換することにした.
事前情報ではジョイスティックセンサは基板にハンダ付けなので,めんどくせぇと思って分解したら,


フィルム基板をゴムでジョイスティックセンサの端子に押し付けてあって,端子部分のハンダ付けは無し.固定はセンサの一部を爪のように折り曲げて固定.これは修理容易だしこの方式が流行って欲しいと思ったが DS4 では基板ハンダ付けにのようなので,残念ながら流行らなかったらしい.
ちなみに DS3 でも基板ハンダ付けバージョンもあるらしい.自分の DS3 の型番は CECHZC2J-A2.

で,交換自体はサクッと終わって,試しに使用してみたらほぼ問題なく使えるものの,センサの入力特性に違和感が.具体的には,標準のセンサに比べて,スティックの倒し量が半分くらいで入力 100% になってしまって,要は微妙な入力が非常にやりにくくなった.

これは,センサに使われている可変ボリューム? ポテンショメーター? の特性によると思うけど,買い直すにしても販売ページにそんな事書いてないし,標準部品の特性に近いセンサの選定は難しそう.

で我慢してこのまま使い続けるかしばらく悩んだが,ダメ元で元のおかしくなったセンサの可変ボリュームを分解清掃してみることにした.更に接点が接する部分に注油して,組み直して動かしてみたところ…

ビンゴヽ(´ー`)ノ 無事無入力時のブレも治った.
結局アリエクのジョイスティックセンサいらんかった.買ったのはこれで,少なくともこれを買うのはおすすめしない.

2024年2月10日土曜日

6年越しの夢が叶う

遡ること 6年前,Ace Combat 7 (以下AC7) が PSVR 対応するということで,「コックピットに乗って空中戦,の疑似体験」ができると思って,PSVR を買った.感想としては思ったとおり「乗り物に乗ってる感」がすごくて,それまでのゲーム体験とは一線を画すものだった.
ただし,ゲーム本編は VR 非対応で,おまけみたいな VR 専用ステージ 3面が遊べるだけ,という大変残念な仕様.本編が VR 対応してれば神ゲーだったのに.しかも PSVR 自体が失敗気味で,その後遊びたいと思えるゲームが全く発売されず,大爆死(;´д⊂)
# 今から思えば,PS4 では性能が足らなくて普通のクォリティで VR ゲーム作れなかったんじゃないかと思う

そして時は流れて現在,PC 版の AC7 と UEVR というフリーソフトで,強制的に VR 化できるという情報を得たので,PICO4 を \41,640 で get.

6年間これを待ってたんだよ(゜ーÅ)ほろり
実際に遊んだ感じでは PSVR での VR 専用ステージとほぼ変わらず,ゲームとして普通に成立している.PSVR では遊べなかった本編を VR で遊べて,なおかつ PSVR よりも高解像度ということで,非常に満足度が高い.

これは大抵の非 VR ゲームに言えることだけど,特に「乗り物に乗る系」は,画面の描画範囲が現実に比べとても狭い.例えば敵機がジグザグに逃げているのを追っているとき,敵機は画面外で切り返しても自分はそれが見えず,追うのに失敗することが頻繁に起こってストレスが溜まる.
VR だと,首が動く範囲である限り敵機を視線で追い続けることができるので,敵機の切り返しにも対応できてドッグファイトが非常にやりやすい.

あと,車運転するときを思い出してもらえればわかると思うけど,現実世界で左右に曲がりたいとき,視線は真正面を見続けているのではなく,曲がる先を見ている.非 VR だとこれも描画範囲外だが,VR は首をそっち方向に向けて曲がるという自然な動作が可能で,こういうところの「実際に乗っている感」が非常に高い.

ただし,元々 VR ゲームでないもの無理やり VR 化している事から,問題点もいくつかある.
・ターゲットマーカー以外のHUD表示がされないのが最大の問題.
 ・レーダーが表示されない(PAUSEすれば見れる)
 ・残弾が表示されない
 ・機銃レティクルが表示されないので機銃当てるのは至難レベル
 ・機体姿勢・速度・高度が表示されない
・コックピット視点が使えない
 ・首の動きにコックピット描画が張り付いているので,コックピット視点は使えないので,「乗り物に乗ってる感」は大分そがれる.

そこは割り切りが必要だけど,上記を改善する MOD も開発されているっぽいので,今後に期待.


2024年1月21日日曜日

JOG 3KJ エンスト修理

 足車の JOG 3KJ が走行中にエンストするようになった.症状としては
・ガス欠の症状に非常によく似ていて,走行中に突然アクセルを抜いたかのようにエンジン回転が下がり,アクセルを開けるとわずかに回転が上がろうとするが,そのままエンストする.
・停車後しばらくセルを回すと,エンジンがかかり普通に走れる.

先人の症例を調べてみると,考えられる原因としては
(1) 燃料タンクに水が混入した
(2) 燃料系統 (キャブとか) が汚れで詰まっている
(3) 点火系が壊れた

あたり.(3) だとすると再現性が低く,プラグの火花見てもわからんだろうな,と思ったので,まずは (1) から順番に可能性を潰そうと思った.

まずは燃料タンクからガソリンを抜いて,抜いたガソリンを観察してみたが,見た感じ水は入ってなさそう.

次にキャブ掃除すっか,とキャブを外したところ,負圧燃料コックに負圧を伝えるホースが割れていることを発見.この時点で勝利を確信したwww

念のためキャブ・負圧コックを分解してみたけど,特に詰まり等はなかった.

ということで負圧燃料コックのホースを交換して完了.
今回はわかりやすい故障で良かった.