湖畔のフィッシング PARAMETER REFERENCE

▶ ゲームに戻る GitHub
このページはゲームのソース(src/data.js など)を直接読み込んで表を生成します。 数値が表示されない場合は file:// で開いています。./serve.sh などの ローカルサーバー経由か、公開版で開いてください。

このページは 湖畔のフィッシング の内部パラメータと計算式を、実装(src/)そのままに解説したものです。 魚と装備の表は ゲーム本体のデータファイルを読み込んで生成しているので、常に実際の値と一致します。 計算式は src/game.js の該当箇所をそのまま引用しています。

0単位と記号

長さ

ワールド 1 単位 = 1 m。魚の全長は cm。水深 m。

時間

式の dt は実時間の秒。実 1 秒 = ゲーム内 1 分(1 時間 = 実 1 分、1 日 = 実 24 分)。

0〜1 の量

テンション比と体力は 0〜1 の内部値、残り距離は実際のメートル。画面に出るのはテンションのバーだけで、距離と体力は内部値のまま(下の 1 章参照)。

以下、sp は魚種データ、rod/line/bait は装備データ、F はファイト中の状態を指します。

1ファイト — 4つのメーターの正体

ヒット後の駆け引きは、次の 4 つの状態量だけで決まります。毎フレーム、この 4 つが更新され、 3 つの終了条件のどれかに触れた瞬間に決着します。

ただし画面に出るのはラインテンションのバーだけです。 表示は「テンションだけ」と「なし(竿と音だけ)」の 2 択(U で切り替え)で、 残り距離・魚の体力・手応えの見出しを並べた従来のパネルは廃止しました。 数字が 3 本並ぶとそれを読む作業になり、竿のしなり・ドラグ音・水面の様子を見なくなるためです。 距離と体力は内部では今までどおり効いていて、寄っている感じは竿の角度と魚の見た目、 弱ってきた感じは浮いてくる魚と引きの軽さで分かります。

ラインテンション

F.tension / line.cap

糸に掛かっている張力。巻くと増え、離すと減る。line.cap(糸の強さ)に達した瞬間にラインブレイク。
警告(バーの点滅・画面端の赤)はテンションの % ではなく「切れるまでの残り秒数」で出す (SNAP_WARN_LEAD = 1.5 秒前から)。張力の上がる速さは魚 × サイズ × 竿 × 糸で 2 倍以上変わるので、 % 固定だと猶予が揃わない(旧方式の実測:80% から切れるまで通常 0.4〜0.6 秒、 突進中はどれも 0.26〜0.32 秒しかなく反応できなかった)。 残り秒数で出すと猶予がどの相手でも 1.5 秒に揃い、警告が始まるテンションは相手によって自動的に変わる (実測:通常 68〜77% / 突進中 45〜55%)。
さらに張力の上下そのものを TENSION_TEMPO = 0.5 で半分の速さにしてある(上がる・戻る・突進中の押し戻しを同じ倍率で遅くするので 押し離しの比率=難易度は保たれ、テンポだけ落ちる)。ゲージが 0 から満タンになるまで約 4〜5 秒。

残り距離 (非表示)

F.dist(実際のメートル)

魚までの距離(m)。ヒット時は掛けた距離そのままで始まり、巻けば減り、放せば魚に引かれて増える。掛けた距離+16m(糸の 62m 上限まで)で逃走/0.7m まで寄せ、かつ魚が水面から出たら取り込み。近くに掛ければ寄せる距離も短い。

魚の体力 (非表示)

F.stamina(1 → 0)

魚のスタミナ。テンションを掛けている間だけ減る。減ると引く力が弱まり、突進しなくなり、水面に浮いてくる。

突進(ラン)

F.running / runTimer

周期的に発生する暴れ。発生中は引く力が 2 倍。「走った!」の表示中に巻くと一気にテンションが上がる。

毎フレームの更新式(実装そのまま)

// 引きの強さ。突進中は2倍、体力が尽きると半分になる
pull = F.pull0 * (F.running ? 2.0 : 1.0) * (0.5 + 0.5 * F.stamina)

// 糸が短いほど衝撃を吸収できない=テンションが跳ねやすい(14m 以上で 1.0)
shortLine = clamp(1.35 - F.dist * 0.025, 1.0, 1.35)

if (巻いている) {
  // リールの立ち上がり(慣性)。押し始めは巻けず、押し続けると乗ってくる
  F.spin  = 1 - (1 - F.spin) * exp(-dt / 0.22)
  resist  = clamp(1.70 - pull * 0.75, 0.35, 1.60)   // 強い魚ほど巻けない
  F.dist    = max(0.15, F.dist - rod.reel * 4.6 * resist * (1 + F.dist * 0.012) * F.spin * dt)   // m/s
  F.tension += (0.08 + pull * 0.55) * shortLine * line.shock * 0.5 * dt / rod.power
} else {
  F.spin   *= exp(-dt / 0.12)                       // 離すとリールは止まっていく
  F.dist    += pull * 0.70 * dt                     // 出る糸は距離に関係なく m/s
  F.tension -= (1.30 + pull * 0.30) * P.tensionDecay * line.cap * 0.5 * dt
  if (F.running) F.tension += pull * 0.30 * shortLine * line.cap * 0.5 * dt   // 走られると放しても張る
}   // ↑ cap を掛ける=「バーの割合で見た回復速度」を糸によらず一定にする。
    //   掛けないと cap の大きい強い糸ほどバーが戻らず、強度を上げたのに不利になる
    // ↑ 3 か所の 0.5 が TENSION_TEMPO。同じ倍率なので押し離しの比率は変わらず、テンポだけ落ちる
F.tension = clamp(F.tension, 0, line.cap * 1.2)

// 体力:テンション比が高いほど速く削れる
tRatio = clamp01(F.tension / line.cap)
F.stamina -= (0.022 + tRatio * 0.17 + (巻いている ? 0.02 : 0)) * dt / max(0.4, sp.sta)

// 決着
if (F.tension >= line.cap) → ラインブレイク
if (F.dist >= F.span)     → 逃走(F.span = 掛けた距離 + 16m、上限は糸の長さ)
if (F.dist <= 0.7 && 魚の深さ <= 0) → 取り込み(= 水面から出るまで巻く)
// 深さの条件が無いと、桟橋の深穴で真下に掛けた魚が水深 10m のまま「釣り上げた」ことになる

// バー表示は 0.7m 分だけ内側に詰めて見せる(0% ちょうどで取り込みになるように)
表示比率 = clamp01((F.dist − LAND_M) / (F.span − LAND_M))

src/game.js — _updateFight()

細かい連打は空回りします。巻き取りには立ち上がり(F.spin)があり、押し始めは 0、 押し続けて 0.22 秒で 63%・0.66 秒で 95% まで乗ります。離すと 0.12 秒の時定数で落ちます。
40ms 間隔で叩くと乗りは 30% 前後で止まるので、同じ時間押し続けた場合と比べて巻き取りは 1/3 程度。実測(カーボン+PE・同じ 1 匹):連打 2.6 秒で −0.24m(=逆に出された)/押し続け 2.6 秒で +7.32m。
慣性のぶん全体が間延びしないよう、巻き取りの基準速度は 4.2 → 4.6 m/s に上げています。
引きの強い魚は長く押せない(すぐテンションが上限に来る)ので乗り切らず寄せられない—— これが装備の壁として効きます。逆に弱い魚は長く押せるので慣性はほぼ影響しません。
乗りは専用のバーを出さず、ファイトパネルの表示(巻き始め… → 巻いている)と リール音の変化で伝えています(メーターを増やすと画面が煩雑になるため)。
なお張力を見ずに一定間隔で叩くだけでは上限を超えて切れます (実測:チョウザメ平均 123cm・竹+ナイロンで、押 0.15〜0.80 秒/離し 0.15〜0.80 秒のどの組み合わせでも 100% ラインブレイク)。 テンションを見ながら間隔を合わせられる人だけ、初期装備でも大物を獲れます。
張力の増え方は立ち上がりに依存させていません。依存させると「連打すると張力も上がらない= 高い張力を保ったまま巻き比率を上げられる」ことになり、かえって連打が有利になりました (実測:張力を spin に比例させると連打の所要時間が 181 秒 → 59 秒に短縮)。
ファイト中の見た目:糸は竿先から魚の口(ローカル +X が頭なので +0.48 × 全長 の点)に付き、 ウキはその糸が水面と交わる位置に置きます(たるみ込みの曲線で二分探索するので、見えている糸の上にちょうど乗る)。 魚が跳ねて糸が水面と交わらないときはウキも口の位置へ。取り込み中も口から下がります。
ファイトパネルの案内文は出しません(操作は 2 つだけなので、状態は見出しの 「跳ねた/首を振っている/走っている」で足りるという判断)。

ヒット時の初期値

