こんにちは、STORES の id:hogelog です。
この記事では、RubyKaigi 2025の STORES ブースで公開されたIRB宝探しゲーム、IRB TreasureHunt Gameの作りとどんなお宝が隠されていたのかのネタバレを解説していきます。
このゲームは @mame がベースのアイデアと土台の実装をおこない、その上で主に @hogelog、一部 @ima1zumi がお宝のアイデアを実装しました。
ゲームの概要
このゲームは https://ruby-quiz-2025.storesinc.tech/ というURLにアクセスすると始まる、kateinoigakukun/irb.wasm を利用した宝探しゲームです。

起動直後に出てくるイラストは人類が姿を消した後の世界でお宝を探す猫人間 (Nekomorph) です。この猫人間がお宝を探しているという物語でゲームが進行していきます。
ネタバレを見ずに遊んでみたいという方は以下の文章を読まずにチャレンジしてみて、一通り遊んだ後にまた見に来てください。
ゲームの実装
このゲームは irb.wasm をベースに動いています。宝探し用のコマンドを追加しているのと、INSPECT_MODE をちょっと特殊なものに変えていること以外はほぼ素のIRBです。
宝探しコマンドは起動直後のメッセージで強調されている help コマンドを実行すると見つけられるものです。
TreasureHunt th Show TreasureHunt score and commands. th-capture Capture found treasure. th-search Search for treasures in the world.
th コマンドはお宝一覧とTreasureHuntコマンドを表示するコマンドで、th-capture は見つけたお宝を登録するコマンド、th-search はお宝を探すコマンドです。 th-search コマンドを繰り返し実行していくと、お宝を見つけるヒントが表示されていくようになっています。
INSPECT_MODE はゲームをやっている中で「なにかしてるな」と気付いた人もいるかもしれません。IRBで文字列の評価をするときにエスケープシーケンス (\e) が含まれていたら #inspect 結果ではなくそのまま表示するようになっています。ゲーム中でしばしば出てくる装飾文字やSixel文字列をそのまま表示するためにこの設定をしています。この辺りになにか謎が隠されてるのではないかと探った方もいたようですが、ここは単にゲーム表示のための細工です。
require "irb/color_printer" IRB::Inspector.def_inspector([:th]) do |v| output = StringIO.new if v.to_s.include?("\e") output.print(v.to_s) else IRB::ColorPrinter.pp(v, output) end output.string.chomp end IRB.conf[:INSPECT_MODE] = :th
お宝の解説 (ネタバレ注意)
さてそれではどんなお宝が隠されているのかと、その想定解法を解説していきます。
1. チュートリアルお宝、Hello STORES
最初の宝は、ゲーム開始時に表示されるヒントから見つけることができます。
At first, try: puts "Hello, STORES" というメッセージ通りに実行すると
> puts "Hello, STORES" You found a treasure!: ST-HELLO You can use th-capture command to capture this treasure: th-capture ST-HELLO Hello, STORES => nil
最初の宝「ST-HELLO」を見つけたメッセージと、そのお宝を th-capture コマンドで登録するよう案内するメッセージが表示されます。
このお宝はチュートリアルのつもりで配置したのですが、ゲーム起動時にしか表示されないメッセージなので、最初に読み飛ばしてずっと見つけられないでいた人もいたようです。
2. インスタンス変数お宝
th-search コマンドの初回実行時には以下のメッセージが表示されます。
You search the area... You look around and notice a crumpled paper on the ground. It reads: See ls output...
ここで案内されている ls コマンドを実行すると
instance variables: @stores
と、明らかに怪しいインスタンス変数が表示されます。このインスタンス変数を評価すると
> @stores => You found a treasure!: ST-IVAR
と ST-IVAR というお宝が見つかります。
3. 定数お宝
th-search コマンドの2回目実行時は以下のメッセージが表示されます。
You search the area... You find a small note hidden under some leaves. The note reads: This game provided by ______
「This game provided by ______」というメッセージがあります。このゲームを提供しているのは STORES です。というわけで STORES と入力し評価すると
STORES => You found a treasure!: ST-CONST
STORES 定数が評価されて ST-CONST というお宝が見つかります。
このヒントが指してるお宝が @stores インスタンス変数のものだと思って、このお宝をなかなか見つけられなかった人もいたようですね。
4. 川の中のお宝
さて th-search コマンドを3回、4回と実行すると、以下のようにやたらと River を調べることを案内されます。
You search the area... You spot a worn sign on a stone. The sign says: The treasure is in the River...
You search the area... Near an archway, you see an instruction: ls River shows River class internal.
ということで River クラスの中身を調べてみると、怪しいメソッドが多数確認できます。
> ls River River.methods: bottom encoded treasure trigger #<Class:Object>#methods: yaml_tag instance variables: @hooks
わかりやすい River.treasure メソッドがあるのでこれを実行すると、お宝が見つかります。
> River.treasure => You found a treasure!: RI-CLASS
5. エンコードされたお宝
さて、Riverにあった他のメソッド、 River.encoded を実行すると以下のように明らかにエンコードされてそうな文字列が表示されます。
> River.encoded => You found a encoded treasure: UkktRU5DT0RFRA==
末尾が == で終わっているエンコード文字列と言えばそう、Base64ですね。またth-searchコマンドを実行すると以下のようなヒントも出てきます。
> th-search You search the area... You notice a scribbled note on a wall: Base64 is useful gem bundled in Ruby.
Base64でデコードするとお宝 RI-ENCODED が得られました。
> Base64.decode64("UkktRU5DT0RFRA==")
=> "RI-ENCODED"
6. trigger/hooksで掘り出すお宝
さて River には River.trigger というメソッドもありました。よくわからないですが呼んでみます。
> River.trigger
eval:279:in 'trigger': wrong number of arguments (given 0, expected 1) (ArgumentError)
from (irb):3:in '<main>'
from <internal:kernel>:168:in 'Kernel#loop'
from eval_async:134:in '<main>'
from /bundle/gems/js-2.7.1/lib/js.rb:109:in 'Kernel.eval'
from /bundle/gems/js-2.7.1/lib/js.rb:109:in 'block in JS.__eval_async_rb'
from /bundle/gems/js-2.7.1/lib/js.rb:120:in 'block in JS.__async'
なにか引数が一つ必要なことがわかります。しかしなにを渡せばいいのかよくわかりません。しかし trigger みたいな呼び出しをするときに関連しそうな @hooks というインスタンス変数があるのでこれを調べてみます。
> River.instance_variable_get("@hooks")
=> {explore: [#<Proc:0x04ac750c eval:299 (lambda)>]}
@hooks は :explore というキーのProcの配列を持っているようです。つまりこのインスタンス変数はおそらくtriggerメソッドの中で呼ばれる変数なのではないかと推測して :explore を River.trigger に渡してみます。
> River.trigger(:explore) You found a treasure!: RI-STONE => [#<Proc:0x04ac750c eval:299 (lambda)>]
すると RI-STONE というお宝が見つかります。
が、 @hooks を見た人はおそらくそのまま @hooks の中身を調べてProcを直接呼び出すまでするでしょうし、お宝の隠し方としてはあまりうまくなかったかもしれません。
7. 石板に書かれたプログラムのお宝
さて、残された River.bottom を呼び出すと川の底に沈んだ石板についてのメッセージが得られます。
> River.bottom => A stone Tablet is at the bottom of the river. It has some writing on it. Let's call: Tablet.read.
言われた通りに Tablet.read を実行すると以下のようにお宝を得るためのプログラムが与えられます。
> Tablet.read The tablet says: You can get the treasure by running program: +z+z+7.1-1.1-v.1+q.1+8.2-b.1. To learn how to run it, read Tablet.interpreter => nil
このプログラムを実行する方法は Tablet.interpreter に書かれてるというので、そちらを読みます。
> Tablet.interpreter The interpreter could run programs like these. Interpreter: - Interpreter has a single memory - At first, the memory is 0 - The interpreter reads the program from left to right Instruction: - Each instruction is 2 characters long - The first character is an operator - +: memory += n - -: memory -= n - .: print memory.chr n times - The second character is a number - Numbers are in base 36 (0-9, a-z) Example: - +z+z+2.1-3.1+7.2+3.1 => HELLO => nil
一命令が2文字で、1文字目が演算子、2文字目が36進数の数値となるようなプログラミング言語のようです。言われるがままにこのプログラムの処理系を実装します。
class MLang def initialize(code) @code = code end def run insns = @code.scan(/../) pc = 0 mem = 0 output = "" while pc < insns.size insn = insns[pc] op = insn[0] n = insn[1].to_i(36) case op when "+" mem += n when "-" mem -= n when "." n.times { output << mem.chr } end pc += 1 end output end end
処理系にプログラムを与えて実行すると、以下のように ML-GOOD というお宝が得られます。
> MLang.new("+z+z+7.1-1.1-v.1+q.1+8.2-b.1.").run
=> "ML-GOOD"
ループも分岐もなく、もちろんチューリング完全でもないのでプログラミング言語と言うには不完全なものですが、RubyKaigi内でのコンテンツですし処理系らしきものを実装しなければいけないお宝も用意してみました。サクッと実装しやすく、一方でプログラムが長くなり過ぎないように命令セットを工夫してみました。
8. 絵文字のお宝
さて Tablet クラスの中もあらためて見てみると、裏側があるようです。
> ls Tablet constants: ReverseSide Tablet.methods: interpreter read
Tablet::ReverseSide クラスを調べてみるとまたいくつかのメソッドがあります。
> ls Tablet::ReverseSide Tablet::ReverseSide.methods: read sentence
とりあえず読んでみると「Tablet::ReverseSide.sentence になにかがある、 /\w\W(?<treasure>\w{2}-(?<e>\p{Emoji})\W\k<e>)/ という道具を使え」というメッセージが得られます。
> Tablet::ReverseSide.read
The reverse side of the tablet says:
There is in the Tablet::ReverseSide.sentence.
Use the tool /\w\W(?<treasure>\w{2}-(?<e>\p{Emoji})\W\k<e>)/ to find it.
=> nil
そこで Tablet::ReverseSide.sentence を見ると、以下のようになんだか絵文字混じりの長い文字列が得られます。
> puts Tablet::ReverseSide.sentence 📀TOI🍵🧪KSLM🛢🍉R🧸🎮M🦉M🧫S-🎒💿️🔩ZO🔗I🎨🍣🔋FA📂🔩FJL🥷PRA🧭G🧪🍩W📚N🪄C🔋️ORX️ 🧪🔦NQSJA🔑🐉🧊EL🧪TY️ WQ️ 🧃🗺📫--🐧SV🪅🐧RF🚨OI🔨PB💿🔧Y📡️🛹ZN🧩DT️🧊L👾ZOJ🦾🍰D🍵XY-️ 📷WCA🗿️ KY💎X🌋DJO🛎FG-🗿🧃🔦PERQ🍓⚡📓🌇MBK🌅🌸🪅IHIXPYT🕹🧃F🌰🏝I⚡🔦KQ🐠️WA📷🌈🎨💿🦖🦊🔗📌G🚨AJ🔩M🎟️ D🔦R🦊U🛸🪅️W🥟QK♂🐾🗓J🪄R🗓🧤📓JIK🧱XM🧊🍰🧵E🛠🧊YRAG🍜JNKTIDJ🍉🛰R️ HV🧃TX📷N🍩BO🎨🎼🐲 G🪓UXR🌈🧼-🧭P-H🪄🍩🛹🧙🧊-A🧤📦🔋🌅🪁️🧬XXX🎳🗺E🔨🔦L🧙N🎯🧃🦖️🪓🧱🥽💿👾🧱🗿R🍉WQB🧯🐾️🧸🍙E🛠N🦋O🧊EAV💿🍀🍜🎨🔨RRN🦊-K🍀TSM🗺️ 🎲⚡EO🌙C🍉🪓N🗺YZUYQ🧱🛎🧀️I🎼SLE-NG-I📓🛢🔬-JHMP-🪅️-QYK 🕹TFX️ 💡🦊️🔨🚨🍓🧩🍩C🗿A📦S🔩🌠🍰🕹HN-RE📫K🧪🍰🌈V📉-🍙W📻Q📀YM🐲KX💎X🎂🦴📫🐉🧠HM🍩O🔥️🔧🎨PRL🍓🗜IL🐧WNT🚨HNYYZ️ 🛠L🛢📡🧃🎂WLYCOI🪓H️ 📈F🚨GY🌸️XL🛸🕹🪁U🌅🍓M🕹🧊X🍰🧫RG🗿🥟🧪📀✨🎯🔩Y🦅CE🗓 🌠IZOAIV📮C-EC🧸WB👾H-🎒🪅🧱🧸F🦴A🔗WE️ F️ 🎼️🛰-📉🦴GJ🔍📜🗺HA🌰👣O🧵O🐠♂️🐠 -📦C✨TP🗺🌇PYCL💡🌈YD🧭G🌠V️ G🍇🔗🛢🧤🔥B🥷UJ🧊Z📷RE-🐾💎🐾HK🎨👾🐉📌A🧬📜P️ WT🔧🌈🧃DJV🎁🪁📻N🧤️Z🐢🍓🍀R💿🧃🌌🧩🪐🍓R R📌🐉X🧩📡YD-🦾🎁🌋🛰📫AWDE🐧🧫🧃💎AJ🌅S📦📮🦴♂NV️ NQ📡🔨SR🛠U🗜️ DG️ L🦉XX🧼🦖L️ 🧫JB🗺VE🐧C🔗🌰E🔥🗓🎤🌇I💎🧼H🍰DRI🐉G🪁SC🧊ER️ 🧙⚡V🎤P🔍️P🐬🧪🧸📼📌UYM🎯️🔧-🧪KZ-🌸I--🕹📻🎨ET🗜EQ🦾H🧃🎂👾🌋 -🧠O🔑🧱📻✨NQ🥟JP-🐢️PL💡V⚡🐧B🥷📋🧃🛢A♂️ C🪅ZY-💎XTQ🐾P📮♂🧭🔗📻BNHV🚀J🦉R🌈🧫R🍓-🔗P🦴🧙️J🦾🍛🛰🌌🧊L📌NG🔨G️ 🪄Q🧬BD🎲🔨Z-🧃-📌JM-F👣-🎟🧤🥟UXJQVAIB🦴N🍣O🎁🧠🧱🎧GYUKM🦉X-📋⚡S🎟🔑F🔬 🧃P🌈HL🎳O🌸DV️ 🔥🎲🔥BVTS🗜Q️ ED📡🔑BO📉D🔍📂🛎️ 🧫R📈🎁E🌸🦊CB🗓🧼F🐲🦴🔨🛠AVDP👾️🔦💡R🐢WAD🍣BQR🍩🧭PILK🦴F🛠🧊🪅💿V🌰LI🦊🎁📻KZR️ C️ 🚀V🔥️🪓🌋♂🦅️B📀T-📋🌈🕹BMD️ ZM🌇👣📓W️ I🧸🧙T-🎯SJ🎲🧵 🪤X🗓O🍀🗃🍣️🍰VX🌠🦾🧤H🌇🎟️ 📦🧃🔩️📡-HVGU💡CQU🚀📷🌠EMSK📈M📂🌸️🌋🚀N️ 🥟YUV🐾🍇📼W📚🔨🚨🔩📋🌠A🧼I🎒-🥷Q🧼🎧🌈QR🦋Q🌙️S🪁F🍀📻️🔥🧀🛢R️ 🐲X🌋FDS🧀️🍜🗺⚡RC🦴U🧊🗃FIIOZ🧊🌸XN🛹AN🛸🦋CZ️ G🍇📂📼🍣 🚨X️ RF🦖🎟X🧭RAV🧸XKFS🗿🎮🗿️OA-🎼📀GQ🦋🧭A🌅🔗RSO️ 🦉Z️ 🗓🌸👾N🎮BE🎳📼B🍰🍉P🧃🧊I🧙P🎳N🦾IFVPZ🧀👣-X🕹🔬AM🌈️🕹️ TR-🎤O🎂🧪📚-🗺AOG📉🌰🦾DSYPU🔑IW🎁II🎮🎧🚨️🎼OWP🐢🎂Z🗜️ Q🥽🎼HN-L-B🥟 => nil
さきほど得られた正規表現とこの文字列をマッチさせてみます。
> Tablet::ReverseSide.sentence.match(/\w\W(?<treasure>\w{2}-(?<e>\p{Emoji})\W\k<e>)/)
=> #<MatchData "Z📷RE-🐾💎🐾" treasure:"RE-🐾💎🐾" e:"🐾">
名前付きキャプチャ treasure に対する文字列として RE-🐾💎🐾 というお宝が見つかります。
知っている人は知っているが知らない人は知らない正規表現のUnicode Propertyを使ってみたり、後方参照を使ったりと少しでもゲームを遊んだ人にとって新鮮に感じる要素を入れてみようとしています。
9. アスキーアートお宝
Riverにあったものは一通り見たのでまた th-search を呼ぶと、別のヒントが出ます。
You search the area... You found a big dial. It looks like it can run in IRB. The dial says: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;1111111;;;$iiiiiiii;;;;;;;;;1111e1111;;:iiiiii;;;;";;;;;";;; ;111;;;;;;;;;;;$ii;;;;;;;;;;;;;;;1e1;;;;;:ii;;;:ii;;";";;;";"; ;;;1111111;;;;;;$ii;;;;;false;;;;;1e1;;;;;:iiiiii;;;;";;;;;";;; ;;;;;;;;111;;;;;$ii;;;;;;;;;;;;;;;1e1;;;;;:ii;;;:ii;;";";;;";"; ;;;1111111;;;;;;$ii;;;;;;;;;;;;1111e1111;;:ii;;;:ii;;";;;;;";;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
なにか謎の文字列がでてきています。IRBで実行できるというのでそのままIRBに貼り付けると以下のような表示となります。

元の文字列だとわかりにくかったですが、色がつくと ST-IRB というお宝がわかりやすくなります。
余談ですがこのゲームのテストにはChatGPTを利用していました。ChatGPTが解いていけるならまあきっと人間でもがんばればできるだろうとテストしていたのですが、このお宝だけはChatGPTはついに見つけることはできませんでした。AIに奪われない仕事はなんなのかみたいな話題が盛り上がる昨今ですが、アスキーアート領域ならば人間はまだまだAIには負けないようです。
10. キーノートお宝
th-search を更に実行するとRubyKaigi 2025 オープニングキーノートを讃える以下のメッセージが得られます。
> th-search You search the area... While you wander, you remember: "The RubyKaigi 2025 opening Keynote was fun!"
Keynote クラスの存在が示唆されているので調べてみます。
> ls Keynote constants: CP290_TABLE LOCATION
Keynote::LOCATION を見てみるとファイルパスらしきものが得られます。
> Keynote::LOCATION => "/home/me/treasure.txt"
ファイルを読んでみると以下のよくわからないバイナリのような文字列が得られます。
> File.read(Keynote::LOCATION) => "'p\xF3\xF3u\xE8w\xB4@gw\xB4ve@b@\xB3\x9Bfb\xAB\xB4\x9BfZz@\xD2\xC5`\xC3\xD7\xF2\xF9\xF0'pu%%\xE6ftdwuf@\xB3w@\xC3\xD7\xF2\xF9\xF0@fvdweqvh@\xB6w\x9BteZ%\xC3\xD7\xF2\xF9\xF0@\x84\xBD\x8AX\x94\xBEH\xBD\x88\xBE\x9A@\x8F\x86\x82\xA2@\xAC\x83\x8A\x90Z%"
バイナリのような文字列、RubyKaigi 2025 オープニングキーノート、 Keynote::CP290_TABLE ということはつまり、与えられたファイルはCP290のテキストファイルかもしれません。 CP290_TABLE の中身も見てみます。
> Keynote::CP290_TABLE
=>
{0 => nil,
1 => nil,
2 => nil,
3 => nil,
4 => nil,
...
248 => "8",
249 => "9",
250 => nil,
251 => nil,
252 => nil,
253 => nil,
254 => nil,
255 => nil}
いかにも文字の変換テーブルらしきものです。これを使ってテキストファイルのデータを変換してみます。
> File.read(Keynote::LOCATION).each_byte{ print Keynote::CP290_TABLE[it] }
You found a treasure!: KE-CP290
Welcome to CP290 encoding world!
CP290 エンコーディングノ セカイヘ ヨウコソ!
=> "'p\xF3\xF3u\xE8w\xB4@gw\xB4ve@b@\xB3\x9Bfb\xAB\xB4\x9BfZz@\xD2\xC5`\xC3\xD7\xF2\xF9\xF0'pu%%\xE6ftdwuf@\xB3w@\xC3\xD7\xF2\xF9\xF0@fvdweqvh@\xB6w\x9BteZ%\xC3\xD7\xF2\xF9\xF0@\x84\xBD\x8AX\x94\xBEH\xBD\x88\xBE\x9A@\x8F\x86\x82\xA2@\xAC\x83\x8A\x90Z%"
お宝 KE-CP290 が得られました。
普段はあまり手で変換表をたどるようなことはしないと思うのですが、言語処理系がしてくれている仕事を感じられたのではないでしょうか。
なおこの文字列が本当にCP290文字列なのか調べるのにはJavaが便利でした。言語処理系界でのJava先輩の大きさを感じます。
var path = Paths.get("/tmp/treasure.txt"); var reader = new BufferedReader(new InputStreamReader(Files.newInputStream(path), "Cp290")); String line; while ((line = reader.readLine()) != null) { System.out.println(line); }
11. 壊れた探知機で見つけるお宝 (2025/05/02 追記)
さて th-search コマンドを更に実行すると以下のように th-search が --more オプションを受け取れることを示すメッセージが表示されます。
> th-search You search the area... A small note at the side says: Try th-search --more...
オプションをつけて実行すると以下メッセージが得られます。
> th-search --more You search the area... You search the area... You found TreasureDetector! You can use it to find treasures.
TreasureDetector を調べると、hint と use というメソッドがあることがわかります。
> ls TreasureDetector TreasureDetector.methods: hint use
おもむろに TreasureDetector.use を実行してみるとエラーメッセージが表示されます。
> TreasureDetector.use
eval:376:in 'TreasureDetector.use': undefined local variable or method 'nop' for class TreasureDetector (NameError)
Did you mean? not
from (irb):11:in '<main>'
from <internal:kernel>:168:in 'Kernel#loop'
from eval_async:134:in '<main>'
from /bundle/gems/js-2.7.1/lib/js.rb:109:in 'Kernel.eval'
from /bundle/gems/js-2.7.1/lib/js.rb:109:in 'block in JS.__eval_async_rb'
from /bundle/gems/js-2.7.1/lib/js.rb:120:in 'block in JS.__async'
hint を実行してみましょう。すると TreasureDetector.nop が未実装であることがわかります。先のエラーとも一致しますね。
> TreasureDetector.hint TreasureDetector.nop is not implemented. Based on the method name, it seems nothing needs to be done. You can define it in IRB. => nil
nop メソッドを実行します。
> def TreasureDetector.nop = nil => :nop
再度実行するとお宝 DT-FIXME が得られます。
TreasureDetector.use You got a treasure! DT-FIXME => nil
Rubyらしく、クラスに後からメソッドを追加するのも簡単にできることを体験してもらおうというお宝でした。
12. 最初に出会っていたお宝
さて、ここまで進むと実は th-search で得られるヒントは特にありません。
ですが、ふと「今まで自分はどんなコマンドを打ってきたかなあ」などと考えて history コマンドなどを打ってみると
> history ... 0: You found a treasure! IR-HISTORY
と、自分が入力していない文字列が履歴の中に残っており、そこに IR-HISTORY というお宝があります。
実はゲーム開始時に上キーやCtrl+Pを押すだけで見つけられていたお宝でした。ゲームをやり込めばやり込むほど見つけるのが難しくなっていくお宝だったかもしれません。スッと見つかる人は見つかるけども、苦労する人は苦労する面白いお宝だったのではないでしょうか。
13. イースターエッグお宝
さて本当にまったくノーヒント、ゲーム中になんのとっかかりもないお宝です。ゲームやアプリケーションにはしばしばイースターエッグというものが隠されています。IRB宝探しのベースとなっているIRBにも IRB.send(:easter_egg) として隠されたプライベートメソッドで動くイースターエッグが仕込まれています。
https://github.com/ruby/irb/blob/v1.14.3/lib/irb/easter-egg.rb
そこでIRBイースターエッグを呼び出してみます。

するとPT-INGT というお宝が得られるのでした。
このお宝はゲーム中ヒントもまったく存在せず、かなり難しいお宝だったのではないでしょうか。しかし挑戦してくれた人の中には「きっとこのゲームの意図としてIRBに詳しくなってほしいという意図があるはず」とIRBのアップデートなどを調べて見つけてくれた人もいたようです。そこまで意図を汲んで解き明かしてくれて、とても嬉しく思います。
13個のお宝すべてを capture するとクリアメッセージが表示されます。
> th-capture PT-INGT You got a treasure! PT-INGT You have 13 treasures: - ST-HELLO - ST-CONST - ST-IVAR - RI-CLASS - RI-STONE - RI-ENCODED - ML-GOOD - DT-FIXME - ST-IRB - RE-🐾💎🐾 - IR-HISTORY - KE-CP290 - PT-INGT Congratulations! You have found all the treasures hidden in the world and are closer to the wisdom of the ancients. For more knowledge, please wait for the next TreasureHunt game update.
こぼれ話(不具合など)
このゲームを進める上でいくつか不具合の声がありました。原因がわかったら、それぞれこっそり修正しておこうかと思います。
対応ブラウザの問題
このゲームはブラウザ内で動くものですが、正常に動作しない環境も多かったようです。スマートフォン環境で安定動作を確認できたのはAndroid Firefoxのみで、他環境だと表示が崩れたりruby.wasmがクラッシュしたり、一部の人はなぜか正常動作したりと不安定なものでした。
表示崩れはSixel対応が問題のような気がしています。クラッシュはブラウザのスタック制限にひっかかってしまっていそうとの話をしていますが原因までは追いきれていません。
th-search コマンド結果が毎回一緒
上述した解説の通り、th-search コマンドは繰り返し実行することで新たなヒントを得ることができるコマンドです。しかし結果がなぜか毎回一緒だったという声もいくらか聞いています。原因解明に至っていませんが、実装に問題がありそうです。
→キーを押し続けると壊れる
ゲーム内で→(右矢印)キーを押し続けると表示や状態が壊れてしまうという不具合報告がありましたが、こちらはirb.wasmでも再現する不具合でした。
この不具合は以下PRで治るのではないかという話です。
https://github.com/ruby/ruby/pull/13142
ソースコードがメモリに残っている
いろいろするとゲームのソースコードを取り出すことができるようです。意図的に残していたわけではありませんが、そんなこともできそうとは思っていました。既知の手法に関しては取り出せないように塞いでしまってもいいかもしれない、と考えています。
まとめ
多くのお宝は手がかりを用意していましたが、ほぼノーヒントで答えに飛びつかないと見つからないようなお宝もあったのに全お宝発見した方も多数いたことは驚いています。本格的な謎解き要素は薄めだったかもしれませんが、Rubyの知識やRubyKaigi 2025の話題について触れられるゲームだったのではないでしょうか。

なおこのゲームのソースコードは以下リポジトリで公開しています。業務で急にirb.wasm上で動くゲームを作らなければいけなくなったときなどに参考にしてみてください。
https://github.com/heyinc/ruby-quiz-2025.storesinc.tech
自分で気に入ってるひどいコードは以下のCP290変換テーブルを作っているところです。手入力した温かみあるコードです。こういうコードこそAIが書いてくれたらいいのですが。
会社の宣伝
STORES の宣伝をします。
STORES はRubyで価値のあるプロダクトを作り、伸ばしていくことにとても気持ちのある会社です。プロダクト価値を伸ばす、技術を深堀りする、両輪あってこそのソフトウェアエンジニアという職種であると強く信じています。
会社で働くこと、技術を追求すること、ソフトウェアエンジニアのキャリアなどについてぜひ一度お話してみませんか。転職機運を問わないカジュアル面談もいつでも受け付けています。
また、IRBコア開発者でありRubyコミッターであり、そしてRubyKaigiキーノートを努めたことがある ima1zumi さんと tompng さんを深堀りする深堀りRubyKaigiというイベントを5/28(水)に開催します。 技術の粋を突き詰めたRubyistを深堀りする話、ぜひ聞きに来てください。