docs/決めたこと.md
分割 1 / 1 · 元ファイル 1〜146行付近
# 決めたこと(石とゾンビ)
**K1が決めた事は `docs/K1仕様書_20260913.md`。この紙は私(子セッション)が決めた事の記録。**
1行ずつ、決めた事と理由。後から「なぜこうなっているか」を探させないため。
## 2026-09-13(立ち上げの日)
### 絵と素材
- **絵は1枚も使わない。全部コードで描く**(K1「フリースローはそもそもイラストないやん」)。`docs/asset_prompts.md` は作らない・Codexへの絵の発注もしない
- **人は共有財産のシュールキャラを流用**(`C:\五月雨開発\シュールキャラ\shurechara.js` を `lib/shurechara.js` へ写した)。**元のファイルは1文字も直していない**——あれは共有財産で、直すなら回転寿司側にも入れる決まりだから
- **ゾンビはシュールキャラを崩した形**。崩し方は4つだけ: ①死人の肌 ②体を傾ける ③片足を引きずる ④腕を前へ出す。新しい絵は作らない
- **死人の肌と汚れた服は、シュールキャラの色表へ**実行時に後ろから足す**だけにした**。元の8色(町の人の服)もゾンビに使う——**町の人がそのまま変わり果てた**方が、全員が同じ死人の制服を着ているより気味が悪い
- ゾンビの見た目は**1体ずつバラバラに引く**。`SHURE.fromSeed` に連番を渡すと服の色が `n/256` で決まるので、**4体並べたら4体とも黄色**になった(実測して直した)
- 石・空・月・雲・校舎・地面も全部コード。画像の読み込みはゼロ
### 遊びの芯
- **倒せるのは「頭の丸へ・上から・急な角度で入った石」だけ。**判定は3つ同時: ①落ちている(vy>0) ②入った所が頭の中心より上 ③落ちる速さ >= 横の速さ×0.60(=水平から31度より急)
- **判定は「投げた角度」ではなく「当たる時に落ちている角度」で見る。**だから低い山なりでも、届く頃に急降下していれば倒せる。跳ね返らせて上から落とすのも同じ理屈で通る(K1の言う「上から落とす軌道を作れるか」はそういう意味だと読んだ)
- 体・足・頭の横に当たったら**ノックバックだけ**。右へ押し戻し(最大2.6m/s相当)+0.40秒よろける。連打しても積み上がらないよう上限を付けた
- **石は無限・同時に3個まで・投げた後0.14秒で次**。弾数を資源にしない(狙いの遊びであって節約の遊びではない)。同時3個が上限なので横なげ連打では押さえきれない
- **1体でも負ける線(投げる人から0.80m)まで来たら即負け。**体力・残機は作らない。短く何度も遊ぶ形
### 画角と寸法
- **横に7.8m入れる引きの画角**(フリースローは4.3m)。シュールキャラの頭の半径は15px=フリースローの7割。K1の「もう少し小さく」
- 9.0m(頭の半径13px)も作って撮ったが、**頭が25px径まで落ちて的が読めなくなった**ので7.8mへ戻した
- 投げる物理の数字は**フリースローそのまま**(引いた長さ→速さ 4.6〜9.2m/s・角度14〜84度・重力9.8・空気抵抗0.0022)。跳ね返りだけ石らしく0.62→0.46へ落とした
- **頭のてっぺんに金色の弧を描く。**「どこを狙うか」を文字で説明しないため。最初は薄い線にしたら夜の髪に沈んで1本も見えなかったので、暗い線を敷いてから明るい線を乗せた
### やってみて捨てた物
- **地平線をぞろぞろ歩く遠い群れ**(「まだいくらでも来る」の気配)。校舎より奥へ置くと1体も見えず、手前へ置くと近くのゾンビと取り違える。どちらでも値打ちが出ないので**消した**
- fps・メモリの計器板(dev_tools.js の物)。盤面に被るので `window.SETTINGS={memMeter:false}` で既定オフ。**🐛報告の箱は残す**(あれは財産)
### 音
- **音のファイルを持たない。**全部その場で合成する(フリースローと同じ作り)。倒した「ゴッ」・押した「ドス」・投げる息・石が地面を打つ音・負けた時の唸り
- **iOSのマナーモードでは効果音が黙る。**合成した音は着信音の仲間で、曲(`<audio>`)が鳴るまで出ない。このゲームは曲を持たないので、今は**仕様として受け入れる**。曲を入れる時に無音mp3の支えごと入れる
### 検品の道具(全部このフォルダに置いた)
- `tools/sweep.mjs` … **大根おろし検査**。距離×角度×力を全部なめて表にする。見るのは2つ: ①どの距離にも倒せる投げ方があるか ②横なげで倒せていないか
- `tools/play.mjs` … **自分で何十回も遊ぶ道具**。総当たりで狙う子を走らせて、何秒生きて何体倒せるかを数える。K1にテストをやらせないため
- `tools/photo.mjs` … 見せる写真をiPhone実寸で撮る。`npm test` は崩れの検品、こちらは見え方
- `index.html` の `window.ZG` が検品の口。遊びには出てこない
### 今の手応え(2026-09-13 実測)
- 総当たりで狙う子(=上手い人の上限)で **平均140秒・52体倒して負ける**。ゲームは必ず終わる
- 距離ごとの倒せる割合は 4.9%〜15.3%(でたらめに投げた場合)。フリースローのゴール(7〜13%)と同じくらい
- 横なげ(14〜28度)384通りで**直接倒せたのは0件**。芯は効いている
---
## 2026-09-13(その2・磨き込みとマルチ)
正本は `docs/K1仕様書_磨き込みとマルチ_20260913.md`。ここは私が決めた数字と理由。
### 置き場が変わった
- **Cloudflare Pages → Workers + Durable Objects へ引っ越した。**Pagesではクラスを持てない
- **公開URLが変わった: https://ishi-to-zombie.kaiten-sushi-online.workers.dev/**
(旧 `ishi-to-zombie.pages.dev` はもう更新していない)
- 静的ファイルは `public/` に寄せた(`[assets]` で同じWorkerから出る)。手本は回転寿司オンライン
- 🐛バグ報告の受け先は Pages Functions から Durable Object へ移した(`/api/bug`)
### 世界の作り(ここが一番大事)
- **長さを全部メートルに直した。**画面の幅が端末で違っても同じ世界になる。マルチの前提
- **遊びの中身を `lib/world.js` 1本にまとめ、サーバーと端末の両方が同じ物を回す。**二重に書かない
- サーバー = `sim:'full'`(**ここが正**: HP・死亡・撃破・スクラップ・大岩・前後交代・参加退出)
- 端末 = `sim:'motion'`(**動きだけ**回して、中身は届いた物で上書き)
- 通信が繋がらない時は端末が `sim:'full'` で回す。**中身は同じ物なのでソロも作り分けない**
- **石だけは世界に入れていない。**投げた本人の端末で飛ばし、当たり判定も本人が決めて
結果だけをサーバーへ渡す(K1「投石は操作レスポンス最優先」)。当たった見た目と音は**先に出す**
- ゾンビの位置は**飛ばさずに寄せる**(毎コマ34%ずつ)。0.75m以上ずれた時だけ飛ばす
- **1〜4を入れても、大根おろし検査の数字は1つも変わらなかった**(倒せる割合・角度・力が
refactorの前後で完全に一致)。芯は壊していない
### 数字(全部私が決めた。触って変える前提)
| 物 | 値 | 理由 |
|---|---|---|
| HP | 100 | |
| 取り付かれている間の減り | 毎秒11(1体につき) | 1体なら約9秒もつ。2体なら4.5秒 |
| 取り付く間合い | 前衛の0.44m前 | |
| 立ち位置 | 前衛1.48m / 後衛0.60m | 1.30/0.58だと体が触れて頭のHP帯が重なった(実測) |
| ノックバック | 1.7m/s(上限2.6)・0.40秒よろける | 連打しても積み上がらない |
| 2人の時の湧き | 間隔を0.68倍 | 2人で楽になりすぎないように。K1指示には無いので私の判断 |
| 大岩 | 30秒・大きさ3倍・速さ0.82倍 | 遅い分だけ重い。どこに当たっても倒せる |
| カラス | 高さ2.5〜3.6m・28秒目から・15秒ごと(最短5.5秒) | 山なりの邪魔をしない高さ |
| カラスの倒し方 | **どこに当たっても落ちる** | 地上と別の仕事にして、2人の分担を作るため |
| カラスの突き | 7ダメージ・3.2秒かけて降りて戻る | |
| スクラップ | ゾンビ+1 / カラス+3 | |
| 生存倍率 | 60秒×1.2 / 90秒×1.3 / 120秒×1.5 / 150×1.7 / 180×1.9 / 以降+0.2 | K1の仮例をそのまま段にした |
### 決めた作りの理由
- **取り付かれた時、自分の石でも外せる**(実測: その距離では360通り全部がノックバックで、
倒すことはできないが**必ず外せる**)。だからソロでも詰まない。相方がいれば救出が速い
- **取り付かれている間は自分の石が全部その1体に当たる**ので、奥のゾンビを狙えなくなる。
これがそのまま「相方に前を任せる」動機になる
- **スクラップはその時いる人全員へ同じだけ入る。**倒した人が誰かは見ていない
(K1「キルの取り合いを発生させない」)。参加前の分が入らないのは、入るのが「今いる人」だけだから
- 大岩・交代のボタンは**押せない時も大きさを変えない。**グレーアウトして赤字を重ねる(UI憲法)
- 着せ替えとお店だけDOMにした。**遊ぶ画面はcanvasのままスクロールしない**
### DoSガード(3盾)
- **盾1** Workers標準のレート制限 20回/10秒(namespace 1201。他のアプリと数えを分けた)
- **盾2** 月**2000万回**で新規接続を断る(2026-09-13 K1指示「全てのガードを月2000万回にしてよ」
「今開発段階やし本格化したら外せばええんやし」)。数えるのは変数+1、1000回ごとに保存、
判定は**新しく繋ぐ時だけ**。遊んでいる最中は一度も確かめない。今いくつかは `/api/meter`
- **盾3** `[limits] cpu_ms = 100`
- 1本の繋がりが1秒に45通を超えたら、その分は黙って捨てる
### ★誰も繋がっていない部屋を畳む
- 人が0人になったら**その場で時計(setInterval)を解く**。1コマも進めない
- そのうえで3分後に目覚ましを仕掛け、まだ0人なら部屋を畳んで目覚ましも消す
- **止まる条件を「全滅」のような遊びの中の出来事にしない**(2026-09-13にぷっちーずで起きた穴)
- 点検の窓 `/api/room?r=部屋名` は**人の気配を更新しない**(見ているこちらが部屋を延命させない)
- 実測: 全員抜けたあと `人数=0 / 時計=止まっている`
### 相手探しで踏んだ穴(実測して直した)
- 部屋が自分の**名前**ではなく**内側の番号(`state.id`)**を相手探しへ伝えていた。
相手探しは `idFromName(番号)` という**別の空っぽの部屋**へ案内していて、
2人で開いても永久に合流しなかった。部屋の名前をURLから受け取って持つようにして直した
### ショップの品(全部コード描画・見た目だけ・性能差なし)
- 特別衣装51種(かわいい寄り35 / 中性・男性寄り15 = 7:3)・帽子21種・石スキン10種
- 値段: 衣装0〜400 / 帽子0〜220 / 石0〜150。各列の1つ目は必ず0(既定品)
- 見本は https://ishi-to-zombie.kaiten-sushi-online.workers.dev/wardrobe_sheet.html
- **スクラップで強くなる物は1つも無い**(K1指示)
### 検品の道具
- `node tools/two.mjs` … **2つの端末で本当に繋がるか**を数字で確かめる。
合流・同じ部屋・スクラップの共有・前後交代・退出・空き部屋の畳み・DoSガードの残量まで見る
- `node tools/sweep.mjs` … 大根おろし検査(芯が壊れていないか)
- `node tools/play.mjs` … 総当たりで狙う子に何十回も遊ばせてテンポを見る
- `node tools/photo.mjs` … 全部の画面をiPhone実寸で撮る