sizeF   = 0.55 + (この個体の全長 / sp.len[1]) * 0.75   // 0.55〜1.30
F.pull0 = sp.str * sizeF                              // この個体の基礎引き
F.len0  = 掛けた地点までの距離(m)                     // プレイヤー基準の水平距離
F.span  = clamp(F.len0 + 16, F.len0 + 4, 糸の長さ)         // ここまで走られたら逃走=バーの上限
                                                      // 糸の長さ = rod.cast + 16 + 8m(竿ごと)。目一杯投げると余裕が縮む
F.dist    = F.len0   F.tension = 0   F.stamina = 1   F.spin = 0
F.runTimer = rand(1.4, 3.2)                           // 最初の突進までの秒数

// 引きが強い魚(F.pull0 ≧ 1.2)は掛かった瞬間から走っている
F.running = F.pull0 >= 1.2
F.runDur  = rand(0.6, 1.2) * P.runDur

src/game.js — _setHook()

突進(ラン)の周期

// 体力が 0.16 以下になると突進しなくなる
継続時間 = rand(0.7, 1.9) * (0.7 + sp.agg * 0.5)
次まで   = rand(1.8, 4.2) / (0.6 + sp.agg * 0.8)

src/game.js — _updateFight()

ファイト中は魚種とレア度を伏せています。以前は pull0(= 種の str × サイズ係数)から 「軽い引き/強い引き!/とてつもない重さ…!」という手応えの見出しを出していましたが、 パネルごと廃止しました(手応えは竿のしなりと巻きの重さで伝わるため)。 pull0 自体は残っていて、ヒット時のメッセージ・突進の警告・エラ洗いの発生は レア度ではなくこの値を基準にしています。

ファイトの型(魚種ごとの振る舞い)

魚種には fight で 型が1つ割り当てられています。型は上の更新式に掛ける倍率と追加イベントだけで、 新しいメーターや操作は増えません(1.0 なら従来と同じ挙動)。 fight が無い魚種は fightPattern() がタグ・体型から自動で決めます。

ジャンプの扱い

ジャンプは突進の途中で発生し、跳ね終わるまで突進が終わりません。 つまり「走ったら糸を送る」という基本ルールを守っていれば自動的に対応できます。 送れていれば魚が余計に消耗し(jumpDrain /秒だけ体力が減る)、 巻いたままだと張力の増加が jumpTension 倍になります。

首振りの扱い

数値そのものを揺らすのではなく、振っている間だけ巻き取り効率が shakeReel 倍に落ち、張力の増加が shakeGain 倍になります。 メーターは正直な値のままなので「振っている間は手を止める」という判断が生まれます。

型はレア度が偏らないように割り当てています(各型にコモン〜レア以上が混在)。 ファイト中に型が分かってもレア度や魚種は特定できません。

質問への回答

Q. 「魚の体力」とは? 減ると何が起きる? 魚のスタミナ F.stamina(1 → 0)です。テンションを掛けている間だけ削れ、 減ると次の 3 つが同時に起きます。
  • 引く力が落ちる:pull の係数 (0.5 + 0.5×stamina) が 1.0 → 0.5 に。つまり最大で半減。 引きが弱まると resist が上がるので巻き取り速度も上がり、加速度的に楽になります。
  • 突進しなくなる:stamina ≤ 0.16 で新しいランが発生しません。
  • 浮いてくる:見た目の泳層が lerp(0.4, 掛けた深さ, stamina²) と浅くなり、水面で暴れる演出になります。
減る速さは ÷ max(0.4, sp.sta)。つまり sp.sta(体力値)が大きい魚は「粘る」=弱らせるのに時間が掛かります。 テンション比 0.7 を維持した場合の目安は (0.022 + 0.7×0.17 + 0.02) / sta ≒ 0.16 / sta /秒 → sta 1.0 の魚なら約 6 秒、sta 3.2 の湖の主なら約 20 秒でスタミナ切れ。 逆にテンションを掛けずに待っても魚は疲れません(0.022/sta しか減らない)。

水面から出るまで巻く

取り込みは残り距離だけでは決まりません。実際の取り込みと同じで魚が水面から出ることが条件です (魚の深さ <= 0)。これが無いと、桟橋の深穴で真下に掛けた魚が 水深 10m のまま「釣り上げた」ことになってしまいます(水平距離だけを見ていたため)。

// 体力から決めた泳層を、寄せるほど水面へ引き上げる。最後の目標は水面より上
riseFrom  = clamp(wantDepth * 1.1, 2.5, 12)   // 深い魚は早めに、浅い魚は直前から上がる(≒45度)
wantDepth = lerp(−0.12, wantDepth, clamp01((F.dist − 0.7) / riseFrom))   // −0.12 = LAND_LIFT_M

// 実際の Y は damp。ただし浮き上がる速さに上限を掛ける
ny = damp(魚.y, 水面 − wantDepth, 目標が上なら 5.5 / 下なら 2.4, dt)
魚.y = min(ny, 魚.y + 3.0 * dt)   // RISE_MPS:水面へ飛び出すのを防ぐ

src/game.js — _updateFight()

真下に深く掛けた場合は水平距離に余裕が無いので、傾斜だけでは間に合わず水面へ飛び出してしまいます。 そこで浮上速度そのものを 3.0 m/s で頭打ちにし、取り込みは深さの条件で待たせています。 その結果ファイトが縦方向に伸びます(実測:真下 2.5m・深さ 10m のケースで 0.9 秒 → 3.2 秒)。
水面を割った瞬間に飛沫が出て、出ている間も水しぶきが続きます(実測:水面付近で飛沫・波紋が各 7 回)。 そのあと landing 演出が 0.85 秒かけて目線の高さまで持ち上げます。
実測(伝説+PE3号・警告どおりに離す操作):

状況掛けた距離/深さ取り込み時の深さ所要
遠投・深場(チョウザメ)28.4m / 18.0m0.00m15.6s
通常(コイ・浅場)18.4m / 3.0m0.00m6.0s
遠投・浅場(ブラックバス)23.4m / 2.0m0.00m5.0s
桟橋の深穴(真下に湖の主)2.5m / 10.0m0.00m3.2s

深さの条件を待つ間も巻けてしまうので、F.dist には 0.15m の下限を入れています (入れないと魚が足元を通り越して背後に回ります)。浅場では先に距離が下限に達し、 そこからその場で巻いて水面まで持ち上げる動きになります。

Q. ラインの「素材 × 号数」って何が変わるの? ラインはナイロン/フロロカーボン/PE の3 素材 × 1〜3号の 9 種類。号数は同じ素材内の強度の階段 (1号<2号<3号)だけを動かし、素材が強度以外の性格(一長一短)を決めます。
  • cap(強さ=切れる張力):ナイロン1号 0.72 〜 PE3号 3.11 まで、号数と素材の両方で伸びる (1号<2号<3号 かつ ナイロン<フロロ<PE)。
  • line.shock(テンションの増え方の倍率):ナイロン 0.90(伸びが衝撃を逃がすので上がりにくい=寛容) → フロロ 1.00 → PE 1.10(伸びがほぼ無く直に伝わる=やや厳しい)。張力の増加は (0.08 + pull×0.55) × line.shock / rod.power /秒なので、PE は cap こそ高いが上がる速さも速い ——強い代わりに雑に巻くとすぐ切れる、という一長一短。
  • line.attract(アタリまでの速さ):見えにくいフロロが最速、ナイロンは標準、 目立つ PE はやや遅い(フロロ>ナイロン>PE、2号基準で 1.15 / 1.00 / 0.88)。 同じ素材でも号数が細いほど速い(1号>2号>3号)——太いほど目立って遅くなる。
  • line.biteWindow(アワセ猶予):伸びないぶんアタリが直に伝わる PE が 1.2 で最長、 ナイロンは 0.85 で最短、フロロは 1.0(標準)。
Q. ロッドの「飛距離(cast)」「竿の力(power)」「巻取(reel)」の違い? cast は狙える最大距離(m)そのもので、竹 10 → グラス 38 → カーボン 52 → 名匠 68 → 伝説 84。 初速の上限はこの値から弾道で逆算されます。最初の竹竿は意図的にごく短く、ほぼ足元しか狙えません。 岸沿いの水深は距離とともに伸びていく(38 m で中央値 約8 m、65 m で 約19 m)ので、中層〜深場・深淵は 竹竿では基本的に手が届かず、より長い竿を買って初めて狙えるようになります。
最弱キャスト(約1.7 m)も竿によらず同じなので、足元狙いが不利になることはありません (むしろ竹竿は「足元しか狙えない」竿なので、この短さそのものが個性です)。
桟橋の先端は元々「先端だけで深場が狙える」設計(水深 6〜19m)の構造物なので、竹竿でもそこから わずかに投げれば中層〜深場に届くことがあります(実測:ほぼ確実に深場止まり、深淵まで届く湖は 2%程度)。深場の魚を安定して狙うにはレベルによる解禁も必要です。
power はテンションの増加を割り算で抑える値(=竿が引きを吸収する)で、tension += … / rod.power。 reel は巻き取り速度の倍率で、dist -= rod.reel × 4.6 × resist × (1+dist×0.012) × F.spin × dt(m/s)。 power は「切れにくさ」、reel は「早さ」に効きます。ラインブレイクを減らしたいなら line.cap と rod.power、 ファイトを短くしたいなら rod.reel です。attract はファイトには無関係(アタリの速さと抽選)。
power はもう一つ効きます:張力の増えが遅くなる=長く押していられる=リールが乗る、という経路です。 チョウザメ(平均 123cm・60 回試行)を連打で獲ろうとすると、伝説+PE3号でようやく獲れる(60/60・約 143 秒)だけで、 竹+ナイロン2号・グラス+フロロ2号・カーボン+PE2号はどれも 0/60。
同じ相手を警告どおりに離す操作なら、竹+ナイロン2号 2/60 → グラス+フロロ2号 59/60(約 31 秒)→ カーボン+PE2号 60/60(約 22 秒)→ 伝説+PE3号 60/60(約 12 秒)と、装備が素直に効きます。 つまり連打は装備が揃っていても損で、テンションを見て離すのが唯一の正解です。
逃走は「掛けた地点より 16m 走られる」ことで起きます。 放している間、距離は pull × 0.70 m/秒で増えます。強い魚の突進中(pull が 2 倍)は 放すだけで毎秒 4m 以上取られることもあり、巻き取り(rod.reel×4.6×resist×F.spin)が それを下回る装備だとどう操作しても逃げられます。これが装備の壁です。

ファイト・シミュレータ

実装と同じ式で 1 フレーム 1/60 秒ずつ回し、「テンション比が閾値を超えたら離す」という操作を 指定した反応遅れで行った場合の結果を、200 回試行して集計します。

🎣 単体シミュレーション

テンション比 残り距離 魚の体力 突進中 首振り ジャンプ — 破線 = 離す閾値

装備 × 魚 の対応表

同じ条件(離す閾値・反応遅れ)で全魚種を 120 回ずつ試行した結果です。取込率が実質的な「その装備で獲れるか」の指標になります。

📊 一括シミュレーション

2アタリ — どの魚が食いつくか

着水すると biteTimer 秒後に「食う魚」を 1 匹抽選します。抽選は重み付きで、 重みは次の 7 つの積です(src/game.js — rollSpecies())。

w = sp.spawn                                            // ① 基礎出現量
  × 生息水深の適合 depthFit(sp, その場所の水深)          // ② どこに居るか
  × sp.times[時間帯]                                    // ③ 朝/昼/夕/夜
  × sp.weather[天候]                                    // ④ 晴/曇/雨
  × エサ相性                                            // ⑤ 魚種ごとの好き嫌い表(pref)
  × 遊泳層[プレイヤーが選んだ層]                        // ⑥ 水中のどこで食うか
  × (レア度≧3 なら bait.rare) × rod.attract             // ⑦ レア補正
  × 底質の相性 ^ 層の底寄り度                            // ⑧ 砂地/岩場/泥底
  × (近くにストラクチャーがあれば その倍率)              // ⑨ 沈み岩・立ち枯れ
  × レベル解禁(エピック/レジェンドのみ)

// ② 生息水深:sp.depth = [d0, d1] は「その魚が居る場所の水深」
d0 ≦ 水深 ≦ d1              → 1.00
水深 < d0                   → 1 − (d0 − 水深) / max(1.0, d0 × 0.35)   // 下限 0
水深 > d1                   → 1 − (水深 − d1) / max(2.5, (d1 − d0) × 0.9)  // 下限 0
  // 例:ドジョウ [0.5, 4] は水深 7.2 m 以上で 0 = 深場には居ない
  //     湖の主 [14, 28] は水深 9.1 m 未満で 0 = 浅場には出て来ない

// ⑥ 遊泳層:魚ごとに 表層/中層/底層 それぞれの食いつきの重み(0〜1)を持つ。
//    プレイヤーの 3 択と直接掛ける(絶対メートルの比較はしない)ので、
//    浅場でも「底物は表層で食わない」「藻場のライギョは表層で食う」が成立する
//    一部の魚は時間帯ごとに重みが入れ替わる(日周鉛直移動)。
//    また ② の生息水深も sp.diel で時間帯ごとに上下する(夜に浅場へ差す魚)

// ⑧ 底質(タグ平均の倍率)。底寄り度は 底層 1 / 中層 0.35 / 表層 0.1 乗
砂地: bottom 1.30 carp 1.15 deep 1.10 trout 1.05 predator 0.90 weed 0.70
岩場: predator 1.50 weed 1.35 trout 1.25 bottom 1.10 deep 0.95 carp 0.70
泥底: carp 1.50 bottom 1.45 deep 1.10 weed 1.05 predator 0.80 trout 0.75

// ⑨ ストラクチャー(ウキから 4.5m 以内)
肉食魚・藻場 ×1.45 / 底物 ×1.3 / 鱒 ×1.15 / それ以外 ×0.95

// ⑩ レベル解禁(伝説タグ以外は下限 0.008 = 解禁前でも極低確率で掛かる)
レア度4(エピック)  × max(0.008, clamp01((Lv − 2) / 5))  // Lv3 から出始め Lv7 で全開
レア度5(レジェンド)× clamp01((Lv − 5) / 6)           // 湖の主・イトウは tags に legend → 下限なし
エサ相性(⑤)は、魚種ごとに直接持たせた好き嫌い表 SPECIES[].pref(エサの並び順に 0〜3 の整数)を PREF_MULT = [0, 0.5, 1.0, 1.6] で倍率に変換したものです(baitPrefMult())。 例:ブラックバスの泳がせ(小魚)への pref は 3(好物)→ 倍率 1.6。
以前は tags のうちエサの aff に載っているものの平均で計算していましたが、 無関係なタグを共有しているだけで食いつきが水増しされる事故が起きたため (例:ワカサギが deep タグを共有するだけで泳がせ餌に寄ってきた)、 魚種ごとに直接持たせる方式に変更しました。生態に合わない組み合わせは pref=0 で絶対に釣れません。

いま何が釣れる? 出現確率シミュレータ

条件を選ぶと、上の式そのままで「次のアタリがどの魚か」の確率を計算します。

🐟 アタリ抽選の確率

タナ(狙う層)を決める — 生息水深 × 遊泳層

魚のデータは2 軸に分かれています。 生息水深(depth)はその魚が居る場所の水深、 遊泳層(layer)は水中のどの層で食うかです。 プレイヤーは E の仕掛けウインドウで 表層/中層/底層 の 3 択を選び、 抽選では「その場所の水深が合っているか」×「その層で食うか」の両方が掛かります。

なぜ 2 軸なのか

1 軸(生息層だけ)だと 水深 20 m の水面下 3 m と 水深 3 m の底 を区別できず、深場の表層でドジョウが釣れてしまいます。 また「浅場に居るが表層で食う魚」(ライギョ)と「浅場の底に居る魚」(ザリガニ)を書き分けられません。

タナの実際の深さ

clamp(水深 × 比率, 0.35, 水深 − 0.35)。 比率は表層 15% / 中層 50% / 底層 88%。この深さは見た目(仕掛けの位置)と底層のゴミ判定に使い、 魚の抽選には⑥の遊泳層の重みを使います。

遊泳層の既定(タグから自動)表層中層底層当てはまる魚
底物・ヒゲ物・甲殻類0.050.401.00 ドジョウ・ナマズ・ウナギ・チョウザメ・ザリガニ・モクズガニ
コイ科0.150.701.00 マブナ・コイ・ニゴイ・アオウオ
藻場の肉食魚1.000.850.30 ライギョ・ブラックバス
鱒0.801.000.45 ニジマス・ヤマメ
中層の小物0.901.000.35 メダカ・ウグイ・オイカワ・タナゴ・ワカサギ・ブルーギル・ハス
深場の大型肉食魚0.300.851.00 湖の主
タグと合わない魚だけ個別に指定しています:メダカ・モロコ・イワナ・ソウギョ(水面の水草)・ ビワマス・サクラマス・アリゲーターガー(水面で空気を吸う)・イトウ。下の魚データ表に実際の値が出ます。

src/data.js — swimLayer() / depthFit() / RIG_LAYERS src/game.js — rigLayer / rollSpecies()

実測(Lv12・カーボン・ミミズ):
・水深 3 m 昼:表層でドジョウ 0.2%/底層で 2.7%(夜・雨なら 4.0% / 23.9%)
・水深 20 m:どの層でもドジョウ 0%(生息水深の外)
・藻場 2.5 m:ライギョは表層 10.2%/底層 3.6%、ザリガニは表層 0.5%/底層 12.4%
・水深 18 m 夕:表層=ワカサギ 50%、中層=ワカサギ 29%・ビワマス 22%、底層=ナマズ 24%・ビワマス 17%

時間帯で釣れる深さが変わる(日周移動)

現実の湖では、多くの魚が1 日のうちで泳ぐ深さを変えます。代表的なのは 日周鉛直移動(diel vertical migration)で、動物プランクトンが昼は捕食者を避けて深みに沈み、 夜に浮上するのに合わせて、それを食べる魚も同じように上下します(ワカサギの氷上釣りで 「昼は底、朝夕は浮く」と言われるのはこれ)。ほかにも

  • 朝夕のマズメ:鱒類・ブラックバスは水温と光量が落ちる朝夕に浅い層へ差して水面を意識し、日中は深い層・カバーの底に落ちる
  • 夜の接岸:ナマズ・ウナギ・コイ・エビ・カニは夜になると浅場へ出て餌を探し、日中は深場の底や穴に潜む
  • 逆パターン:ライギョは変温動物で日中の高水温を好み、昼に水面のカバーで浮いている。夜は沈む
  • 朝夕の待ち伏せ:イトウは朝夕に浅場の表層で待ち伏せ、日中は深場の底へ落ちる

実装は 2 つの入口を用意しています。層が入れ替わる魚は layer を時間帯ごとの表にし、 浅場と深場を行き来する魚は diel(−1 で浅場寄り 〜 +1 で深場寄り)で 生息水深の帯そのものを上下させます(ずれ幅は帯の 35%)。両方持つ魚もいます。

src/data.js — swimLayer(sp, band) / depthBandAt(sp, band) / dielNote()

図鑑には 🕒 日中は深く・朝夕は浅く のように自動生成した一言が出ます(時間帯ごとの層と生息水深から計算)。 仕掛けウインドウ(E)の「ここで食いつく魚」もいまの時間帯で判定するので、 同じ場所・同じ層でも昼と夜で ◎○△ が入れ替わります。
実測(水深 14 m・くもり・ミミズ/ワカサギの出現率): 朝 表層 43.6% → 昼はどの層も 16〜19% → 夜 表層 52.3% / 底層 4.2%。
ナマズ(水深 4 m の底層):昼 0% → 夜 7.9%。ライギョ(水深 2.5 m の表層):昼 3.8% → 夜 0.7%。

アタリまでの流れとタイミング

段階長さ・条件内容
① 着水—半径 lerp(1.0, 3.2 + power×2.0, 1−acc) m の魚が lerp(0.8, 5.0, 1−acc) 秒驚く(acc = キャスト精度。下記)
② アタリ待ちrand(2.2, 7.0) ÷ attract 秒
水深 < 0.9 m ならさらに ×1.7
attract = bait.attract × rod.attract × 天候 × (1 + 0.18×acc)
③ 抽選 → 接近実測 3〜9 秒
(22 秒で諦めて再抽選)
18 m 以内に同種がいればその個体が寄る。いなければ 5.5〜9.5 m 先に生成して寄せる。遊泳速度 (0.78 + 全長cm/100 × 1.7) × 1.45 m/s
④ 前アタリrand(0.7, 1.9) 秒
レア度 4 以上は ×1.5
ウキが小刻みに震える。ここで巻くと回収になってしまう
⑤ 本アタリlerp(1.55, 0.85, レア度/5) 秒
トラウト系はさらに ×0.8
アワセ猶予。ウキが 0.42 m 沈み ! が出る。コモン 1.55 秒/レジェンド 0.85 秒/ヤマメ・イワナ等 0.68〜1.24 秒

見えている魚は釣れるのか

湖には常に 22 匹(描画品質 軽量 14/高品質 30)が泳いでいて、水中カメラ(V)で見えるのはその実体です。 ただし見えている個体を狙い撃つことはできません。処理の順番が逆で、 先に魚種が抽選され、あとからその種の個体が選ばれるためです。

// ③ 抽選 → 個体選び
1. rollSpecies(水深, エサ, 時刻, 天候, Lv) で魚種を決める
2. その魚種の個体が ウキから 18 m 以内・回遊中(wander)・驚いていない?
   ├ いる  → いちばん近いその個体が実際に泳いで寄ってくる
   │          (=水中カメラで見ていた魚が、そのまま掛かる)
   └ いない → ウキの 5.5〜9.5 m 先・水深 0.8 m 以上に新しく生成して寄せる
3. 寄ってきて 0.5 + 全長cm×0.004 m まで近づいたら 前アタリ(④)へ

src/game.js — _chooseBiter() / src/fish.js — Fish.update() の 'approach'

目安にはなる

まわりを泳ぐ 22 匹も同じ rollSpecies(水深) で湧いているので、 見えている魚種=その場所・その時刻に釣れる魚の目安になります。最初は自分の周り 6〜52 m に配置され、 78 m 以上離れる/水深 0.35 m 未満に乗り上げると 26〜58 m 先へ入れ替わります。

ただし条件は違う

泳いでいる魚の抽選にはエサ・タナ・ロッド・レベル解禁が掛かりません。 つまり Lv1 でもレジェンドが泳いでいるのは見えます。アタリの抽選ではレア度 4 が Lv3〜7、 レア度 5 が Lv6〜11 で解禁されますが、伝説(湖の主・イトウ)以外は解禁前も重み 0.008 で残るので、 Lv1 でもエピックが掛かることがあります。

魚が逃げる条件

きっかけ範囲・時間挙動
着水(キャスト) 半径 lerp(1.0, 3.2 + power×2.0, 1−acc) m
時間 lerp(0.8, 5.0, 1−acc) 秒
(個体ごとに ±25%)
範囲内の魚が驚く。回遊中なら着水点の反対へ 14 m の目標を取り直し、遊泳速度が 1.5 倍になる
仕掛けの回収2.2 秒 寄ってきていた個体が逃走。次のアタリは投げ直しから
アワセ失敗3 秒 その個体が22 m 先・1 m 下へ逃走。待機は続き、次の抽選まで 3〜6 秒
ライン切れ・逃走—掛かっていた個体が逃走して回遊に戻る

src/fish.js — FishSchool.startle() / src/game.js — _onLandWater(), _retrieve(), _missBite()

驚いている魚はアタリの相手になりません。f.startle > 0 の個体は 「近くの個体」の候補からも、魚種の抽選を近くの魚へ寄せる near からも外れます。
以前は状態が回遊(wander)のままなので抽選から外れず、18 m の圏内に留まるかぎり普通に掛かっていました (逃げる距離 14 m < 圏内 18 m なので、実質ほとんど効いていなかった)。 いま外しているのは、キャスト精度が「着水点の魚が残るかどうか」として実際に釣果に効くようにするためです。 驚きは最長でも 5 秒なので、外した投げが無駄になるわけではなくその分アタリが遠くの魚に置き換わるだけです。

ゴミが釣れる確率

P(ゴミ) = 0.085 × bait.junk
        × (水深 < 1.4 m ? 1.8 : 1)
        × (底層 ? 1.5 : 0.55)              // ゴミは底に沈んでいる
        × (総釣果 < 3 匹 ? 0.3 : 1)

ミミズ(junk 1.0)で底層なら 12.8%、同じミミズを中層・表層にすると 4.7%。 秘伝の撒き餌(junk 0.25)なら底層でも 3.2% です。ゴミは泳いで来ないのでその場で即・前アタリになります。 底層は大物とゴミが同時に増えるのが、層を上げる動機になります。

3魚データ(全 34 種)

src/data.js の SPECIES をそのまま表にしています。 str=引きの強さ、sta=体力(粘り)、agg=突進頻度。 内訳は魚 30 種+ゴミ 4 種で、魚のうち 3 種は甲殻類(テナガエビ・ザリガニ・モクズガニ)です。 甲殻類は体型が魚と違うため専用メッシュで作られ、shape が shrimp / crayfish / crab になっています(扱いは魚と同じ。図鑑でも「魚」として数えます)。 カニの len は甲幅、wc はそれに合わせた係数です。

時間帯・天候の倍率

③④ に掛かる倍率です。時間帯は 朝 4:30–7:30 / 昼 7:30–16:30 / 夕 16:30–19:30 / 夜 それ以外。

全長・重さ・売値・経験値の式

// 全長の抽選:小さい個体が出やすい(べき分布)
p    = 2.1 − min(1.2, bait.rare × 0.25)
全長 = len[0] + (len[1] − len[0]) × random()^p       // 期待値は範囲の 1/(p+1) ≒ 32%

重さ(kg)   = sp.wc × 全長³ / 100000
売値(G)    = round(sp.value + sp.perCm × 全長 × (0.75 + 0.5 × 範囲内の位置))
経験値     = round(レア度XP + 全長 × 0.45 × (1 + レア度 × 0.22))
             // レア度XP = ゴミ2 / コモン8 / アンコモン18 / レア40 / エピック95 / レジェンド260

src/data.js — rollLength(), weightOf(), valueOf(), xpOf()

秘伝の撒き餌(rare 2.2)は p が 2.1 → 1.55 になり、期待サイズが範囲の 32% → 39% に上がります。 レア度 3 以上の出現重みも 2.2 倍。

サイズ別の実例

4装備データ

ゲーム内のショップでは数値を出していません(「巻き取り 速い」「大物 出やすい」のように言葉だけ)。 内部の倍率はここに載せているので、比較したいときはこの表を見てください。 ロッド・ラインは価格順の階段なので同種内の順位、エサは 1.0 を基準にしたしきい値で言葉に変換しています。

ロッド

reel=巻き取り速度倍率(dist -= reel×4.6×resist×F.spin×dt m/s)、power=テンション増加の割り算(大きいほど切れにくい)、attract=アタリの速さと抽選重み。

ライン

cap=テンションの上限(この値に達すると切れる)。

エサ

attract=アタリの速さ、rare=レア度 3 以上の重み&サイズ補正、junk=ゴミ率の倍率。 食いつきそのものは魚種ごとの pref 表(下記「エサ相性」参照)で決まります。 エサは層を持ちません(狙う層=タナは E の仕掛けウインドウで 表層/中層/底層 の 3 択で決めます)。 ルアーは投げて放置する操作と噛み合わないため廃止し、すべて置き餌・生き餌に置き換えました。

エサは消耗品(在庫)

// 買うとき:束(pack)ぶんまとめて在庫に入る。何度でも買える
在庫[id] += pack        // ミミズ 10 / アカムシ 20 / 練り餌 20 / イクラ 15
                       // 川エビ 12 / 小魚 10 / 秘伝 3

// 減るとき:魚がエサに触ったときだけ 1 個
アタリを逃した   → 80% で取られる(20% は無事に残り、そのまま待てる)
                   取られたら針が空なので強制回収
ラインブレイク   → 取られる(仕掛けごと)
走り切られて逃走 → 取られる
取り込んだ       → 減る(ゴミを釣ったときも同じ)

// 減らないとき
何も来ずに回収 / 魚が寄って来ただけ / 触られる前に回収 → そのまま使える
根掛かり・糸が伸びきって強制回収 → そのまま使える

// 0 個になったら
在庫のある一番安いエサへ自動で持ち替え → 無ければキャスト不可
(ミミズは 0G なので、ショップで必ず立て直せる)

src/game.js — buy(), _useBait(), _missBite(), _land(), _lineSnap(), _fishEscaped()

1 個あたりの値段は狙う魚の値段に釣り合うように束の数を決めています (アカムシ 9G/個、練り餌 12.5G/個、イクラ 60G/個、川エビ 133G/個、小魚 380G/個、秘伝 4000G/個)。 レア度 1 の魚は 20〜116G、レア度 3 は 484〜1785G、レア度 5 は 1 万 G 超なので、 高級エサで小物を狙うと赤字になります。

5キャストの物理

キャストは「狙った距離に当てる」操作です。視線が水面と交わる点が狙い点で、そこに水色の輪が出ます。 メーターの緑の帯はその距離に必要なパワーの位置。帯の中で離すと狙い通りに落ちます。

// ① 狙い点:視線と水面(y=0)の交点。見下ろすほど近く、水平で遠く
狙い距離 = clamp(視線が y=0 に達する水平距離, 最短キャスト, rod.cast) m
           // 上限は竿ごと(竹 26 / グラス 38 / カーボン 52 / 名匠 68 / 伝説 84 m)

// ② 目印:その距離に落ちるパワーを弾道の逆算(二分探索12回)で求める
仰角成分 = clamp(0.46 + pitch × 0.55, 0.16, 0.95)
初速     = lerp(0.5, 初速上限, power) m/s
           // 初速上限は rod.cast × 1.07 が出る速さを弾道から逆算(竹 9.6 〜 伝説 40.6 m/s)
毎フレーム: v.y −= 9.8·dt ;  v ×= (1 − 0.0055·|v|·dt)    // 重力 + 空気抵抗

// ③ 判定:長押し中は 1.05 /秒で 0↔1 を往復(ピンポン)
誤差     = |power − 目印|
狙い通り = 誤差 ≤ 0.06(= CAST_TOL)
  → パワーを目印に補正して発射(=小さな誤差は吸収され、狙い点にきっちり落ちる)
外した   → そのパワーの距離に着水(手前に落ちる/飛ばし過ぎる)

// ④ キャスト精度 acc:CAST_TOL 以内なら 1、CAST_MISS=0.30 まで外すと 0
acc = clamp01(1 − max(0, 誤差 − 0.06) / (0.30 − 0.06))
  // 着水の水音・飛沫が ×(1 − acc×0.55)、驚かす半径と時間、アタリの速さが連続に変わる

糸の最大長 = rod.cast + 16(走られる余裕)+ 8 m
             // 竹 34 m 〜 伝説 108 m。超えるとその場で着水/待機中に超えると強制回収

// 最弱キャスト(初速 0.5 m/s)は竿によらず約 1.85m。ロッド先端が足元より
// 1.39m 前に出ているので、初速を絞ってもそれ未満にはならない(8 章)

src/game.js — _updateAim(), _solveTargetPower(), _castVelocity(), _releaseCast()

視点(pitch)狙い距離必要パワー実際の着水用途
−1.01.9 m0.351.85 m真下・足元
−0.455.7 m0.395.6 m竹竿の中距離
−0.309.0 m0.729.0 m竹竿でギリギリ届く距離
−0.20 以上rod.cast〜0.8同左最大飛距離(竿で決まる。竹は 10m で頭打ち)
桟橋の先端・竹竿での実測。狙いと着水の誤差は 0.1〜0.5 m。 最短キャストは初速 0.5 m/s の弾道+ロッド先端のオフセットで決まり、立ち位置とロッドの高さでわずかに変わります。
竹竿は狙い距離が 10m で頭打ちになるため、必要パワーは 0.8 前後で収まる(「狙い通り」を出す余地は残る)が、 より遠くを見ても狙い距離自体は伸びない。

2つの輪

水色=狙い点、黄色=いまのパワーでの着水予測。 重なると緑になり、メーターも光ります。予測は実際の弾道を 0.055 秒刻みで積分した落下点です。 画面中央の照準(レティクル)は置いていません:狙いは水面の輪だけで示します。 輪は実寸(半径 0.5〜0.66 m、着水予測は 0.86〜1.12 m)なので、遠いほど小さく・水面に寝て見えます — それも距離感の手がかりです。狙い距離と水深は HUD に数値でも出ます。

精度の効果

ジャストに近いほど着水地点の魚が逃げません。以前は「狙い通りか外したか」の 2 択で、外した側はどれも同じ扱いでした。いまは acc で連続に効くので、 目印に近づける練習がそのまま釣果になります。遠投でも足元でも同じ条件で成立するので、 浅場狙いが不利になりません。実測は下表。

キャスト精度で何が変わるか

桟橋の先端から 20 m 先へ投げ、着水点の 0.8〜6.3 m に 12 匹並べた状態で実測 (目印 0.78・実際の _releaseCast() → _onLandWater() を通した値)。

誤差精度 acc驚かす半径驚く時間散った魚散った最遠アタリ倍率
0.001.001.0 m0.8 s1/120.8 m×1.18
0.061.001.0 m0.8 s1/120.8 m×1.18
0.100.831.7 m1.5 s2/121.3 m×1.15
0.150.622.5 m2.4 s4/122.3 m×1.11
0.200.423.2 m3.2 s6/123.3 m×1.08
0.30 以上0.004.8 m5.0 s10/125.3 m×1.00
半径・時間は個体ごとに ±25% ばらすので、「散った魚」「散った最遠」は 1 回の試行の値。 ジャストなら足元の 1 匹が 1 秒ざわつくだけで、大外しすると 5 m 圏の 10 匹が 5 秒散る。 驚いている魚はアタリの相手に選ばれないので、大外しの直後は遠くから来る魚しか掛からない。
外したときはトーストで「飛ばし過ぎた… 魚が散った/魚を少し散らした」と結果を返します(境目は acc 0.45)。 着水の水音と飛沫も ×(1 − acc×0.55) で小さくなるので、決まったキャストは音でも静かだと分かります。

糸が掛かる判定

糸(ロッド先端 → ウキ)が桟橋・陸の張り出し・岩を貫通する向きには投げられません。 ためている間に判定して着水予測リングが赤くなり、それでも投げると着水時に 「〜に糸が掛かった」で自動回収されます。待機中に歩いて障害物を挟んだ場合も同じです。

// 桟橋:線分 vs OBB(スラブ法)。床(桁を含む厚み)と先端の手すりの2箱
床  : along[0, 長さ] × side[−1.62, 1.62] × y[床−0.42, 床+0.18]
手すり: along[長さ−2.3, 長さ] × 同 × y[床−0.42, 床+1.05]

// 地形・岩:たるみ込みの曲線を 1.6m 刻みでサンプリング
サンプル数 = clamp(ceil(距離 / 1.6), 8, 40)
y(t) = lerp(先端, 着水点, t) − 6.75t²(1−t) × min(距離×0.16, 1.2) × たるみ
掛かる条件: y(t) + 0.22 < 地面の高さ(または岩の上端)
たるみの形 6.75t²(1−t)(util.js の lineSagProfile)は 描画・ウキの位置・地形との当たりで共有しています。以前は sin(πt) でしたが、 これは t=0 での傾きが π もあり、竿先の時点で糸が垂れ始めてしまうのが難点でした。 実際の竿先はガイドで糸が折れ返る=竿と糸は鋭角に交わり、糸自体はピンと張って出ていくので、 t=0 で値も傾きも 0 になる形に変えました。最大は t=2/3 のウキ寄り(係数 6.75 = 1 / ((2/3)²·(1/3)) は最大値を 1 に正規化するもの)。 実測:竿先の 1 点目の垂れ 0.078 m → 0.007 m、たるみ最大の位置 t=0.50 → t=0.65。

src/terrain.js — dockBlocksSegment(), lineBlocked()

判定に 0.22 m の余裕を入れているので、岸をかすめる程度では掛かりません。 岩は実際の当たり判定の円+上端の高さを使っており、デバッグ表示(F3)で可視化できます。

6環境(時間・天候・地形)

時間

実時間 1 秒でゲーム内 1/60 時間(= 1 分)進みます。1 日 = 実 24 分、夜は実 9 分ほど。急ぐときはメニューの「ひと休み」で 1 時間ジャンプできます。

時間帯タグゲーム内時刻主な魚
dawn(朝)4:30 – 7:30ヤマメ・イワナ・イトウ(×1.9)
day(昼)7:30 – 16:30メダカ・モロコ・ブルーギル・ライギョ・ソウギョ
dusk(夕)16:30 – 19:30ニジマス・ブラックバス・ビワマス・イトウ(×1.8)
night(夜)19:30 – 4:30ナマズ・チョウザメ・ガー・湖の主(×1.7)

天候

2.5〜6.5 ゲーム内時間ごとに抽選(現在の天候は重み ×0.35 で出にくい)。bite はアタリまでの時間に掛かる倍率です。

天候抽選重みbite(アタリ速さ)雲量雨見た目の変化
☀️ 晴れ441.000.140影が強い・水面のきらめき
☁️ くもり341.120.720日光 −45%・環境光 +35%
🌧️ 雨221.300.950.85波 ×2.15・視界 210 m・雨粒と白波
波の大きさ 1 + 雨×1.15 + 雲量×0.18。雨は魚の weather 倍率にも効く(ウグイ・ドジョウ・ナマズ・イワナ・湖の主 = 1.8)。

水中の見え方(描画)

水面を半透明で被せる方式をやめ、シーンを一度テクスチャに描いてから水中の減衰で合成する方式にしています。 半透明だと「湖底の明るい砂」と「水の濃紺」の差が大きく、不透明度をどれだけ滑らかに変えても 見える所と見えない所の境目が線として出てしまうためです。

// 1) 毎フレーム、水面(と波紋・しぶき)を隠してシーンを RT に描く
//    空・雨・森の木は水越しに写らないので除外。解像度は 0.4〜0.7 倍
water.capture(renderer, scene, camera)     // 影はこのパスで更新し本描画では再利用

// 2) 水面シェーダで、深度テクスチャから「水面の奥にある物までの距離」を出す
path = 水面から、その奥にある物までの距離(視線に沿った長さ)

// 3) 下から来る光と、水面の反射をフレネルで混ぜる
透過率 = exp(−吸収 × path)                 // 吸収 = (0.46, 0.20, 0.13)/m = 赤から消える
濁り   = smoothstep(7, 30, カメラからの距離)
下     = mix(mix(水の色, シーン色, 透過率), 水の色, 濁り)
出力   = mix(下, 反射+太陽のきらめき, フレネル) を 泡で上書き

src/water.js — capture() / src/game.js — update()

湖底は「水の色に溶けていく」ので境目が出ません。赤から先に吸収されるため、 深くなるにつれて自然に青緑へ沈みます。魚や仕掛けも同じ減衰を受けるので、 深い所の魚はぼんやり消えていきます。
反射と太陽のきらめきはフレネルの重みだけ載るようになったので、 真下を覗き込んだときに水面のテカリで底が見えなくなることもありません。
水際にまたがって置かれた岩や、桟橋の脚、ウキのように一部だけ水面から出ている物も、 水中部分がちゃんと水越しに見えます(取り込みから外すと水中部分だけ消えてしまう)。
コストはシーン 1 枚ぶんの追加描画(0.55 倍解像度・空と雨と森の木は除外)。 屈折は入れていません。

地形と水深

湖はシードから毎回生成されます(新しく始めるたびに別の湖/ポーズ画面でいつでも引き直し可)。 同じシードなら必ず同じ湖になり、描画品質を変えても地形は変わりません。 生成は src/lakefield.js にあり、採用前に検証を通すことで「破綻」と「詰み」を防いでいます。

// 湖底の高さ(over = 岸線からの距離、k = 1 − r/岸線半径)
岸線半径 = clamp(130 + 34×うねり(方角) + 12×入り組み(方角), 88, 172)
  // 角度だけの関数なので、どんなに入り組んでも湖は常に1つ(島も内陸湖もできない)

// 斜面は方角ごとに変わる:指数が小さいほど急、大きいほど広い浅棚
指数 exp     = 0.46 〜 1.30(方角ノイズ)
ブレイク位置 = k 0.16 〜 0.42、段差 0 〜 5.8 m
水中: 深さ = min(26 × k^exp × smoothstep(0, 0.075, k)
            + 段差 × smoothstep(ブレイク±0.04, k), 32)
      h = −深さ + 湖底ノイズ×1.35k − 淵 + 藻場
陸上: h = over × 0.15 + 丘ノイズ×7.5 + 山リッジ×105×smoothstep(70, 320, over)

// 底質(砂地 / 岩場 / 泥底):深いほど泥、急斜面ほど岩、浅場はノイズで混じる
// 岩場には転石(0.28〜1.28m)を撒く:v ≥ 0.64 かつ smoothstep(0.64, 0.92, v) で間引き、
// さらに疎密ノイズで patchy にする。水深 0.5〜16m・勾配 1.45 以下・水面より下
v = clamp01(0.52 + ノイズ×0.62 + clamp01(勾配−0.3)×0.7 − clamp01((水深−4)/18)×0.66)
    → v < 0.34 泥底 / < 0.68 砂地 / それ以上 岩場

// 水中ストラクチャー:沈み岩(岩3つのシモリ)と立ち枯れ(幹+枝3〜4本)
本数 = 16〜23(うち最初の 4 本は桟橋の先から狙える扇の中)
水深 1.2〜20 m / 高さ 岩 0.7〜2.6m・枯れ木 1.8〜5.2m(必ず水面下)
互いに 11 m 以上離す・桟橋の真下 4.5 m は避ける

// 桟橋:方角はシードでランダム。岸線を二分探索して位置を決める
桟橋 = 岸線 +3.5m(陸側) 〜 −26m(沖側)
床の高さ = clamp(陸側の最高地面 + 0.5, 1.15, 2.6)     // 地形に埋まらないよう追従

// 深い淵:目標水深になるよう掘る量を決める(レジェンドの層を保証)
淵の深さ = clamp(24 − その場の水深, 0, 16)、岸際は smoothstep(0, 0.12, k) でフェード
// 藻場:目標水深まで底上げ。ただし必ず 1.2m の水を残す(陸ができない)
底上げ = min(clamp(その場の水深 − 2.5, 0, 12) × ガウス, その場の水深 − 1.2)

src/lakefield.js — makeLake()

採用条件(すべて満たすまでシードを引き直す)

検証項目条件防いでいる事故
湖内の陸湖の内側 12〜94% に h > −0.2 m の点がない湖の真ん中に島や砂州ができる
最浅部湖内のどこでも水深 ≥ 0.25 m水面が地面とチラつく
桟橋の水深先端が 6〜19 m浅すぎて何も来ない/深すぎて小物が来ない
桟橋のクリアランス全長・全幅で床が地面より 0.25 m 以上上桟橋が斜面に埋まる・地面を突き抜ける
付け根の勾配勾配 ≤ 1.4崖で桟橋から岸に上がれない
水深帯の網羅一番よく飛ぶ竿(84m)で届く範囲に 1〜4 m / 5〜10 m / 12〜18 m / 20 m 以上 がすべて存在特定の層の魚が永久に釣れない
全魚種の到達性18 魚種すべてについて、生息層(fit=1 の窓)に入る水深が、一番よく飛ぶ竿で届く範囲にある図鑑がコンプできない
最初の竿の上限最初の竿(10m)の範囲で深淵(20m+)に当たるサンプルが 6% 未満(ごく稀)竹竿だけで深淵まで安定して届いてしまう
淵水深 20〜34 m・岸から 18 m 以上内側・桟橋の先端から 24〜68 m(最初の竿+14m 〜 最遠の竿−16m)レジェンドが狙えない/最初の竿で届いてしまう/淵が土手を削る
藻場水深 1.4〜4.6 m・岸から 6 m 以上内側・淵から 45 m 以上離れる浅場が成立しない/2つの地形が潰し合う
ストラクチャー水中に 7 個以上、うち最初の竿で桟橋から届く所に 2 個以上スタート地点の近くに狙う目標がない
「届く範囲」= 桟橋の先端と岸線 64 地点から、キャスト距離 4.5〜84 m・±43° の扇形(竿の cast 一覧の最大値・最小値を自動で使う)。 淵は最初の竿では届かず、良い竿なら届く帯に置くことで「竿を買い替える理由」を作っています。 桟橋の先端は元々「そこだけで深場が狙える」構造物(水深 6〜19m)なので、竹竿でも桟橋からわずかに投げるだけで 中層〜深場には届きますが、深淵(20m+)まで届いてしまう湖は 2% 程度に抑えています(詳しくは §6 参照)。
ランダムなシード 300 個で 82% がそのまま合格(落ちるのは主に「淵と藻場が近すぎる」)。 不合格ならシードを +1 して再生成するためすべて最終的に合格します(平均 1.2 回の引き直し・最大 40 回まで)。

湖ジェネレータ

シードを入れると、実際のゲームと同じコード(src/lakefield.js)で湖を生成し、検証結果と水深マップを表示します。

🗺️ シードから湖をプレビュー

桟橋 深い淵 藻場 沈み岩 立ち枯れ 白い輪 = 桟橋からキャストが届く範囲

桟橋の長さ

方角ごとに水深がばらつくので、方角は「先端が 7〜17 m になる向き」から選び、 さらに長さを 18〜32 m の中から選んで先端の水深を 11 m 前後に寄せます(平均 21 m)。桟橋から浅場も中深場も狙える状態を保つためです。

ドロップオフ

岸線からすぐ落ち込む急深型です(岸から 1 m で約 2 m、8 m で約 8 m)。浅場の魚は岸すぐ、深場の魚は遠投で狙い分けます。キャスト中の予測リングと水深表示がそのまま指標になります。

深い淵(ホール)— 平均 2.5 か所

1 つ目は桟橋から見て左右どちらかの前方、岸から 26〜40 m 内側に半径 30 m。 必ず水深 24 m 前後になるよう掘る量が決まるので、どの湖でもレジェンドの層(湖の主 14〜28 m / イトウ 10〜26 m)が成立します。
追加で 1〜2 か所、半径 16〜26 m・水深 18〜26 m のものを湖のどこかに置きます(1 つだけだと「なんでもない水域」が広すぎるため)。

浅い平場(藻場)— 平均 3.5 か所

1 つ目は淵の反対側、岸から 14〜30 m 内側。必ず水深 2.5 m 前後。 weed タグ(ライギョ・ブルーギル・バス)を中層〜底層で狙う場所(水深 2.5 m なので底層でも 2.2 m)。葦が密生します。
追加で 2〜3 か所、半径 14〜26 m・水深 1.8〜3.7 m。元の水深が 3〜14 m の所にだけ作ります(深すぎる所を盛っても浅場にならないため)。
フィーチャ同士は 44 m 以上、桟橋の先からは 45 m 以上離します(桟橋の先の水深が変わらないように)。

棚とかけあがり(階段)

湖底の深さを「なめらかな斜面 30〜40%」+「2〜4 段の落ち込み」に分けて作ります。 段のあいだは平らな棚になり、段の所だけ急なかけあがりになります(冪関数だけだとどこも同じ傾きで、平場ができませんでした)。
実測(水深 1.5〜8m):傾き 0.12 以下が 23〜30%(=棚)、0.30 以上が 46〜52%(=かけあがり)。 断面の例:0→1.3m の浜 → 1.2m の棚が 8m → 6m で 5.8m 落ちる → 7.5m の棚が 12m 以上。
段の数は方角ごとに 2〜4 段ですが、3・4 段目は 0 から連続的に立ち上げるようにしています(整数で切り替えると方角の境目で深さが飛び、湖底が扇状に割れて見えました)。

水中ストラクチャー

沈み岩と立ち枯れが 16〜23 個。水面上には出ないので キャストの邪魔はしませんが、肉食魚・藻場の魚 ×1.45/底物 ×1.3 の集魚効果があります。水中カメラ(V)で見えます。

底質

砂地・岩場・泥底の 3 種。底を釣るときだけ強く効きます(中層 0.35 乗・表層 0.1 乗)。 岩場はバス・ライギョ・鱒、泥底はコイ科と底物、砂地は底物と深場の魚に向きます。
湖底には種類ごとのタイルテクスチャ(砂紋・転石・泥)を貼っており、水中カメラ(V)で見た目でも区別できます。 加えて岩場には転石が 600〜2400 個(描画品質で変わる)散らばります。 砂地との境は底質の値でまばらにし、斜面ほど深く埋めて浮いて見えないようにしています。

歩ける範囲

地形高さ −0.55 m まで(水深 0.55 m まで)入れます。勾配 1.5 超の急斜面は登れません。中心から 460 m が世界の端。

湖の測量図(M)

440m 四方(WATER_REGION)を 72×72 の格子(1 マス 6.1m)で持ち、 歩いた所と仕掛けを落とした所(どちらも半径 30m)だけを開けていきます。 開いたマスはその場の高さから色を決めて描くので、水深・汀線・陸がそのまま地図になります。

// 保存:72×72 ビット = 648 バイト → base64(864 文字)でセーブに入れる
state.map = { seed, cells }        // seed が変わったら白紙に戻す(別の湖なので)

// 測量率の分母は「湖 + 岸から 24m」のマスだけ
測量率 = 開いたマス(湖の内側) / 湖のマス総数

src/game.js — _initMap() / _revealMap() / mapProgress()、src/ui.js — openMap()

地図には、開いたマスの中にあるものだけが出ます:深い淵(紫の輪)・浅い平場(緑の輪)・ 沈み岩 ●/立ち枯れ ◆(橙)・桟橋(金)・いまの仕掛け(赤)・自分の位置と向き(白い矢印)。
1 投で開くのは 30m の円(約 76 マス)。実測の伸び方は 5 投 20%/10 投 35%/20 投 52%/50 投 70%/180 投 86%、 250 投+岸を一周で 100%(ランダムに投げた場合。狙って投げれば もっと早く埋まります)。

地形図鑑(判定の条件)

着水した瞬間にその地点を調べて、当てはまる地形が図鑑(Q → 地形)に登録されます。 1 回のキャストで水深帯 + 底質 + 地形の特徴がまとめて埋まることもあります。 その場所で釣れた魚も、その地形に記録されます。

// 判定に使う値(terrainCtxAt)
depth    = その地点の水深
grad     = 沖へ向かう傾き = (岸側 4m の水深 − 沖側 4m の水深) / 8      // + が沖へ深くなる
bed      = 底質(mud / sand / rock)
struct   = 4.5m 以内の水中ストラクチャー(rock / snag)
inHole   = 淵の中心から 半径×0.8 以内 かつ 水深 19m 以上
inFlat   = 藻場の中心から 半径×0.8 以内 かつ 水深 5.5m 以下
dockDist = 桟橋までの距離
400 地点で試した出現率:深場 61%/泥底 56%/かけあがり 29%/砂地 26%/中場 21%/浅場 15%/ 葦際 8%/岩場 15%/藻場 4%/浅棚 3%(浅場の中では 23〜30%)/淵 1.5%/沈み岩 1%/立ち枯れ 0.2%/桟橋際 0.7%。
デバッグ表示(F3 / ポーズ画面)を ON にすると、さかな図鑑・地形図鑑・仕掛けウインドウの 「ここで食いつく魚」が全部見えます(未発見のものは「未発見(デバッグ表示)」と出ます)。 進捗のカウントは実際の発見数のままです。

7成長・お金・実績

レベル曲線

次のレベルまでの累積経験値 = round(55 × Lv^1.42)

src/save.js — xpForLevel()

実績

セーブされる内容

localStorage["lakeside-fishing-save-v1"] に、所持金・経験値・レベル・総釣果・総収入・最大全長・ レジェンド数・夜釣り数・ライン切れ数・逃走数・ゲーム内時刻・装備・所持品・魚種別記録(匹数/最大全長/最大重量)・ 実績・設定を保存します。釣果確定時に即書き込み、購入や設定変更は 0.4 秒のデバウンス書き込みです。

8釣り人・ロッドのモデル

もとはプリミティブを組んだ自作モデルでしたが、顔・手・服のつくりが手組みの限界だったので、 Quaternius の CC0 アセットに差し替えました。 表示義務のないパブリックドメインですが、出所は README とタイトル画面に記載しています。

ファイル元アセット三角容量
assets/models/angler.glbUltimate Modular Men Pack の "Casual"5,7761.4 MB
assets/models/rod-bamboo.glbFishing Rod Lvl130817 KB
assets/models/rod-glass.glbFishing Rod Lvl252228 KB
assets/models/rod-carbon.glbFishing Rod Lvl391049 KB
assets/models/rod-master.glbFishing Rod Lvl489848 KB
assets/models/rod-legend.glbFishing Rod Lvl51,17463 KB
竿は Lvl1〜5 がそのままゲームの 5 種に対応します(竿を買うと見た目も変わります)。 Lvl1 はリールの無い竹竿で、上位ほどガイドとリールが付いた作りになります。 自作時代は 53 メッシュ・13,812 三角でしたが、いまは12 メッシュ・6,686 三角。 シーン全体のドローコールも 116 → 65 に減りました。

読み込み

three.js 本体に glTF のローダーは入っていないので、r185 のアドオン 3 本 (GLTFLoader / BufferGeometryUtils / SkeletonUtils・約 160KB)を vendor/addons/ に置き、importmap に three/addons/ を足しています。 読み込みは game.build() の途中で await angler.load() して待ちます (ローディング表示に「釣り人を呼んでいます」が出るのがこれ)。

ボーン名から . が消えます。glTF では UpperArm.L ですが、 three.js のローダーがノード名を正規化して UpperArmL になります (アニメーションのトラック名と衝突しないようにするため)。 元の綴りで引くと undefined になるので、両方の綴りで引けるようにしてあります。

骨組みと姿勢

キャラクターは 62 本のボーンでリグ済み、24 種のアニメーション付きです。 ただし釣りの動作は入っていないので、脚と体はアセットのアニメーション・腕は計算で分担しています。

// 毎フレームの順番
1. mixer.update()          // Idle_Neutral と Walk を moving の重みで混ぜる(脚・体幹)
2. 前傾と首の向き           // 腰を軸にした包みのグループと Head ボーン
3. 右手の位置を決める        // 右肩 + 姿勢ごとのオフセット
4. ロッドをそこに置く        // グリップの握り位置が手に来るよう、竿の向きに沿って戻す
5. 両腕を逆運動学で通す      // 肩→肘→手首の 2 リンク
6. 手首を竿の向きへ・指を握らせる
7. 竿のしなり(_applyBend)

src/angler.js — update(), _poseUpper(), _solveArm(), _aimBone()

どのボーンも +Y に伸びている

このリグは子ボーンが必ず親のローカル +Y にあります。 つまり「+Y を狙いの向きに合わせる」だけでどのボーンも向けられるので、 ボーンごとに回転軸を調べる必要がありません(_aimBone)。

ロッドは手のボーンの子にしない

手に付けるとリグの癖がそのまま竿の角度に乗り、 狙いの計算まで動きます。root に直付けして、毎フレーム「手の位置」と「設計どおりのピッチ」で置くことで、 竿の角度は姿勢ごとの設計値のまま・位置だけ手に追従します。

アニメーション対象のボーンに角度を足してはいけません。 three.js の AnimationMixer は値が前フレームと変わらなければシーングラフに書き戻しません。 そのため bone.rotation.x += 前傾 と書くと戻されずに毎フレーム積み上がり、 数十フレームで体が折れ曲がります(実際にそうなりました)。 いまは前傾をmixer が触らない包みのグループで作り、首は足さずに毎フレーム決め打ちで入れています。

竿のしなりを保つ

アセットの竿は 1 枚の固いメッシュなので、そのまま使うとしならなくなります。 ファイトの手応えはしなりで見せているので、読み込み時にボーンを自動で仕込んでいます。

// 1. モデルの上下端をしなりのボーン列に合わせて拡大・移動する(全長 6〜8m のものもある)
s = (穂先 − グリップ尻) / (モデルの上端 − 下端)
o = 穂先 − モデルの上端 × s

// 2. 高さから skinIndex / skinWeight を作る(関節 i と i+1 で線形に混ぜる)
関節 i の高さ Y[i] ≤ y < Y[i+1] のとき
  skinIndex  = (i, i+1)
  skinWeight = (1 − t, t),  t = (y − Y[i]) / (Y[i+1] − Y[i])

src/angler.js — setRod(), _weightRod()

関節の数(6)と長さは自作時代と同じなので、しなりの計算(1 章の _applyBend)はそのまま動きます。 グリップより下の頂点は根元のボーンに重み 1 で固定されるため、握りとリールは曲がりません。

差し替えで変わった数値

項目自作アセット備考
竿先が足元より前に出る量(構え)1.39 m1.28 m腕が短いぶん手前になった
最弱キャストの飛距離1.85 m1.74 m同上(竿先が起点なので竿によらず一定)
メッシュ数 / 三角数53 / 13,81212 / 6,686見た目は上がって軽くなった
シーンのドローコール11665—
狙いの計算と実際の着水はどちらも getRodTip() を起点にしているので、 竿先が動いても狙い点と着水点はずれません(変わるのは最短距離の下限だけ)。

モーションエディター

姿勢の数値はすべて src/angler.js の TUNING に集約してあり、 motion-editor.html から スライダーで調整できます。エディターはゲーム本体と同じ Angler クラスを動かしているので、 そこで見えるものがそのままゲームの動きです(別実装を並べて持つと必ずズレます)。

項目中身
pose場面ごとの竿のピッチ・右手の位置(右肩からのずれ)・前傾。竿は手の位置に置かれるので、この 2 つで構えが決まる
fightテンションで竿を立てる量・前傾する量、巻いている間の上乗せ
fpv一人称のときのピッチ(穂先を画面に残すため三人称より寝かせる)
cast振りの時間・振り終わりのピッチ・前傾の変化・追従の速さ
arm肘を張り出す向き(裏返り防止)、右手・左手が竿を握る高さ、指の曲げ
body / walk / damp前傾の効き・首の追従、歩きの再生倍率、姿勢が切り替わる速さ
保存すると localStorage に入り、ゲームを別タブで開いていればその場で反映されます (storage イベントを拾って読み直しているため)。恒久的に残すときは「コードをコピー」で TUNING に貼れる形の JS を出します。
エディターの左上には足の滑りが出ます。歩きアニメーションの歩幅から出した 「足が滑らない速さ」は 1.32 m/s(片足の前後幅 0.879m・1 周期 1.33 秒)で、 ゲームの歩きは 3.1 m/s・走りは 6.2 m/s。 いまの再生倍率 0.85〜1.35 倍では歩きで 2.35 倍・走りで 4.7 倍ぶん足が滑ります。 移動速度を落とすか再生倍率を上げるかはゲームの手触りに関わるので、エディターで見ながら決められるようにしてあります。
失ったもの:回るリール。自作のスピニングリールはローターがギア比 5.2 で回り、 スプールが 4mm 前後していましたが、アセットの竿はリールが竿と一体の 1 メッシュで、 グリップと同じマテリアルなので切り離せません。巻いてもリールは動きません。 腕の長さも 0.42m と短いため、左手はリールではなく右手のすぐ下に添える持ち方にしています。

9ソース対応表

ファイル主な内容
src/data.js魚種・ロッド・ライン・エサ・実績・レア度、重さ/売値/経験値/全長抽選の式
src/game.js状態機械、rollSpecies(抽選)、_onLandWater(アタリ時間)、_startBite(アワセ猶予)、_setHook/_updateFight(ファイト)、キャスト物理、レベル・実績処理
src/save.jsセーブ/ロード、xpForLevel
src/fish.js魚メッシュ生成、遊泳AI(回遊・接近・前アタリ・逃走・跳ね)、魚群の入れ替え、startle(キャストで散らす)
src/angler.js釣り人・ロッド(glTF の読み込みとリグ)、歩き/キャスト/ファイトの姿勢、両腕の逆運動学、竿のしなり(自動スキニング)、糸とウキの描画(8 章)
src/terrain.js湖底の高さ関数(ドロップオフ・淵・藻場)、桟橋、岸辺の装飾
src/water.js波の式(CPU/GPU 共通)、水面シェーダ、波紋・しぶき
src/sky.js時間帯ごとの空と光、天候の遷移と倍率、雨
src/ui.jsHUD・図鑑・ショップ・釣果カード、魚のシルエット描画
src/debug.jsデバッグ表示(F3)。当たり判定の可視化、効果音モニタ、状態機械とファイト内部値の表示