This commit is contained in:
33333-33333 2026-06-28 16:59:53 +09:00
commit cefa2ace5a
27 changed files with 1275 additions and 314 deletions

58
AI_MAP.md Normal file
View file

@ -0,0 +1,58 @@
# tarinai_ AI map
Purpose: static Japanese browser simulation/game. Optimize context by reading this file only after `README.md`, then open the relevant subsystem files. For exact file expansion, use `scripts/ai_inventory.ps1` or `scripts/ai_inventory.py`.
## Architecture
- Entrypoint: `index.html` defines the app shell, canvas, dialogs, right panels, and exact script order.
- Runtime style: plain browser JS, global IIFEs, exports on `window`, no bundler/module loader.
- Version/cache: `app_manifest.json`, `js/version.js`, and generated `service-worker.js` share `39.15.47`.
- Main loop: `js/main.js` initializes assets/audio/world/UI/save, then drives update/render.
- Update order: `js/system_order.js` calls phase facades from `js/world_update_phases.js` / `js/simulation_systems.js`.
- Command path: UI/input emits command objects; `js/command_dispatcher.js` routes tool, pointer, selection, save, reset, and UI intents.
- Persistence: save files are schema/codec/storage/coordinator modules; snapshot/history modules keep world restoration and stats consistent.
## Subsystem Map
- Core/bootstrap: `js/version.js`, `js/event_bus.js`, registries, `js/data.js`, `js/ground_types.js`, `js/health.js`, `js/main.js`, `js/debug_tools.js`, `js/perf_profiler.js`.
- Rendering/assets/audio: `js/assets.js`, `js/audio.js`, `js/render.js`, `js/tarinai_render.js`, `js/item_render_runtime.js`, `js/sound_pack.js`.
- Simulation phases: `js/sim_core.js`, `js/simulation*.js`, `js/world_update*.js`, `js/system_order.js`.
- World domain: `js/world*.js`, `js/weather_system.js`; covers state, camera/view, environment, combat/effects, placement/history, tools, family/social, ants, spatial budgets.
- Creature domain: `js/tarinai*.js`; covers state, identity, needs, actions, forced behavior, social life, item effects, disease/nests, movement/update pipeline, rendering.
- Item/structure domain: `js/item*.js`, `js/items.js`, `js/structures.js`, `js/structure_lifecycle.js`; covers definitions, spawning defaults, lifecycle, dynamic behavior, rendering.
- Physics/mechanics: `js/physics*.js`, `js/mechanical_system.js`, `js/constraint_system.js`, `js/collision_footprint_system.js`.
- UI/input: `js/ui*.js`, `js/text_catalog.js`, `js/command_dispatcher.js`; covers panels, tools, selected creature, logs, charts, family tree, mouse/touch, dialogs, labels.
- Family tree: `js/family_graph.js`, `js/ui_family_*.js`; data transform, layout, paths, async rendering, validation.
- Save/history: `js/save*.js`, `js/snapshot_system.js`, `js/restore_coordinator.js`, `js/history_system.js`.
- Scripts: `scripts/generate_app_files.py`, `scripts/generate_item_icons.py`, `scripts/regression_check.py`, `scripts/ai_inventory.py`, `scripts/ai_inventory.ps1`.
## File Pattern Semantics
- `*_pipeline.js`: ordered runner/facade for update or lifecycle work.
- `*_step_*.js`: narrow pipeline step wrapper.
- `*_system.js`: domain service, phase service, or compatibility facade.
- `*_runtime.js`: stable runtime-facing facade around split modules.
- `*_registry.js`: definition registry and lookup/normalization helpers.
- `world_*`: world-owned behavior; `tarinai_*`: creature-owned behavior; `item_*`: item-owned behavior; `ui_*`: DOM/input/panel behavior.
- `assets/sprites/tarinai_*.webp`: creature visual state sprites; numeric prefix is stable asset ID/order.
- `assets/ui/tool_*`: tool palette icons; `assets/ui/favicon*`, `apple-touch-icon`, `ecology_*` are app/help icons.
- `assets/objects/*`: field object sprites for ants, zunchi, pushpin, oshibyo, genkotsu, plushie.
- `assets/sounds/voice_*`: creature voice samples; `shoot_*`, `major_damage_*`, `firecracker_*` are SFX.
## Task-Oriented Read Sets
- UI/layout bug: `index.html`, relevant `css/*.css`, `js/ui_bind.js`, `js/ui_layout_dialogs.js`, matching `js/ui_*.js`.
- Tool behavior: `js/command_dispatcher.js`, `js/world_tool_actions.js`, `js/item_registry.js`, relevant `js/item_*` or `js/tarinai_item_*`.
- Creature behavior: `js/tarinai.js`, `js/tarinai_update_pipeline.js`, relevant `js/tarinai_*`, `js/system_order.js`.
- World/environment/combat: matching `js/world_*`, `js/simulation_*`, `js/system_order.js`.
- Save/load: `js/save_schema.js`, `js/save_codec.js`, `js/save_system.js`, `js/snapshot_system.js`, `js/restore_coordinator.js`.
- Family tree: `js/world_family_social.js`, `js/family_graph.js`, `js/ui_family_*.js`.
- Static app/cache/version: `app_manifest.json`, `scripts/generate_app_files.py`, `service-worker.js`, `js/version.js`, `index.html`.
## Invariants
- Preserve `index.html` script order unless all dependent manifests/generated files are updated together.
- Keep `app_manifest.json`, `js/version.js`, `service-worker.js`, and cache query versions synchronized.
- Keep global exports for compatibility with regression checks and browser runtime.
- Prefer focused edits in the owning subsystem; split modules are intentional compatibility boundaries.
- Run `node --check js/*.js` and `python scripts/regression_check.py` for behavior changes.

91
FILES.json Normal file
View file

@ -0,0 +1,91 @@
{
"purpose": "Token-efficient machine-readable routing for tarinai_. Use scripts/ai_inventory.py for exact current file expansion.",
"version": "39.15.47",
"entrypoints": {
"app": "index.html",
"runtime": "js/main.js",
"update_order": "js/system_order.js",
"manifest": "app_manifest.json",
"service_worker": "service-worker.js"
},
"read_strategy": {
"default": ["README.md"],
"architecture": ["README.md", "AI_MAP.md"],
"exact_inventory": ["powershell -NoProfile -ExecutionPolicy Bypass -File scripts/ai_inventory.ps1 -All", "python scripts/ai_inventory.py --all"],
"machine_routing": ["FILES.json"]
},
"groups": [
{
"id": "root",
"patterns": [".gitignore", ".htaccess", "app_manifest.json", "favicon.ico", "index.html", "README.md", "AI_MAP.md", "FILES.json", "service-worker.js"],
"meaning": "Repo/app shell metadata, server/static cache config, docs, manifest, entrypoint."
},
{
"id": "css",
"patterns": ["css/*.css"],
"meaning": "Base, layout, panel, component, and mobile responsive styling."
},
{
"id": "scripts",
"patterns": ["scripts/*.py", "scripts/*.ps1"],
"meaning": "Generators, regression checks, and token-aware AI inventory tooling."
},
{
"id": "core",
"patterns": ["js/version.js", "js/event_bus.js", "js/registry_base.js", "js/disease_registry.js", "js/sound_pack.js", "js/data.js", "js/ground_types.js", "js/health.js", "js/input_mode_manager.js", "js/main.js", "js/math.js", "js/patch_helpers.js", "js/perf_profiler.js", "js/debug_tools.js"],
"meaning": "Bootstrap, shared constants/helpers, registries, config, profiling/debug."
},
{
"id": "assets_runtime",
"patterns": ["js/assets.js", "js/audio.js", "js/render.js", "js/tarinai_render.js", "js/item_render_runtime.js"],
"meaning": "Image/audio loading and canvas rendering."
},
{
"id": "simulation",
"patterns": ["js/sim_core.js", "js/simulation*.js", "js/system_order.js"],
"meaning": "Simulation primitives, concrete systems, phase ordering."
},
{
"id": "world",
"patterns": ["js/world*.js", "js/weather_system.js"],
"meaning": "World state/view/update/tools/environment/combat/family/social/ants."
},
{
"id": "physics",
"patterns": ["js/physics*.js", "js/mechanical_system.js", "js/constraint_system.js", "js/collision_footprint_system.js"],
"meaning": "Geometry, collisions, constraints, mechanical item behavior."
},
{
"id": "items",
"patterns": ["js/item*.js", "js/items.js", "js/structures.js", "js/structure_lifecycle.js"],
"meaning": "Item/structure registry, lifecycle, dynamic behavior, rendering."
},
{
"id": "tarinai",
"patterns": ["js/tarinai*.js", "js/ants.js"],
"meaning": "Creature state/actions/needs/social life/item effects/disease/rendering plus ant actor model."
},
{
"id": "save",
"patterns": ["js/save*.js", "js/snapshot_system.js", "js/restore_coordinator.js", "js/history_system.js"],
"meaning": "Snapshot schema/codec/storage/restore/history."
},
{
"id": "ui",
"patterns": ["js/ui*.js", "js/text_catalog.js", "js/command_dispatcher.js", "js/family_graph.js"],
"meaning": "DOM UI, input, tools, selected panel, logs, charts, family tree, text labels, command routing."
},
{
"id": "assets",
"patterns": ["assets/sprites/tarinai_*.webp", "assets/ui/tool_*", "assets/ui/favicon*", "assets/ui/apple-touch-icon.png", "assets/ui/ecology_*.webp", "assets/objects/*", "assets/sounds/*"],
"meaning": "Creature sprites, UI/tool icons, field object images, voice/SFX samples."
}
],
"conventions": {
"*_pipeline.js": "ordered runner/facade",
"*_step_*.js": "pipeline stage wrapper",
"*_system.js": "domain service or compatibility facade",
"*_runtime.js": "stable runtime-facing facade",
"*_registry.js": "definition lookup/normalization"
}
}

View file

@ -1,18 +1,82 @@
# tarinai v39.15.39
# tarinai_
## 変更点
AI-first entrypoint for a static browser simulation/game. Keep this file small; read deeper docs only when needed.
- PC表示を再度ロールバックし、PC側の変更はクイックスクロールの「本体」「道具」ボタン非表示に限定。道具UI本体は維持。
- PC上部ボタンを右詰め・やや大きめに調整し、ゲームタイトルがボタンに押されて省略されないように修正。
- スマホ用のスクロールスナップ系指定を無効化し、通常スクロールを妨げないように修正。
- スマホで「家系図」へ移動したとき、更新OFFでも手動ジャンプ扱いで現在の家系図を描画してからスクロールするように修正。
- スマホの自動スクロールボタンは、初期折り畳み部分を開いてから目的地へ移動。ユーザーが手動で閉じた部分は開かない挙動を維持。
- たりない/道具系ツールチップの最大幅・折り返し・画面端補正を維持。
- 「333の部屋に移動」のリンク先は `https://2012r2.nishi.boats/~333/`
- 「観察」は「たりないデータ」の表記を維持。
## Read Strategy
## 確認
- Start here for architecture, script order constraints, and where to look next.
- For subsystem work, read `AI_MAP.md` plus only the matching files.
- For exact file inventory, run `powershell -NoProfile -ExecutionPolicy Bypass -File scripts/ai_inventory.ps1 -All` or narrow it: `powershell -NoProfile -ExecutionPolicy Bypass -File scripts/ai_inventory.ps1 ui`. Python equivalent: `python scripts/ai_inventory.py --all`.
- For machine-readable routing, read `FILES.json`.
- Avoid loading all assets into context; asset filenames are mostly self-describing and grouped by glob.
- `node --check js/*.js` passed
- `python3 scripts/regression_check.py` passed
- version synchronized: `39.15.39`
## Runtime Shape
- No bundler; `index.html` loads ordered global-IIFE scripts with `?v=39.15.47`.
- Mutable runtime lives on `window`; primary entities are `World`, `Tarinai`, items, structures, ants, family graph, weather, and save snapshots.
- Update loop: `main.js` creates the world/UI loop; `World.update` delegates to `TarinaiSystemOrder.update`; `system_order.js` runs named phases.
- UI loop: pointer/tool/UI intents go through `command_dispatcher.js`; canvas drawing is in render modules; side panels are `ui_*.js`.
- Generated/static app shell: `app_manifest.json`, `service-worker.js`, and `js/version.js` must stay version-aligned.
## File Routing
- `index.html`: DOM shell, CSS/JS load order, panels/dialogs/canvas.
- `css/*.css`: base/layout/panel/components/mobile styling.
- `js/world*.js`: world state, view, update phases, tools, placement, combat, environment, family/social, ants.
- `js/tarinai*.js`: creature state, needs, actions, behavior, social life, item effects, update pipeline, rendering.
- `js/item*.js`, `js/structures*.js`: item registry/runtime/lifecycle/dynamic behavior/rendering plus structures.
- `js/simulation*.js`, `js/system_order.js`, `js/sim_core.js`: concrete update systems and phase ordering.
- `js/ui*.js`, `js/text_catalog.js`, `js/command_dispatcher.js`: DOM UI, input, selected panel, logs, charts, family tree, tools, labels.
- `js/save*.js`, `js/snapshot_system.js`, `js/restore_coordinator.js`, `js/history_system.js`: persistence and history.
- `js/physics*.js`, `js/mechanical_system.js`, `js/constraint_system.js`, `js/collision_footprint_system.js`: geometry, mechanics, constraints, collisions.
- `assets/sprites/tarinai_*.webp`: creature state sprites.
- `assets/ui/tool_*`: tool palette icons; other `assets/ui/*` are app/help icons.
- `assets/objects/*`: field object images.
- `assets/sounds/*`: voice and SFX samples.
- `scripts/*`: generators, regression checks, and AI inventory.
## Conventions
- `*_pipeline` = ordered runner/facade.
- `*_step_*` = pipeline stage wrapper.
- `*_system` = domain service or compatibility facade.
- `*_runtime` = stable runtime-facing facade.
- `*_registry` = definition lookup/normalization.
- Many split files preserve regression contracts; do not collapse them casually.
## Checks
- Syntax: `node --check js/*.js`
- Regression: `python scripts/regression_check.py`
- Inventory sanity: `powershell -NoProfile -ExecutionPolicy Bypass -File scripts/ai_inventory.ps1 -All`
## v39.15.47 UI media isolation / scroll repair
- PC / smartphone UI override rules are separated into `@media (min-width: 981px)` and `@media (max-width: 980px)`.
- PC family tree card is restored below the main game layout, not inside the right menu.
- Smartphone manual scroll no longer uses internal vertical scroll traps for event log / family tree content.
- Auto-scroll does not open collapsed cards.
## v39.15.47 event/family/oshibyo UI fixes
- Event UI hides direct player item placement records while keeping those records in the internal log.
- Collapsed Event UI no longer leaves a large blank area from the log-card min-height.
- Family tree nodes show inherited stats in a 2x2 grid and move the tarinai portrait to the upper-right.
- Oshibyo lodged position is anchored to the left side of the tarinai sprite.
## v39.15.47 family / sprinkler polish
- Family tree generation row spacing is tightened.
- Family tree personality tags also use a 2x2 grid (up to 4 visible tags).
- Sprinkler effective cleaning radius is increased to 3x the previous value and still filters targets by actual distance.
- Sprinkler rendering is redesigned to a more realistic yard-sprinkler look and now emits water-like spray effects when active.
## v39.15.47 owned object panic / shoot effect
- Tarinai panic when their own plushie or simple grass bed is destroyed.
- The selected Tarinai data behavior text explicitly shows the owned-object panic reason.
- Shooting impact effects are larger and remain visible for slightly longer.

View file

@ -1,43 +0,0 @@
# センサー系アイデア
実験観察ゲームとして、直接介入ではなく「測る」ための道具案。
## 低コストで入れやすいもの
| センサー | 役割 | 記録する値 | 観察上の使い道 |
|---|---|---|---|
| ストレスセンサー | 範囲内の緊張度を測る | 平均ストレス / 最大ストレス / 急上昇回数 | 地形・混雑・病気・王者個体の影響を見る |
| 通過カウンター | 置いた地点を通った回数を数える | 通過数 / 個体別通過数 | 道・縄張り・回避行動の発見 |
| 滞在センサー | 範囲内にいた時間を測る | 滞在秒数 / 個体ID / 世代 | 好まれる場所・避けられる場所の比較 |
| けんかセンサー | 範囲内の争いを記録 | 勝者 / 敗者 / 勝率変化 | 王者候補・危険地帯の検出 |
| 汚染センサー | ずんち・病気・汚れを見る | 汚染量 / 増加量 / 感染者数 | スプリンクラーや隔離の効果検証 |
| 出生センサー | 近くで生まれた個体を記録 | 親 / 子 / 遺伝値 | 繁殖条件・血統観察 |
## 中コストのセンサー
| センサー | 仕様 |
|---|---|
| 個体追跡ビーコン | 特定個体に付ける。移動経路、接触相手、食事、喧嘩、睡眠を時系列で記録する。 |
| 関係センサー | 範囲内で友好・敵対が増減した時だけログを残す。群れの分裂や王者の圧力を見やすくする。 |
| 遺伝スキャナー | 選択個体の寿命・サイズ・攻撃・速度の遺伝倍率を読み取る。家系図の補助UIにも使える。 |
| 行動理由センサー | 近くの個体が「なぜその行動を選んだか」を短く記録する。空腹、恐怖、王者回避、繁殖欲求など。 |
| 死因センサー | 範囲内の死亡時に、直前の状態・接触相手・地面・病気・道具をまとめる。事故検証用。 |
## 高コストだが強いもの
| センサー | 仕様 |
|---|---|
| ヒートマップ装置 | ストレス、滞在、喧嘩、死亡、汚染、出生を色で重ねて表示する。 |
| A/B実験レコーダー | 2つの区画に別条件を設定し、生存数・出生数・平均寿命・王者発生率を比較する。 |
| 血統観察端末 | 家系ごとの平均寿命、平均サイズ、勝率、移動速度、病気耐性をランキング表示する。 |
| 社会構造センサー | 群れの中心個体、孤立個体、王者、敵対ネットワークをグラフ化する。 |
## 特に相性が良い実装順
1. 通過カウンター
2. ストレスセンサー
3. けんかセンサー
4. 遺伝スキャナー
5. ヒートマップ装置
最初は「画面内に結果を出す道具」ではなく、ログとグラフに測定値を流すだけでも十分に実験感が出る。

View file

@ -1,5 +1,5 @@
{
"version": "39.15.39",
"version": "39.15.47",
"css": [
"css/base.css",
"css/layout.css",

View file

@ -801,3 +801,9 @@
max-width: min(280px, calc(100vw - 24px));
}
}
/* v39.15.47: collapsed event card must shrink to its header.
layout.css gives .log-card a min-height, which left a large blank panel when folded. */
.collapsible-card.collapsed { min-height: 0 !important; }
.log-card.collapsed { min-height: 0 !important; height: auto !important; flex: 0 0 auto !important; flex-basis: auto !important; }
.log-card.collapsed .panel-card-body { display: none !important; }

View file

@ -59,7 +59,7 @@
.sound-panel input:disabled + span { opacity: .55; }
.sound-master { font-weight: 700; }
.sound-panel-divider { height: 1px; background: rgba(84,68,48,.12); margin: 2px 0; }
@media (max-width: 760px) { .sound-panel { right: auto; left: 0; } }
@media (max-width: 980px) { .sound-panel { right: auto; left: 0; } }
/* Collapsed card sizing is shared by the base panel rules; no event-only shrink override. */
@ -363,7 +363,7 @@ body.touch-mobile-ui .tool-tip-popover {
}
/* v39.15.39: desktop rollback guard. Keep the normal PC layout; only remove duplicated quick-scroll buttons. */
/* v39.15.47: desktop rollback guard. Keep the normal PC layout; only remove duplicated quick-scroll buttons. */
@media (min-width: 981px) {
.topbar {
display: flex;
@ -410,19 +410,281 @@ body.touch-mobile-ui .tool-tip-popover {
}
}
/* v39.15.39: mobile scroll must remain manual; disable any CSS scroll snapping. */
/* v39.15.47: PC / smartphone UI isolation. All override UI placement is scoped by width. */
@media (min-width: 981px) {
html,
body {
overflow-x: hidden;
overflow-y: auto;
}
#app {
width: min(1480px, 100vw);
padding: 18px;
overflow: visible;
}
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 14px 18px;
}
.top-left {
flex: 1 0 auto;
min-width: max-content;
max-width: 100%;
}
.brand,
.brand h1,
h1 {
flex: 0 0 auto;
min-width: max-content;
max-width: none !important;
overflow: visible !important;
text-overflow: clip !important;
}
.top-actions {
flex: 1 1 560px;
margin-left: auto;
justify-content: flex-end;
align-items: center;
gap: 10px;
}
.top-actions .btn,
.top-actions .sound-menu-btn,
.room-link {
min-height: 42px;
padding: 10px 16px;
font-size: 13px;
line-height: 1.15;
}
.layout {
display: grid;
grid-template-columns: minmax(680px, 1fr) minmax(320px, 390px);
align-items: start;
}
.panel {
position: sticky;
top: 12px;
align-self: start;
max-height: calc(100vh - 24px);
overflow-y: auto;
overflow-x: hidden;
}
.panel-quick-tabs {
position: sticky;
top: 0;
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.panel-quick-tabs [data-panel-tab="game"],
.panel-quick-tabs [data-panel-tab="tools"] {
display: none !important;
}
.lineage-main-card {
display: block;
visibility: visible;
position: relative;
z-index: 1;
width: auto;
margin: 16px 0 0;
overflow: hidden;
contain: layout paint;
}
.lineage-main-content {
height: min(78vh, 980px);
min-height: 520px;
max-height: calc(100vh - 120px);
overflow: auto;
overscroll-behavior: auto;
}
}
@media (max-width: 980px) {
html, body, #app, .layout, .stage-wrap, .panel, .card, .lineage-main-card {
scroll-snap-type: none !important;
scroll-snap-align: none !important;
scroll-snap-stop: normal !important;
html,
body {
width: 100%;
max-width: 100%;
min-height: 100%;
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior-y: auto;
}
#app {
width: 100%;
max-width: 100%;
padding: 10px max(10px, env(safe-area-inset-right, 0px)) calc(env(safe-area-inset-bottom, 0px) + 104px) max(10px, env(safe-area-inset-left, 0px));
overflow-x: clip;
overflow-y: visible;
}
.topbar,
.top-left,
.brand,
.layout,
.stage-wrap,
.panel,
.card,
.lineage-main-card {
min-width: 0;
max-width: 100%;
box-sizing: border-box;
}
.topbar {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 8px;
}
.top-left {
display: flex;
align-items: center;
gap: 8px;
}
.brand h1,
h1 {
max-width: 100%;
overflow: visible;
text-overflow: clip;
white-space: nowrap;
font-size: clamp(20px, 7vw, 30px);
}
.top-actions {
display: flex;
flex-wrap: nowrap;
justify-content: flex-start;
gap: 5px;
width: 100%;
overflow-x: auto;
overflow-y: hidden;
padding: 1px 0 5px;
-webkit-overflow-scrolling: touch;
}
.top-actions > .btn,
.top-actions > .sound-menu,
.top-actions .sound-menu-btn {
flex: 0 0 auto;
white-space: nowrap;
}
.top-actions .btn,
.top-actions .sound-menu-btn {
min-height: 34px;
padding: 7px 9px;
font-size: 11px;
}
.layout {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 10px;
position: relative;
z-index: 2;
overflow: visible;
}
.stage-wrap {
position: relative;
width: 100%;
overflow: hidden;
}
.panel {
position: relative !important;
top: auto !important;
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
max-height: none !important;
height: auto !important;
overflow: visible !important;
isolation: auto;
}
body.touch-mobile-ui .panel-quick-tabs,
.panel-quick-tabs {
position: fixed;
left: max(8px, env(safe-area-inset-left, 0px));
right: max(8px, env(safe-area-inset-right, 0px));
bottom: calc(env(safe-area-inset-bottom, 0px) + 8px);
top: auto;
z-index: 12002;
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 4px;
width: auto;
max-width: none;
margin: 0;
padding: 6px;
border: 1px solid rgba(102, 169, 215, 0.34);
border-radius: 16px;
background: rgba(244, 250, 247, 0.95);
box-shadow: 0 12px 28px rgba(42, 54, 64, 0.18);
}
.panel-quick-tabs button {
min-height: 34px;
padding: 5px 2px;
font-size: 10px;
}
body.touch-mobile-ui .mobile-mode-controls:not([hidden]) {
display: flex !important;
position: absolute;
left: 50%;
right: auto;
bottom: 10px;
transform: translateX(-50%);
z-index: 24;
max-width: calc(100% - 18px);
}
body.touch-mobile-ui.mobile-mode-auto #gameCanvas {
touch-action: pan-y pinch-zoom !important;
}
body.touch-mobile-ui.mobile-mode-camera #gameCanvas,
body.touch-mobile-ui.mobile-mode-tool #gameCanvas {
touch-action: none !important;
}
.log {
max-height: none !important;
height: auto !important;
overflow: visible !important;
overscroll-behavior-y: auto !important;
}
.lineage-main-card {
display: block !important;
visibility: visible !important;
margin-bottom: calc(env(safe-area-inset-bottom, 0px) + 132px);
position: relative;
z-index: 1;
width: 100%;
margin: 10px 0 calc(env(safe-area-inset-bottom, 0px) + 132px);
overflow: visible !important;
contain: none !important;
clear: both;
}
.lineage-main-content {
display: block !important;
height: auto !important;
min-height: 360px;
max-height: none !important;
overflow-x: hidden !important;
overflow-y: visible !important;
overscroll-behavior-y: auto !important;
-webkit-overflow-scrolling: auto;
touch-action: pan-y pinch-zoom;
}
.lineage-family-tree {
max-width: 100%;
overflow-x: auto !important;
overflow-y: visible !important;
overscroll-behavior-x: contain;
overscroll-behavior-y: auto;
-webkit-overflow-scrolling: touch;
touch-action: pan-x pan-y pinch-zoom;
}
.lineage-family-tree .lineage-tree-panel {
max-width: none;
}
.selected-card,
.tool-card,
.colony-card,
.log-card,
.lineage-main-card,
.stage-wrap {
scroll-margin-top: 0 !important;
scroll-margin-bottom: calc(env(safe-area-inset-bottom, 0px) + 104px);
}
}

View file

@ -121,19 +121,39 @@ body::before {
}
.family-genetic-stats {
display: block;
display: grid !important;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1px 5px;
margin-top: 2px;
font-size: 9px;
line-height: 1.16;
line-height: 1.12;
color: rgba(88, 70, 48, 0.86);
letter-spacing: -0.02em;
white-space: normal;
max-width: 104px;
max-width: 82px;
}
.family-genetic-stats b {
.family-genetic-stats span {
display: inline-flex;
gap: 1px;
min-width: 0;
white-space: nowrap;
}
.family-genetic-stats b,
.family-fight-stat b {
font-weight: 800;
color: rgba(120, 86, 30, 0.92);
}
.family-fight-stat {
display: block;
margin-top: 1px;
max-width: 82px;
font-size: 9px;
line-height: 1.12;
color: rgba(88, 70, 48, 0.82);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.lineage-family-tree .tree-node.champion {
border-color: rgba(202, 146, 33, 0.58);
box-shadow: inset 0 0 0 1px rgba(247, 200, 64, 0.30), 0 3px 8px rgba(120, 78, 12, 0.09);
@ -173,18 +193,19 @@ body::before {
color: rgba(126, 80, 64, 0.92);
}
.family-personality-tags {
display: flex !important;
display: grid !important;
grid-template-columns: repeat(2, minmax(0, 1fr));
overflow: visible !important;
text-overflow: clip !important;
flex-wrap: wrap;
gap: 2px;
gap: 2px 4px;
margin-top: 2px;
white-space: normal !important;
overflow: visible;
max-height: none;
max-width: 84px;
}
.family-personality-tags em {
display: inline-block;
display: block;
min-width: 0;
max-width: none;
padding: 1px 4px;
border-radius: 999px;
@ -194,8 +215,8 @@ body::before {
font-size: 7.5px;
font-style: normal;
line-height: 1.25;
overflow: visible;
text-overflow: clip;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.family-copy strong { font-size: 11px; }
@ -407,10 +428,11 @@ body::before {
transform: none;
}
.lineage-family-tree .tree-node .family-portrait {
right: 0;
bottom: -6px;
width: 58px;
height: 58px;
right: 4px;
top: 4px;
bottom: auto;
width: 48px;
height: 48px;
}
.lineage-family-tree .tree-generation-label {
position: absolute;
@ -450,3 +472,11 @@ body::before {
.tool[data-tip] { position: relative; }
.lineage-family-tree .tree-node .family-copy {
max-width: calc(100% - 46px);
}
.lineage-family-tree .tree-node .family-copy strong,
.lineage-family-tree .tree-node .family-copy > span:not(.family-genetic-stats):not(.family-fight-stat):not(.family-personality-tags) {
max-width: 100%;
}

View file

@ -7,11 +7,11 @@
<link rel="icon" type="image/png" href="assets/ui/favicon.png?v=16.07.17" />
<link rel="apple-touch-icon" href="assets/ui/apple-touch-icon.png?v=16.07.17" />
<link rel="shortcut icon" href="favicon.ico?v=16.07.17" />
<link rel="stylesheet" href="css/base.css?v=39.15.39" />
<link rel="stylesheet" href="css/layout.css?v=39.15.39" />
<link rel="stylesheet" href="css/panel.css?v=39.15.39" />
<link rel="stylesheet" href="css/components.css?v=39.15.39" />
<link rel="stylesheet" href="css/mobile.css?v=39.15.39" />
<link rel="stylesheet" href="css/base.css?v=39.15.47" />
<link rel="stylesheet" href="css/layout.css?v=39.15.47" />
<link rel="stylesheet" href="css/panel.css?v=39.15.47" />
<link rel="stylesheet" href="css/components.css?v=39.15.47" />
<link rel="stylesheet" href="css/mobile.css?v=39.15.47" />
</head>
<body>
<div id="loadingScreen" class="loading-screen" aria-live="polite">
@ -123,6 +123,10 @@
</div>
</div>
</section>
</aside>
</main>
<section class="card lineage-main-card">
<div class="lineage-head">
@ -134,8 +138,7 @@
</div>
<div id="archiveContent" class="archive-content lineage-main-content"></div>
</section>
</aside>
</main>
<button id="backToGameBtn" class="back-to-game-btn" type="button" hidden aria-label="&#x30B2;&#x30FC;&#x30E0;&#x672C;&#x4F53;&#x3078;&#x623B;&#x308B;">&#x25B2; &#x672C;&#x4F53;</button>
@ -193,141 +196,141 @@
</div>
<div id="logPushOverlay" class="log-push-overlay" aria-live="polite" aria-atomic="false"></div>
<script src="js/version.js?v=39.15.39" defer></script>
<script src="js/event_bus.js?v=39.15.39" defer></script>
<script src="js/registry_base.js?v=39.15.39" defer></script>
<script src="js/disease_registry.js?v=39.15.39" defer></script>
<script src="js/sound_pack.js?v=39.15.39" defer></script>
<script src="js/item_registry.js?v=39.15.39" defer></script>
<script src="js/data.js?v=39.15.39" defer></script>
<script src="js/ground_types.js?v=39.15.39" defer></script>
<script src="js/input_mode_manager.js?v=39.15.39" defer></script>
<script src="js/math.js?v=39.15.39" defer></script>
<script src="js/physics_helpers.js?v=39.15.39" defer></script>
<script src="js/collision_footprint_system.js?v=39.15.39" defer></script>
<script src="js/mechanical_system.js?v=39.15.39" defer></script>
<script src="js/constraint_system.js?v=39.15.39" defer></script>
<script src="js/physics_world_system.js?v=39.15.39" defer></script>
<script src="js/assets.js?v=39.15.39" defer></script>
<script src="js/audio.js?v=39.15.39" defer></script>
<script src="js/perf_profiler.js?v=39.15.39" defer></script>
<script src="js/render.js?v=39.15.39" defer></script>
<script src="js/sim_core.js?v=39.15.39" defer></script>
<script src="js/tarinai_seed_factory.js?v=39.15.39" defer></script>
<script src="js/structures.js?v=39.15.39" defer></script>
<script src="js/items.js?v=39.15.39" defer></script>
<script src="js/item_type_initializers.js?v=39.15.39" defer></script>
<script src="js/item_lifecycle_support.js?v=39.15.39" defer></script>
<script src="js/item_lifecycle_runtime.js?v=39.15.39" defer></script>
<script src="js/item_update_policy.js?v=39.15.39" defer></script>
<script src="js/item_dynamic_tool_system.js?v=39.15.39" defer></script>
<script src="js/item_dynamic_ball_system.js?v=39.15.39" defer></script>
<script src="js/item_dynamic_duplicator_system.js?v=39.15.39" defer></script>
<script src="js/item_dynamic_pin_system.js?v=39.15.39" defer></script>
<script src="js/item_dynamic_zunchi_system.js?v=39.15.39" defer></script>
<script src="js/item_dynamic_system.js?v=39.15.39" defer></script>
<script src="js/item_lifecycle_decay_system.js?v=39.15.39" defer></script>
<script src="js/item_lifecycle_growth_system.js?v=39.15.39" defer></script>
<script src="js/item_lifecycle_step_frame.js?v=39.15.39" defer></script>
<script src="js/item_lifecycle_step_decay.js?v=39.15.39" defer></script>
<script src="js/item_lifecycle_step_dynamic.js?v=39.15.39" defer></script>
<script src="js/item_lifecycle_step_growth.js?v=39.15.39" defer></script>
<script src="js/item_lifecycle_pipeline.js?v=39.15.39" defer></script>
<script src="js/item_runtime.js?v=39.15.39" defer></script>
<script src="js/structure_lifecycle.js?v=39.15.39" defer></script>
<script src="js/item_render_runtime.js?v=39.15.39" defer></script>
<script src="js/ants.js?v=39.15.39" defer></script>
<script src="js/health.js?v=39.15.39" defer></script>
<script src="js/tarinai.js?v=39.15.39" defer></script>
<script src="js/tarinai_action_spec.js?v=39.15.39" defer></script>
<script src="js/tarinai_behavior_state.js?v=39.15.39" defer></script>
<script src="js/tarinai_identity_social.js?v=39.15.39" defer></script>
<script src="js/tarinai_action_state.js?v=39.15.39" defer></script>
<script src="js/tarinai_disease_nest.js?v=39.15.39" defer></script>
<script src="js/tarinai_item_effects.js?v=39.15.39" defer></script>
<script src="js/tarinai_needs_core.js?v=39.15.39" defer></script>
<script src="js/tarinai_behavior_text.js?v=39.15.39" defer></script>
<script src="js/tarinai_forced_behavior.js?v=39.15.39" defer></script>
<script src="js/tarinai_item_targeting.js?v=39.15.39" defer></script>
<script src="js/tarinai_consumable_behavior.js?v=39.15.39" defer></script>
<script src="js/tarinai_social_action_runtime.js?v=39.15.39" defer></script>
<script src="js/tarinai_building_behavior.js?v=39.15.39" defer></script>
<script src="js/tarinai_action_definitions.js?v=39.15.39" defer></script>
<script src="js/tarinai_needs_items.js?v=39.15.39" defer></script>
<script src="js/tarinai_forced_action_planner_system.js?v=39.15.39" defer></script>
<script src="js/tarinai_action_planner_system.js?v=39.15.39" defer></script>
<script src="js/tarinai_need_planner_system.js?v=39.15.39" defer></script>
<script src="js/tarinai_item_interaction_context.js?v=39.15.39" defer></script>
<script src="js/tarinai_food_interaction_system.js?v=39.15.39" defer></script>
<script src="js/tarinai_contact_item_system.js?v=39.15.39" defer></script>
<script src="js/tarinai_item_interaction_system.js?v=39.15.39" defer></script>
<script src="js/tarinai_sunbath_system.js?v=39.15.39" defer></script>
<script src="js/tarinai_cursor_care_system.js?v=39.15.39" defer></script>
<script src="js/tarinai_local_environment_system.js?v=39.15.39" defer></script>
<script src="js/tarinai_social_move_life.js?v=39.15.39" defer></script>
<script src="js/tarinai_update_step_frame.js?v=39.15.39" defer></script>
<script src="js/tarinai_update_step_ai.js?v=39.15.39" defer></script>
<script src="js/tarinai_update_step_environment.js?v=39.15.39" defer></script>
<script src="js/tarinai_update_step_movement.js?v=39.15.39" defer></script>
<script src="js/tarinai_update_step_health.js?v=39.15.39" defer></script>
<script src="js/tarinai_update_pipeline.js?v=39.15.39" defer></script>
<script src="js/tarinai_runtime.js?v=39.15.39" defer></script>
<script src="js/tarinai_render.js?v=39.15.39" defer></script>
<script src="js/world.js?v=39.15.39" defer></script>
<script src="js/world_view.js?v=39.15.39" defer></script>
<script src="js/family_graph.js?v=39.15.39" defer></script>
<script src="js/world_family_social.js?v=39.15.39" defer></script>
<script src="js/world_combat_effects.js?v=39.15.39" defer></script>
<script src="js/world_environment.js?v=39.15.39" defer></script>
<script src="js/world_spatial_budget.js?v=39.15.39" defer></script>
<script src="js/world_ants_system.js?v=39.15.39" defer></script>
<script src="js/weather_system.js?v=39.15.39" defer></script>
<script src="js/item_update_scheduler.js?v=39.15.39" defer></script>
<script src="js/simulation_runtime_helpers.js?v=39.15.39" defer></script>
<script src="js/tarinai_update_policy.js?v=39.15.39" defer></script>
<script src="js/simulation_environment_system.js?v=39.15.39" defer></script>
<script src="js/simulation_item_ant_system.js?v=39.15.39" defer></script>
<script src="js/simulation_effects_system.js?v=39.15.39" defer></script>
<script src="js/simulation_creature_system.js?v=39.15.39" defer></script>
<script src="js/simulation_maintenance_system.js?v=39.15.39" defer></script>
<script src="js/simulation_ambient_system.js?v=39.15.39" defer></script>
<script src="js/simulation_systems.js?v=39.15.39" defer></script>
<script src="js/world_update_phases.js?v=39.15.39" defer></script>
<script src="js/system_order.js?v=39.15.39" defer></script>
<script src="js/simulation.js?v=39.15.39" defer></script>
<script src="js/world_update.js?v=39.15.39" defer></script>
<script src="js/world_tool_actions.js?v=39.15.39" defer></script>
<script src="js/world_placement_log.js?v=39.15.39" defer></script>
<script src="js/world_event_effects.js?v=39.15.39" defer></script>
<script src="js/command_dispatcher.js?v=39.15.39" defer></script>
<script src="js/text_catalog.js?v=39.15.39" defer></script>
<script src="js/ui.js?v=39.15.39" defer></script>
<script src="js/ui_log.js?v=39.15.39" defer></script>
<script src="js/ui_helpers.js?v=39.15.39" defer></script>
<script src="js/ui_selected.js?v=39.15.39" defer></script>
<script src="js/save_schema.js?v=39.15.39" defer></script>
<script src="js/snapshot_system.js?v=39.15.39" defer></script>
<script src="js/restore_coordinator.js?v=39.15.39" defer></script>
<script src="js/history_system.js?v=39.15.39" defer></script>
<script src="js/save_codec.js?v=39.15.39" defer></script>
<script src="js/save_storage.js?v=39.15.39" defer></script>
<script src="js/save_system.js?v=39.15.39" defer></script>
<script src="js/patch_helpers.js?v=39.15.39" defer></script>
<script src="js/ui_tooltips.js?v=39.15.39" defer></script>
<script src="js/ui_layout_dialogs.js?v=39.15.39" defer></script>
<script src="js/ui_ground.js?v=39.15.39" defer></script>
<script src="js/ui_tools.js?v=39.15.39" defer></script>
<script src="js/ui_input_shared.js?v=39.15.39" defer></script>
<script src="js/ui_input_touch.js?v=39.15.39" defer></script>
<script src="js/ui_input_mouse.js?v=39.15.39" defer></script>
<script src="js/ui_bind.js?v=39.15.39" defer></script>
<script src="js/ui_charts.js?v=39.15.39" defer></script>
<script src="js/ui_family_data.js?v=39.15.39" defer></script>
<script src="js/ui_family_async.js?v=39.15.39" defer></script>
<script src="js/ui_family_layout.js?v=39.15.39" defer></script>
<script src="js/ui_family_paths.js?v=39.15.39" defer></script>
<script src="js/ui_family_render.js?v=39.15.39" defer></script>
<script src="js/main.js?v=39.15.39" defer></script>
<script src="js/debug_tools.js?v=39.15.39" defer></script>
<script src="js/version.js?v=39.15.47" defer></script>
<script src="js/event_bus.js?v=39.15.47" defer></script>
<script src="js/registry_base.js?v=39.15.47" defer></script>
<script src="js/disease_registry.js?v=39.15.47" defer></script>
<script src="js/sound_pack.js?v=39.15.47" defer></script>
<script src="js/item_registry.js?v=39.15.47" defer></script>
<script src="js/data.js?v=39.15.47" defer></script>
<script src="js/ground_types.js?v=39.15.47" defer></script>
<script src="js/input_mode_manager.js?v=39.15.47" defer></script>
<script src="js/math.js?v=39.15.47" defer></script>
<script src="js/physics_helpers.js?v=39.15.47" defer></script>
<script src="js/collision_footprint_system.js?v=39.15.47" defer></script>
<script src="js/mechanical_system.js?v=39.15.47" defer></script>
<script src="js/constraint_system.js?v=39.15.47" defer></script>
<script src="js/physics_world_system.js?v=39.15.47" defer></script>
<script src="js/assets.js?v=39.15.47" defer></script>
<script src="js/audio.js?v=39.15.47" defer></script>
<script src="js/perf_profiler.js?v=39.15.47" defer></script>
<script src="js/render.js?v=39.15.47" defer></script>
<script src="js/sim_core.js?v=39.15.47" defer></script>
<script src="js/tarinai_seed_factory.js?v=39.15.47" defer></script>
<script src="js/structures.js?v=39.15.47" defer></script>
<script src="js/items.js?v=39.15.47" defer></script>
<script src="js/item_type_initializers.js?v=39.15.47" defer></script>
<script src="js/item_lifecycle_support.js?v=39.15.47" defer></script>
<script src="js/item_lifecycle_runtime.js?v=39.15.47" defer></script>
<script src="js/item_update_policy.js?v=39.15.47" defer></script>
<script src="js/item_dynamic_tool_system.js?v=39.15.47" defer></script>
<script src="js/item_dynamic_ball_system.js?v=39.15.47" defer></script>
<script src="js/item_dynamic_duplicator_system.js?v=39.15.47" defer></script>
<script src="js/item_dynamic_pin_system.js?v=39.15.47" defer></script>
<script src="js/item_dynamic_zunchi_system.js?v=39.15.47" defer></script>
<script src="js/item_dynamic_system.js?v=39.15.47" defer></script>
<script src="js/item_lifecycle_decay_system.js?v=39.15.47" defer></script>
<script src="js/item_lifecycle_growth_system.js?v=39.15.47" defer></script>
<script src="js/item_lifecycle_step_frame.js?v=39.15.47" defer></script>
<script src="js/item_lifecycle_step_decay.js?v=39.15.47" defer></script>
<script src="js/item_lifecycle_step_dynamic.js?v=39.15.47" defer></script>
<script src="js/item_lifecycle_step_growth.js?v=39.15.47" defer></script>
<script src="js/item_lifecycle_pipeline.js?v=39.15.47" defer></script>
<script src="js/item_runtime.js?v=39.15.47" defer></script>
<script src="js/structure_lifecycle.js?v=39.15.47" defer></script>
<script src="js/item_render_runtime.js?v=39.15.47" defer></script>
<script src="js/ants.js?v=39.15.47" defer></script>
<script src="js/health.js?v=39.15.47" defer></script>
<script src="js/tarinai.js?v=39.15.47" defer></script>
<script src="js/tarinai_action_spec.js?v=39.15.47" defer></script>
<script src="js/tarinai_behavior_state.js?v=39.15.47" defer></script>
<script src="js/tarinai_identity_social.js?v=39.15.47" defer></script>
<script src="js/tarinai_action_state.js?v=39.15.47" defer></script>
<script src="js/tarinai_disease_nest.js?v=39.15.47" defer></script>
<script src="js/tarinai_item_effects.js?v=39.15.47" defer></script>
<script src="js/tarinai_needs_core.js?v=39.15.47" defer></script>
<script src="js/tarinai_behavior_text.js?v=39.15.47" defer></script>
<script src="js/tarinai_forced_behavior.js?v=39.15.47" defer></script>
<script src="js/tarinai_item_targeting.js?v=39.15.47" defer></script>
<script src="js/tarinai_consumable_behavior.js?v=39.15.47" defer></script>
<script src="js/tarinai_social_action_runtime.js?v=39.15.47" defer></script>
<script src="js/tarinai_building_behavior.js?v=39.15.47" defer></script>
<script src="js/tarinai_action_definitions.js?v=39.15.47" defer></script>
<script src="js/tarinai_needs_items.js?v=39.15.47" defer></script>
<script src="js/tarinai_forced_action_planner_system.js?v=39.15.47" defer></script>
<script src="js/tarinai_action_planner_system.js?v=39.15.47" defer></script>
<script src="js/tarinai_need_planner_system.js?v=39.15.47" defer></script>
<script src="js/tarinai_item_interaction_context.js?v=39.15.47" defer></script>
<script src="js/tarinai_food_interaction_system.js?v=39.15.47" defer></script>
<script src="js/tarinai_contact_item_system.js?v=39.15.47" defer></script>
<script src="js/tarinai_item_interaction_system.js?v=39.15.47" defer></script>
<script src="js/tarinai_sunbath_system.js?v=39.15.47" defer></script>
<script src="js/tarinai_cursor_care_system.js?v=39.15.47" defer></script>
<script src="js/tarinai_local_environment_system.js?v=39.15.47" defer></script>
<script src="js/tarinai_social_move_life.js?v=39.15.47" defer></script>
<script src="js/tarinai_update_step_frame.js?v=39.15.47" defer></script>
<script src="js/tarinai_update_step_ai.js?v=39.15.47" defer></script>
<script src="js/tarinai_update_step_environment.js?v=39.15.47" defer></script>
<script src="js/tarinai_update_step_movement.js?v=39.15.47" defer></script>
<script src="js/tarinai_update_step_health.js?v=39.15.47" defer></script>
<script src="js/tarinai_update_pipeline.js?v=39.15.47" defer></script>
<script src="js/tarinai_runtime.js?v=39.15.47" defer></script>
<script src="js/tarinai_render.js?v=39.15.47" defer></script>
<script src="js/world.js?v=39.15.47" defer></script>
<script src="js/world_view.js?v=39.15.47" defer></script>
<script src="js/family_graph.js?v=39.15.47" defer></script>
<script src="js/world_family_social.js?v=39.15.47" defer></script>
<script src="js/world_combat_effects.js?v=39.15.47" defer></script>
<script src="js/world_environment.js?v=39.15.47" defer></script>
<script src="js/world_spatial_budget.js?v=39.15.47" defer></script>
<script src="js/world_ants_system.js?v=39.15.47" defer></script>
<script src="js/weather_system.js?v=39.15.47" defer></script>
<script src="js/item_update_scheduler.js?v=39.15.47" defer></script>
<script src="js/simulation_runtime_helpers.js?v=39.15.47" defer></script>
<script src="js/tarinai_update_policy.js?v=39.15.47" defer></script>
<script src="js/simulation_environment_system.js?v=39.15.47" defer></script>
<script src="js/simulation_item_ant_system.js?v=39.15.47" defer></script>
<script src="js/simulation_effects_system.js?v=39.15.47" defer></script>
<script src="js/simulation_creature_system.js?v=39.15.47" defer></script>
<script src="js/simulation_maintenance_system.js?v=39.15.47" defer></script>
<script src="js/simulation_ambient_system.js?v=39.15.47" defer></script>
<script src="js/simulation_systems.js?v=39.15.47" defer></script>
<script src="js/world_update_phases.js?v=39.15.47" defer></script>
<script src="js/system_order.js?v=39.15.47" defer></script>
<script src="js/simulation.js?v=39.15.47" defer></script>
<script src="js/world_update.js?v=39.15.47" defer></script>
<script src="js/world_tool_actions.js?v=39.15.47" defer></script>
<script src="js/world_placement_log.js?v=39.15.47" defer></script>
<script src="js/world_event_effects.js?v=39.15.47" defer></script>
<script src="js/command_dispatcher.js?v=39.15.47" defer></script>
<script src="js/text_catalog.js?v=39.15.47" defer></script>
<script src="js/ui.js?v=39.15.47" defer></script>
<script src="js/ui_log.js?v=39.15.47" defer></script>
<script src="js/ui_helpers.js?v=39.15.47" defer></script>
<script src="js/ui_selected.js?v=39.15.47" defer></script>
<script src="js/save_schema.js?v=39.15.47" defer></script>
<script src="js/snapshot_system.js?v=39.15.47" defer></script>
<script src="js/restore_coordinator.js?v=39.15.47" defer></script>
<script src="js/history_system.js?v=39.15.47" defer></script>
<script src="js/save_codec.js?v=39.15.47" defer></script>
<script src="js/save_storage.js?v=39.15.47" defer></script>
<script src="js/save_system.js?v=39.15.47" defer></script>
<script src="js/patch_helpers.js?v=39.15.47" defer></script>
<script src="js/ui_tooltips.js?v=39.15.47" defer></script>
<script src="js/ui_layout_dialogs.js?v=39.15.47" defer></script>
<script src="js/ui_ground.js?v=39.15.47" defer></script>
<script src="js/ui_tools.js?v=39.15.47" defer></script>
<script src="js/ui_input_shared.js?v=39.15.47" defer></script>
<script src="js/ui_input_touch.js?v=39.15.47" defer></script>
<script src="js/ui_input_mouse.js?v=39.15.47" defer></script>
<script src="js/ui_bind.js?v=39.15.47" defer></script>
<script src="js/ui_charts.js?v=39.15.47" defer></script>
<script src="js/ui_family_data.js?v=39.15.47" defer></script>
<script src="js/ui_family_async.js?v=39.15.47" defer></script>
<script src="js/ui_family_layout.js?v=39.15.47" defer></script>
<script src="js/ui_family_paths.js?v=39.15.47" defer></script>
<script src="js/ui_family_render.js?v=39.15.47" defer></script>
<script src="js/main.js?v=39.15.47" defer></script>
<script src="js/debug_tools.js?v=39.15.47" defer></script>
</body>
</html>

View file

@ -89,10 +89,10 @@
item.deletable = false;
item.pinTargetId = t.id;
const faceDir = t.facingDir ? t.facingDir() : (t.facing || 1);
const visualSide = isOshibyo ? -faceDir : faceDir;
const visualSide = isOshibyo ? -1 : faceDir;
item.pinVisualSide = visualSide;
item.pinAttachAngle = isOshibyo ? visualSide * 0.36 : Math.atan2(dy || -1, dx || visualSide);
item.pinAttachDistance = isOshibyo ? (t.radius || 16) * 0.72 : clamp(distToTarget || (t.radius || 16) * 0.58, (t.radius || 16) * 0.20, (t.radius || 16) * 0.74);
item.pinAttachAngle = isOshibyo ? -0.46 : Math.atan2(dy || -1, dx || visualSide);
item.pinAttachDistance = isOshibyo ? (t.radius || 16) * 0.88 : clamp(distToTarget || (t.radius || 16) * 0.58, (t.radius || 16) * 0.20, (t.radius || 16) * 0.74);
item.pinOffsetY = isOshibyo ? (t.radius || 16) * 0.40 : clamp(dy, -(t.radius || 16) * 0.74, (t.radius || 16) * 0.38);
item.pinDamageTick = 0;
item.pinFallCheckTimer = 0;
@ -138,13 +138,13 @@
t.stuckPushpinId = item.id;
item.deletable = false;
const faceDir = t.facingDir ? t.facingDir() : (t.facing || 1);
const visualSide = isOshibyo ? -faceDir : faceDir;
const visualSide = isOshibyo ? -1 : faceDir;
item.pinVisualSide = visualSide;
item.x = isOshibyo ? t.x + visualSide * (t.radius || 16) * 0.72 : t.x + visualSide * (t.radius || 16) * 0.36;
item.x = isOshibyo ? t.x - (t.radius || 16) * 0.88 : t.x + visualSide * (t.radius || 16) * 0.36;
item.y = isOshibyo ? t.y + (t.radius || 16) * 0.42 : t.y - (t.radius || 16) * 0.04;
item.prevX = item.x;
item.prevY = item.y;
item.spin = visualSide * (isOshibyo ? 0.52 : 0.28);
item.spin = isOshibyo ? -0.52 : visualSide * 0.28;
item.vx = t.vx || 0;
item.vy = t.vy || 0;
if (isOshibyo) {

View file

@ -56,7 +56,27 @@
function sprinklerRange(item) {
return Math.max(96, (Number(item?.r || 28) || 28) * 4.6);
// v39.15.47: effective cleaning radius increased to 3x the prior radius.
// Previous formula: max(96, r * 4.6). Current: max(288, r * 13.8).
return Math.max(288, (Number(item?.r || 28) || 28) * 13.8);
}
function spawnSprinklerWaterEffects(item, worldRef, range, flashStrength = 1) {
if (!worldRef?.effects || !item) return;
const burst = 8;
for (let i = 0; i < burst; i++) {
const base = (Math.PI * 2 * i) / burst + (Number(item.sprinklerSpin || 0) || 0) * 0.35;
const dist = Math.min(range * 0.55, 46 + i * 6);
const px = item.x + Math.cos(base) * dist;
const py = item.y + Math.sin(base) * dist * 0.72 - 6;
worldRef.effects.push(new Effect("ring", px, py, {
size: 6 + (i % 3) * 2,
life: 0.16 + (i % 2) * 0.05,
color: `rgba(120,204,245,${0.28 + 0.12 * flashStrength})`,
vx: Math.cos(base) * 8,
vy: Math.sin(base) * 6 - 10,
}));
}
}
function updateSprinkler(item, dt, worldRef) {
@ -77,9 +97,10 @@
if (distXY(item.x, item.y, it.x, it.y) <= range + r) targets.push(it);
}
const sprayAngle = global.deterministicAngle ? global.deterministicAngle(worldRef, "sprinkler-spray-angle", item) : Math.random() * Math.PI * 2;
const sprayX = item.x + Math.cos(sprayAngle) * Math.min(range * 0.42, 86);
const sprayY = item.y + Math.sin(sprayAngle) * Math.min(range * 0.42, 86);
const sprayX = item.x + Math.cos(sprayAngle) * Math.min(range * 0.42, 132);
const sprayY = item.y + Math.sin(sprayAngle) * Math.min(range * 0.42, 132);
worldRef.effects?.push(new Effect("ring", sprayX, sprayY, { size: 14, life: 0.18, color: "rgba(95,166,230,0.22)" }));
spawnSprinklerWaterEffects(item, worldRef, range, 0.8);
if (!targets.length) return true;
const pickIndex = Math.floor((global.deterministicUnit ? global.deterministicUnit(worldRef, "sprinkler-pick", item, targets.length) : Math.random()) * targets.length) % targets.length;
const target = targets[pickIndex];
@ -87,8 +108,10 @@
target.amount = 0;
target.hp = 0;
target.removedReason = "sprinkler-clean";
item.sprinklerFlash = 0.45;
item.sprinklerFlash = 0.55;
item.sprinklerJustActivated = 0.22;
worldRef.effects?.push(new Effect("ring", target.x, target.y, { size: Math.max(18, (target.r || 12) * 1.6), life: 0.28, color: "rgba(95,166,230,0.34)" }));
spawnSprinklerWaterEffects(item, worldRef, range, 1);
worldRef.markItemBucketsDirty?.("sprinkler-clean");
worldRef.markSpatialDirty?.("sprinkler-clean");
worldRef.updateItemCounts?.();
@ -123,6 +146,7 @@
updateReciprocator,
updatePoisonBlock,
updateSprinkler,
sprinklerRange,
updateFlexibleLink,
updateRigidLink,
resolveMechanicalInteractions,

View file

@ -297,41 +297,115 @@ draw(ctx, t, lighting = null) {
ctx.save();
const flash = clamp(Number(this.sprinklerFlash || 0) || 0, 0, 1);
const spin = Number(this.sprinklerSpin || 0) || 0;
ctx.fillStyle = styleNight ? "#405b6f" : "#8fc7df";
ctx.strokeStyle = styleNight ? "#20313f" : "#4e7f96";
ctx.lineWidth = Math.max(1.6, this.r * 0.06);
const range = globalThis.TarinaiItemDynamicToolSystem?.sprinklerRange ? globalThis.TarinaiItemDynamicToolSystem.sprinklerRange(this) : Math.max(288, (Number(this.r || 28) || 28) * 13.8);
const metalA = styleNight ? "#6a7782" : "#d6dde3";
const metalB = styleNight ? "#39434d" : "#8a969f";
const darkMetal = styleNight ? "#1e252b" : "#53606a";
const baseGreen = styleNight ? "#4f6a3a" : "#76a54b";
const baseGreenDark = styleNight ? "#304324" : "#547635";
const hose = styleNight ? "#2f3a42" : "#4b5963";
// base shadow
ctx.fillStyle = "rgba(30,40,48,0.14)";
ctx.beginPath();
ctx.ellipse(0, this.r * 0.22, this.r * 0.74, this.r * 0.34, 0, 0, Math.PI * 2);
ctx.ellipse(0, this.r * 0.70, this.r * 0.92, this.r * 0.26, 0, 0, Math.PI * 2);
ctx.fill();
// ground stake and foot
ctx.fillStyle = baseGreen;
ctx.strokeStyle = baseGreenDark;
ctx.lineWidth = Math.max(1.6, this.r * 0.05);
roundedRect(ctx, -this.r * 0.56, this.r * 0.12, this.r * 1.12, this.r * 0.46, this.r * 0.12);
ctx.fill();
ctx.stroke();
ctx.fillStyle = styleNight ? "#587789" : "#b8e5f2";
roundedRect(ctx, -this.r * 0.28, -this.r * 0.46, this.r * 0.56, this.r * 0.78, this.r * 0.12);
ctx.beginPath();
ctx.moveTo(-this.r * 0.18, this.r * 0.10);
ctx.lineTo(-this.r * 0.38, this.r * 0.88);
ctx.lineTo(-this.r * 0.18, this.r * 0.88);
ctx.lineTo(-this.r * 0.02, this.r * 0.10);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(this.r * 0.18, this.r * 0.10);
ctx.lineTo(this.r * 0.38, this.r * 0.88);
ctx.lineTo(this.r * 0.18, this.r * 0.88);
ctx.lineTo(this.r * 0.02, this.r * 0.10);
ctx.closePath();
ctx.fill();
ctx.stroke();
// riser pipe
const pipeGrad = ctx.createLinearGradient(-this.r * 0.12, -this.r * 0.54, this.r * 0.12, this.r * 0.36);
pipeGrad.addColorStop(0, metalA);
pipeGrad.addColorStop(0.55, metalB);
pipeGrad.addColorStop(1, metalA);
ctx.fillStyle = pipeGrad;
ctx.strokeStyle = darkMetal;
roundedRect(ctx, -this.r * 0.12, -this.r * 0.30, this.r * 0.24, this.r * 0.62, this.r * 0.08);
ctx.fill();
ctx.stroke();
// head and rotor
ctx.save();
ctx.translate(0, -this.r * 0.40);
ctx.rotate(spin);
ctx.strokeStyle = flash > 0 ? "rgba(174,226,255,0.95)" : (styleNight ? "rgba(142,190,214,0.72)" : "rgba(67,129,160,0.78)");
ctx.lineWidth = Math.max(2.0, this.r * 0.075);
const armGrad = ctx.createLinearGradient(-this.r * 0.84, 0, this.r * 0.84, 0);
armGrad.addColorStop(0, metalB);
armGrad.addColorStop(0.5, metalA);
armGrad.addColorStop(1, metalB);
ctx.strokeStyle = armGrad;
ctx.lineWidth = Math.max(2.4, this.r * 0.09);
ctx.lineCap = "round";
ctx.beginPath();
ctx.moveTo(-this.r * 0.58, -this.r * 0.46);
ctx.lineTo(this.r * 0.58, -this.r * 0.46);
ctx.moveTo(0, -this.r * 0.72);
ctx.lineTo(0, -this.r * 0.20);
ctx.moveTo(-this.r * 0.72, 0);
ctx.lineTo(this.r * 0.72, 0);
ctx.stroke();
ctx.strokeStyle = darkMetal;
ctx.lineWidth = Math.max(1.2, this.r * 0.04);
ctx.beginPath();
ctx.moveTo(0, -this.r * 0.28);
ctx.lineTo(0, this.r * 0.20);
ctx.stroke();
for (const sx of [-1, 1]) {
ctx.fillStyle = hose;
ctx.beginPath();
ctx.arc(sx * this.r * 0.68, -this.r * 0.46, this.r * 0.10, 0, Math.PI * 2);
ctx.fillStyle = "rgba(112,184,224,0.72)";
ctx.arc(sx * this.r * 0.74, 0, this.r * 0.10, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = flash > 0 ? "rgba(170,225,255,0.95)" : "rgba(118,188,232,0.80)";
ctx.beginPath();
ctx.arc(sx * this.r * 0.80, 0, this.r * 0.06, 0, Math.PI * 2);
ctx.fill();
}
if (flash > 0.02) {
ctx.strokeStyle = `rgba(142,216,255,${0.45 + flash * 0.35})`;
ctx.lineWidth = Math.max(1.0, this.r * 0.04);
for (const sx of [-1, 1]) {
for (let i = 0; i < 3; i++) {
const dir = sx < 0 ? -1 : 1;
const spread = (i - 1) * 0.18;
const len = this.r * (0.95 + i * 0.20);
ctx.beginPath();
ctx.moveTo(sx * this.r * 0.80, 0);
ctx.quadraticCurveTo(sx * this.r * (1.10 + i * 0.14), -this.r * (0.10 + Math.abs(spread) * 0.4), sx * this.r * (1.36 + i * 0.18), -this.r * (0.18 + spread));
ctx.stroke();
ctx.beginPath();
ctx.arc(sx * this.r * (1.36 + i * 0.18), -this.r * (0.18 + spread), this.r * 0.035 * (1.0 + i * 0.14), 0, Math.PI * 2);
ctx.fillStyle = `rgba(164,228,255,${0.35 + flash * 0.25})`;
ctx.fill();
}
}
}
ctx.restore();
const rangePulse = 0.30 + flash * 0.45;
ctx.strokeStyle = `rgba(88,157,214,${rangePulse})`;
ctx.lineWidth = 1.4;
ctx.setLineDash([4, 5]);
// top cap
ctx.fillStyle = metalA;
ctx.strokeStyle = darkMetal;
ctx.beginPath();
ctx.arc(0, 0, Math.max(this.r * 2.15, 44), 0, Math.PI * 2);
ctx.arc(0, -this.r * 0.40, this.r * 0.16, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
const rangePulse = 0.14 + flash * 0.30;
ctx.strokeStyle = `rgba(88,157,214,${rangePulse})`;
ctx.lineWidth = 1.2;
ctx.setLineDash([7, 6]);
ctx.beginPath();
ctx.arc(0, 0, range, 0, Math.PI * 2);
ctx.stroke();
ctx.restore();
} else if (this.type === "duplicator") {

View file

@ -42,6 +42,7 @@ function initializeItemTypeState(item, type, x, y) {
item.sprinklerTimer = rand(0.18, 0.72);
item.sprinklerSpin = rand(0, Math.PI * 2);
item.sprinklerFlash = 0;
item.sprinklerJustActivated = 0;
}
if (type === "ball") {
item.vx = rand(-5, 5);

View file

@ -18,6 +18,73 @@
return true;
}
function ownedStructureDestroyedLabel(structure) {
if (structure?.type === "plushie") return "ぬいぐるみ";
if (structure?.type === "grass_bed") return "かんたんベッド";
return structure?.label || "自分のもの";
}
function shouldPanicOwnerOnStructureGone(structure, reason = "") {
if (!structure || !structure.ownerId) return false;
if (!(structure.type === "plushie" || structure.type === "grass_bed")) return false;
if (String(reason || "") === "grass-bed-wake-used") return false;
return true;
}
function triggerOwnedStructureDestroyedPanic(worldRef, structure, breaker = null, reason = "structure-destroyed") {
if (!worldRef || !shouldPanicOwnerOnStructureGone(structure, reason)) return false;
const owner = (worldRef.tarinai || []).find(t => t && !t.dead && t.id === structure.ownerId);
if (!owner) return false;
const label = ownedStructureDestroyedLabel(structure);
const detail = `自分の${label}が壊された`;
const panicReason = `自分の${label}を壊されてパニックになっている`;
const threat = breaker || {
id: `${structure.id || "structure"}:destroyed`,
x: Number(structure.x) || owner.x,
y: Number(structure.y) || owner.y,
type: "owned_structure_destroyed",
dead: false,
};
owner.lastPanicDetail = detail;
owner.fearTimer = Math.max(owner.fearTimer || 0, structure.type === "plushie" ? 3.2 : 2.6);
owner.surpriseTimer = Math.max(owner.surpriseTimer || 0, 0.85);
owner.awakeLockTimer = Math.max(owner.awakeLockTimer || 0, 2.4);
if (owner.enterPanic) {
owner.enterPanic({
threat,
target: owner.panicDestination?.(threat, true) || null,
forceDestination: true,
reason: panicReason,
fear: structure.type === "plushie" ? 1.65 : 1.35,
stress: structure.type === "plushie" ? 20 : 15,
wake: true,
cause: `${structure.type}_destroyed`,
surpriseTimer: 0.85,
awakeLockTimer: 2.4,
});
} else {
owner.setActionState?.("panic", { target: owner.panicDestination?.(threat, true) || null, reason: panicReason, wake: true, sleeping: false });
}
if (typeof global.setBehaviorText === "function") {
global.setBehaviorText(owner, {
need: "safety",
actionId: "panic_escape",
actionLabel: "パニックになっている",
reasonText: panicReason,
causeText: detail,
target: threat,
phase: "perform",
source: "behavior",
});
}
owner.thought = panicReason;
owner.behaviorLockTimer = Math.max(owner.behaviorLockTimer || 0, 1.2);
worldRef.spawnBubble?.(owner.x, owner.y - (owner.radius || 20) * 1.20, "", "rgba(188,66,76,0.88)");
worldRef.log?.(`${owner.name || "たりない"}は自分の${label}を壊されてパニックになった。`, "danger");
worldRef.drawListDirty = true;
return true;
}
function releaseStructureUsers(worldRef, structure, opts = {}) {
if (!worldRef || !structure) return 0;
const reason = opts.reason || "\u306a\u304f\u306a\u3063\u305f";
@ -51,8 +118,10 @@
if (!structure || structure.dead) return false;
const reason = opts.reason || "structure-deleted";
releaseStructureUsers(worldRef, structure, { reason: opts.userReason || "\u7f6e\u304d\u3082\u306e\u304c\u306a\u304f\u306a\u3063\u305f", wake: opts.wake !== false });
const ownerPanic = shouldPanicOwnerOnStructureGone(structure, reason);
const changed = markStructureGone(structure, reason);
if (!changed) return false;
if (ownerPanic && opts.panicOwner !== false) triggerOwnedStructureDestroyedPanic(worldRef, structure, opts.breaker || null, reason);
markWorldAfterStructureChange(worldRef, reason);
worldRef?.emit?.("structure:deleted", { structure, type: structure.type, reason });
return true;
@ -73,7 +142,7 @@
function deleteItem(worldRef, item, opts = {}) {
if (!item || item.dead) return false;
if (item.isStructure) return deleteStructure(worldRef, item, { reason: opts.reason || "delete-tool", userReason: opts.userReason, wake: opts.wake });
if (item.isStructure) return deleteStructure(worldRef, item, { reason: opts.reason || "delete-tool", userReason: opts.userReason, wake: opts.wake, breaker: opts.breaker || null, panicOwner: opts.panicOwner });
item.amount = 0;
item.hp = 0;
item.removedReason = String(opts.reason || "delete-tool");
@ -87,5 +156,6 @@
deleteStructure,
consumeGrassBedOnWake,
deleteItem,
triggerOwnedStructureDestroyedPanic,
});
})(window);

View file

@ -98,6 +98,7 @@
this.dead = true;
this.amount = 0;
global.StructureRegistry?.get?.(this.type)?.onDestroyed?.(this, worldRef, breaker);
global.TarinaiStructureLifecycle?.triggerOwnedStructureDestroyedPanic?.(worldRef, this, breaker, "structure-damaged-destroyed");
return true;
}

View file

@ -61,36 +61,18 @@ function bindUI() {
}
function openAutoCollapsedSectionsForMobileScroll() {
if (!isTouchMobileLayout()) return;
for (const card of document.querySelectorAll(".collapsible-card.collapsed")) {
if (card.dataset.userCollapsed === "1") continue;
markCollapsibleCardState(card, false);
}
for (const category of ui.toolPalette?.querySelectorAll(".tool-category.collapsed") || []) {
if (category.dataset.userCollapsed === "1") continue;
markToolCategoryState(category, false);
}
// v39.15.47: 自動スクロールは折り畳み状態を一切変更しない。
return;
}
function scrollWindowToElement(target, offset = 0, behavior = "smooth") {
function scrollWindowToElement(target, offset = 0, behavior = "auto") {
if (!target) return;
const scroller = document.scrollingElement || document.documentElement;
const clampTop = (value) => Math.max(0, Math.min(value, Math.max(0, scroller.scrollHeight - window.innerHeight)));
const desiredTop = () => clampTop(window.scrollY + target.getBoundingClientRect().top - offset);
const go = (b = behavior) => window.scrollTo({ top: desiredTop(), behavior: b });
go(behavior);
// Explicit button scroll only. No scroll-snap listener is used; these
// delayed corrections are only for DOM height changes after opening cards
// or rendering the family graph.
let tries = 0;
const correct = () => {
tries += 1;
const rect = target.getBoundingClientRect();
const delta = rect.top - offset;
if (Math.abs(delta) > 10) go("auto");
if (tries < 5 && Math.abs(delta) > 10) window.setTimeout(correct, tries < 2 ? 180 : 320);
};
window.setTimeout(correct, 180);
const rect = target.getBoundingClientRect?.();
if (!rect) return;
const maxTop = Math.max(0, scroller.scrollHeight - window.innerHeight);
const top = Math.max(0, Math.min(window.scrollY + rect.top - offset, maxTop));
window.scrollTo({ top, behavior: "auto" });
}
function scrollPanelToTab(tab = "") {
@ -108,30 +90,18 @@ function bindUI() {
if (!target) return false;
target.classList?.remove?.("hidden");
if (tab === "family") {
// Auto update OFF should not make the family graph disappear.
// A direct jump to the family section is treated as a manual refresh,
// while continuous auto-refresh remains disabled.
// 更新OFFでも、ユーザーが家系図へ移動した時だけ現在スナップショットを描画する。
window.resetArchiveRenderState?.();
renderArchive?.();
}
if (isTouchMobileLayout()) openAutoCollapsedSectionsForMobileScroll();
const collapsedCard = target.closest?.(".collapsible-card") || (target.classList?.contains?.("collapsible-card") ? target : null);
if (collapsedCard?.classList?.contains?.("collapsed") && collapsedCard.dataset.userCollapsed !== "1") {
markCollapsibleCardState(collapsedCard, false);
}
const tabsH = ui.panelQuickTabs?.offsetHeight || 0;
const style = window.getComputedStyle ? window.getComputedStyle(panel) : null;
const panelScrollable = panel.scrollHeight > panel.clientHeight + 4 && style?.overflowY !== "visible";
const mobileOffset = isTouchMobileLayout()
? ((ui.panelQuickTabs?.getBoundingClientRect?.().height || tabsH || 0) + 10)
: 0;
if (tab === "game") {
scrollWindowToElement(target, 8);
} else if (panelScrollable && !isTouchMobileLayout()) {
const isDesktop = window.matchMedia?.("(min-width: 981px)")?.matches ?? window.innerWidth >= 981;
if (isDesktop && tab !== "game" && tab !== "family") {
const tabsH = ui.panelQuickTabs?.offsetHeight || 0;
const top = Math.max(0, target.offsetTop - tabsH - 8);
panel.scrollTo({ top, behavior: "smooth" });
panel.scrollTo({ top, behavior: "auto" });
} else {
scrollWindowToElement(target, mobileOffset);
// スマホはページ全体スクロール。目的地UIの上端を画面上端へ合わせる。
scrollWindowToElement(target, 0, "auto");
}
setActivePanelTab(tab);
return true;
@ -143,7 +113,7 @@ function bindUI() {
const btn = e.target.closest("[data-panel-tab]");
if (!btn) return;
const now = performance?.now?.() ?? Date.now();
if (now - Number(ui.panelQuickTabs?.dataset?.lastActivateAt || 0) < 160) return;
if (now - Number(ui.panelQuickTabs?.dataset?.lastActivateAt || 0) < 50) return;
if (ui.panelQuickTabs) ui.panelQuickTabs.dataset.lastActivateAt = String(now);
e.preventDefault();
audio.uiClick?.();

View file

@ -42,12 +42,18 @@ function lineageTreeNodeHtml(n, x, y) {
const reason = briefReason ? ` / ${briefReason}` : "";
const rawTags = live?.personalityTags ? live.personalityTags() : (Array.isArray(n?.personalityTags) ? n.personalityTags : []);
const tags = rawTags.slice();
const displayTags = tags.slice(0, 4);
const tagText = tags.length ? ` / ${tags.join(" ")}` : "";
const title = escapeHtml(`${n?.name || "\u4e0d\u660e"} / \u4e16\u4ee3 ${n?.generation || "?"} / ${status}${reason}${tagText}`);
const deathLine = briefReason ? `<span class="death-reason">${escapeHtml(briefReason)}</span>` : "";
const tagLine = tags.length ? `<span class="family-personality-tags">${tags.map(tag => `<em>${escapeHtml(tag)}</em>`).join("")}</span>` : "";
const tagLine = displayTags.length ? `<span class="family-personality-tags">${displayTags.map(tag => `<em>${escapeHtml(tag)}</em>`).join("")}</span>` : "";
const champion = !!(live?.isTarinaiChampion || n?.isTarinaiChampion);
const statLine = n ? `<span class="family-genetic-stats" title="寿命 / 体格 / 攻撃 / 速度 / 勝率"><b>寿</b>${escapeHtml(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "life")))} <b>体</b>${escapeHtml(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "size")))} <b>攻</b>${escapeHtml(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "attack")))} <b>速</b>${escapeHtml(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "speed")))} <b>勝</b>${escapeHtml(lineageFightRateText(n))}</span>` : "";
const statLine = n ? `<span class="family-genetic-stats" title="寿命 / 体格 / 攻撃 / 速度">
<span><b>寿</b>${escapeHtml(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "life")))}</span>
<span><b></b>${escapeHtml(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "size")))}</span>
<span><b></b>${escapeHtml(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "attack")))}</span>
<span><b></b>${escapeHtml(lineageSignedPercentText(lineageGeneticPercentFromNode(n, "speed")))}</span>
</span><span class="family-fight-stat" title="けんか勝率 / 回数"><b>勝</b>${escapeHtml(lineageFightRateText(n))}</span>` : "";
const cls = n ? ((live || n.alive) ? "alive" : "dead") : "missing";
const childCls = (n?.parents || []).length ? " child" : "";
const iconBaseScale = live
@ -382,7 +388,7 @@ function lineageLayoutRows(rows, idSet) {
const SIBLING_GAP = 32;
const GROUP_GAP = 52;
const PARTNER_GROUP_GAP = 24;
const ROW_GAP = 138;
const ROW_GAP = 108;
const LEFT_PAD = 82;
const TOP_PAD = 44;
const RIGHT_PAD = 48;

View file

@ -55,7 +55,7 @@ function lineageLayoutRowsFast(rows, idSet) {
const NODE_W = 150;
const NODE_H = 104;
const SIBLING_GAP = 22;
const ROW_GAP = 116;
const ROW_GAP = 92;
const LEFT_PAD = 82;
const TOP_PAD = 44;
const RIGHT_PAD = 48;

View file

@ -18,6 +18,7 @@
mode = inputModeManager?.setMode?.(mode) || (["auto", "camera", "tool"].includes(mode) ? mode : "auto");
uiCache.mobileInputMode = mode;
ui.mobileModeControls?.querySelectorAll("[data-mobile-mode]").forEach(btn => btn.classList.toggle("active", btn.dataset.mobileMode === mode));
document.body.classList.toggle("mobile-mode-auto", mode === "auto");
document.body.classList.toggle("mobile-mode-camera", mode === "camera");
document.body.classList.toggle("mobile-mode-tool", mode === "tool");
return mode;

View file

@ -5,12 +5,18 @@
if (!ctx?.canvas) return false;
const { canvas, world, uiCache } = ctx;
ctx.bindMobileModeControls();
const isMobileAutoScrollMode = () => {
const mode = ctx.mobileInputMode?.() || "auto";
const touchMobile = document.body.classList.contains("touch-mobile-ui") || window.matchMedia?.("(max-width: 980px)")?.matches;
return touchMobile && mode === "auto" && (!world.tool || world.tool === "observe");
};
canvas.addEventListener("touchstart", (e) => {
if (!e.touches?.length) return;
e.preventDefault();
uiCache.touchMoved = false;
const mode = ctx.mobileInputMode();
const autoPageScroll = isMobileAutoScrollMode();
if (!autoPageScroll) e.preventDefault();
if (mode === "camera" && e.touches.length === 1) {
const t = e.touches[0];
uiCache.touchStartX = t.clientX;
@ -62,12 +68,12 @@
return;
}
if (world.tool === "pinch" && p.inside && ctx.beginGrab(p, t.clientX, t.clientY, { touchMode: "grab" })) return;
uiCache.touchMode = mode === "tool" ? "toolTapOnly" : "panOrTap";
uiCache.touchMode = autoPageScroll ? "autoTapOrScroll" : (mode === "tool" ? "toolTapOnly" : "panOrTap");
}, { passive: false });
canvas.addEventListener("touchmove", (e) => {
if (!e.touches?.length || !uiCache.touchMode) return;
e.preventDefault();
if (uiCache.touchMode !== "autoTapOrScroll") e.preventDefault();
if (uiCache.touchMode === "pinchZoom" && e.touches.length >= 2) {
const c = ctx.touchCenter(e.touches);
const distNow = Math.max(1, ctx.touchDistance(e.touches));
@ -116,6 +122,10 @@
ctx.moveGrab(p, t.clientX, t.clientY, { touch: true, renderFrame: true });
return;
}
if (uiCache.touchMode === "autoTapOrScroll") {
if (totalMove > 7) uiCache.touchMoved = true;
return;
}
if (uiCache.touchMode === "panOrTap") {
if (totalMove > 7) {
uiCache.touchMoved = true;
@ -126,7 +136,7 @@
}, { passive: false });
canvas.addEventListener("touchend", (e) => {
e.preventDefault();
if (uiCache.touchMode !== "autoTapOrScroll") e.preventDefault();
if (uiCache.areaDeleting) ctx.endAreaDelete();
if (uiCache.hosing) { ctx.endHose(); if (uiCache.hoseHoldTimer) { clearInterval(uiCache.hoseHoldTimer); uiCache.hoseHoldTimer = null; } }
if (uiCache.shooting) ctx.endShoot();
@ -138,6 +148,13 @@
window.TarinaiCommands?.dispatch?.(world, { type: "pointer.primary", x: p.x, y: p.y });
renderStats();
}
} else if (uiCache.touchMode === "autoTapOrScroll" && !uiCache.touchMoved) {
e.preventDefault();
const p = ctx.screenToWorldClient(uiCache.touchLastX || uiCache.touchStartX, uiCache.touchLastY || uiCache.touchStartY);
if (p.inside) {
window.TarinaiCommands?.dispatch?.(world, { type: "pointer.primary", x: p.x, y: p.y });
renderStats();
}
} else if (uiCache.touchMode === "panOrTap" && !uiCache.touchMoved) {
const p = ctx.screenToWorldClient(uiCache.touchLastX || uiCache.touchStartX, uiCache.touchLastY || uiCache.touchStartY);
if (p.inside) {

View file

@ -83,12 +83,27 @@ function pushLogNotification(entry = {}) {
window.pushLogNotification = pushLogNotification;
function shouldShowInEventLog(entry = {}) {
if (!entry) return false;
if (entry.eventType === "player_item_placement") return false;
const text = String(entry.text || "");
const kind = entry.kind || "";
// Older saves may contain direct placement records without eventType.
// Hide only participant-less player-like placement lines, preserving births, accidents, fights, and natural events.
if ((kind === "observe" || kind === "note") && !(entry.participants || []).length && /(?:を配置した。|を落とした。)$/.test(text)) return false;
return true;
}
function renderLog(logs) {
ui.log.innerHTML = "";
const frag = document.createDocumentFragment();
const touchFirst = Boolean(window.TarinaiInputMode?.classification?.touchFirst || document.body.classList.contains("touch-mobile-ui") || window.innerWidth <= 980);
const limit = touchFirst ? 34 : 80;
const rows = Array.isArray(logs) ? logs.slice(0, limit) : [];
const visibleLogs = Array.isArray(logs) ? logs.filter(raw => {
const entry = typeof raw === "string" ? { time: world.time, text: raw, kind: "note" } : raw;
return shouldShowInEventLog(entry);
}) : [];
const rows = visibleLogs.slice(0, limit);
for (const raw of rows) {
const entry = typeof raw === "string" ? { time: world.time, text: raw, kind: "note" } : raw;
const kind = entry.kind || "note";

View file

@ -1,8 +1,8 @@
"use strict";
(function () {
const APP_VERSION = "39.15.39";
const APP_BUILD = "tarinai-pc-rollback-mobile-family-fix-v39-15-39";
const APP_VERSION = "39.15.47";
const APP_BUILD = "tarinai-event-family-oshibyo-fix-v39-15-45";
const APP_CACHE_NAME = `tarinai-colony-${APP_VERSION}`;
const STATIC_VERSION_PARAM = `v=${APP_VERSION}`;

View file

@ -1433,7 +1433,7 @@
if (!placed) return;
forceImmediateToolVisualRefresh(this, "tool-place");
const label = toolLabel(itemType);
this.log(itemType === "genkotsu" || isPinType(itemType) ? `${label}\u3092\u843d\u3068\u3057\u305f\u3002` : `${label}\u3092\u914d\u7f6e\u3057\u305f\u3002`);
this.log(itemType === "genkotsu" || isPinType(itemType) ? `${label}\u3092\u843d\u3068\u3057\u305f\u3002` : `${label}\u3092\u914d\u7f6e\u3057\u305f\u3002`, "observe", { eventType: "player_item_placement" });
}
},

View file

@ -378,8 +378,8 @@
if (!aim?.inside) return false;
const hit = this.findShootToolTargetAt?.(aim.x, aim.y);
audio.shoot?.();
this.effects?.push(new Effect("shoot_impact", aim.x, aim.y, { size: hit ? 10 : 7, life: hit ? 0.20 : 0.14, color: hit ? "rgba(255,208,116,0.96)" : "rgba(190,212,240,0.58)" }));
this.effects?.push(new Effect("ring", aim.x, aim.y, { size: hit ? 8 : 5, life: hit ? 0.11 : 0.08, color: hit ? "rgba(255,246,206,0.72)" : "rgba(196,220,255,0.34)" }));
this.effects?.push(new Effect("shoot_impact", aim.x, aim.y, { size: hit ? 22 : 16, life: hit ? 0.28 : 0.22, color: hit ? "rgba(255,208,116,0.96)" : "rgba(190,212,240,0.64)" }));
this.effects?.push(new Effect("ring", aim.x, aim.y, { size: hit ? 20 : 14, life: hit ? 0.18 : 0.14, color: hit ? "rgba(255,246,206,0.74)" : "rgba(196,220,255,0.42)" }));
let handled = false;
if (hit?.kind === "tarinai") {
const t = hit.target;

104
scripts/ai_inventory.ps1 Normal file
View file

@ -0,0 +1,104 @@
param(
[string]$Query = "",
[switch]$All,
[switch]$Json
)
$Root = Resolve-Path (Join-Path $PSScriptRoot "..")
$RoutingPath = Join-Path $Root "FILES.json"
$Routing = Get-Content -Raw -LiteralPath $RoutingPath | ConvertFrom-Json
$Exact = @{
".gitignore" = "ignore rules for local/generated artifacts"
".htaccess" = "Apache/static-host cache, MIME, and service-worker behavior"
"app_manifest.json" = "canonical version plus CSS/JS/static asset manifest"
"favicon.ico" = "legacy favicon"
"index.html" = "app DOM shell, canvas, panels, dialogs, and ordered script tags"
"README.md" = "small AI entrypoint and read strategy"
"AI_MAP.md" = "subsystem map and task-oriented read sets"
"FILES.json" = "machine-readable file routing and glob semantics"
"service-worker.js" = "generated offline/cache service worker"
}
$JsExact = @{
"ants.js" = "ant actor and ant nest worker behavior"
"assets.js" = "image loading, image metrics, and render caches"
"audio.js" = "sample playback and audio controls"
"command_dispatcher.js" = "central command routing for UI/tools/pointers/save"
"data.js" = "sprite/decor definitions, needs, config, field tuning"
"debug_tools.js" = "browser debug overlay and diagnostics"
"family_graph.js" = "lineage graph helpers"
"ground_types.js" = "ground definitions, multipliers, limits, labels"
"health.js" = "health, stress, need, damage, and death helpers"
"history_system.js" = "rolling world statistics/history"
"input_mode_manager.js" = "pointer/touch/mobile input classification"
"items.js" = "item constructors, food/grass helpers, item preview drawing"
"main.js" = "startup, restore, UI binding, update/render loop"
"render.js" = "main canvas renderer"
"system_order.js" = "named update phase order"
"text_catalog.js" = "Japanese UI/state/behavior labels"
"version.js" = "app version/cache/static query config"
"weather_system.js" = "weather selection and application"
}
function Get-GroupId($Path) {
foreach ($Group in $Routing.groups) {
foreach ($Pattern in $Group.patterns) {
if ($Path -like $Pattern) { return $Group.id }
}
}
return ($Path -split "/")[0]
}
function Get-Meaning($Path) {
if ($Exact.ContainsKey($Path)) { return $Exact[$Path] }
if ($Path.StartsWith("js/")) {
$Name = Split-Path -Leaf $Path
if ($JsExact.ContainsKey($Name)) { return $JsExact[$Name] }
if ($Name.StartsWith("world_")) { return "world state/view/update/tool/environment/combat/social subsystem" }
if ($Name.StartsWith("tarinai_")) { return "creature action/needs/social/item/update subsystem" }
if ($Name.StartsWith("item_lifecycle_step_")) { return "item lifecycle pipeline stage" }
if ($Name.StartsWith("item_lifecycle_")) { return "item lifecycle subsystem" }
if ($Name.StartsWith("item_dynamic_")) { return "dynamic item behavior subsystem" }
if ($Name.StartsWith("item_")) { return "item registry/runtime/update/render subsystem" }
if ($Name.StartsWith("simulation_")) { return "simulation phase subsystem" }
if ($Name.StartsWith("ui_family_")) { return "family tree UI subsystem" }
if ($Name.StartsWith("ui_input_")) { return "input handling subsystem" }
if ($Name.StartsWith("ui_")) { return "DOM/panel UI subsystem" }
if ($Name.StartsWith("save_")) { return "save/load persistence subsystem" }
if ($Name.StartsWith("physics_")) { return "physics helper/world subsystem" }
return "runtime JavaScript module"
}
if ($Path.StartsWith("scripts/")) { return "repo script/tooling" }
if ($Path.StartsWith("css/")) { return "stylesheet layer" }
if ($Path.StartsWith("assets/sprites/")) { return "creature state sprite" }
if ($Path.StartsWith("assets/ui/")) { return "UI/app/tool image asset" }
if ($Path.StartsWith("assets/objects/")) { return "field object image asset" }
if ($Path.StartsWith("assets/sounds/")) { return "voice or sound-effect sample" }
return "project file"
}
$Files = Get-ChildItem -LiteralPath $Root -Recurse -File -Force |
Where-Object { $_.FullName -notmatch "\\.git\\" -and $_.FullName -notmatch "\\__pycache__\\" } |
ForEach-Object { $_.FullName.Substring($Root.Path.Length + 1).Replace("\", "/") } |
Sort-Object
$Records = foreach ($Path in $Files) {
$Group = Get-GroupId $Path
$Meaning = Get-Meaning $Path
if ($Query -and ($Path.ToLowerInvariant() -notlike "*$($Query.ToLowerInvariant())*" -and $Group.ToLowerInvariant() -notlike "*$($Query.ToLowerInvariant())*" -and $Meaning.ToLowerInvariant() -notlike "*$($Query.ToLowerInvariant())*")) {
continue
}
[pscustomobject]@{ path = $Path; group = $Group; meaning = $Meaning }
}
if ($Json) {
$Records | ConvertTo-Json -Depth 4
} elseif ($All) {
$Records | ForEach-Object { "$($_.path): $($_.meaning)" }
} else {
$Records | Group-Object group | Sort-Object Name | ForEach-Object {
"$($_.Name): $($_.Count) files"
$_.Group | Select-Object -ExpandProperty meaning -Unique | Sort-Object | Select-Object -First 8 | ForEach-Object { " - $_" }
}
}

207
scripts/ai_inventory.py Normal file
View file

@ -0,0 +1,207 @@
from __future__ import annotations
import argparse
import fnmatch
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
ROUTING_FILE = ROOT / "FILES.json"
EXCLUDE_DIRS = {".git", "__pycache__"}
EXACT_DESCRIPTIONS = {
".gitignore": "ignore rules for local/generated artifacts",
".htaccess": "Apache/static-host cache, MIME, and service-worker behavior",
"app_manifest.json": "canonical version plus CSS/JS/static asset manifest",
"favicon.ico": "legacy favicon",
"index.html": "app DOM shell, canvas, panels, dialogs, and ordered script tags",
"README.md": "small AI entrypoint and read strategy",
"AI_MAP.md": "subsystem map and task-oriented read sets",
"FILES.json": "machine-readable file routing and glob semantics",
"service-worker.js": "generated offline/cache service worker",
}
GROUP_DESCRIPTIONS = {
"css": "stylesheet layer",
"scripts": "repo tooling",
"js": "runtime JavaScript",
"assets/sprites": "creature state sprite",
"assets/ui": "UI/app/tool image asset",
"assets/objects": "field object image asset",
"assets/sounds": "voice or sound-effect sample",
}
JS_PREFIX_DESCRIPTIONS = [
("world_", "world state/view/update/tool/environment/combat/social subsystem"),
("tarinai_", "creature action/needs/social/item/update subsystem"),
("item_lifecycle_step_", "item lifecycle pipeline stage"),
("item_lifecycle_", "item lifecycle subsystem"),
("item_dynamic_", "dynamic item behavior subsystem"),
("item_", "item registry/runtime/update/render subsystem"),
("simulation_", "simulation phase subsystem"),
("ui_family_", "family tree UI subsystem"),
("ui_input_", "input handling subsystem"),
("ui_", "DOM/panel UI subsystem"),
("save_", "save/load persistence subsystem"),
("physics_", "physics helper/world subsystem"),
]
JS_EXACT_DESCRIPTIONS = {
"ants.js": "ant actor and ant nest worker behavior",
"assets.js": "image loading, image metrics, and render caches",
"audio.js": "sample playback and audio controls",
"collision_footprint_system.js": "collision footprint shapes and overlap tests",
"command_dispatcher.js": "central command routing for UI/tools/pointers/save",
"constraint_system.js": "attachments, ropes, push/pull, and collision constraints",
"data.js": "sprite/decor definitions, needs, config, field tuning",
"debug_tools.js": "browser debug overlay and diagnostics",
"disease_registry.js": "disease definitions and lookup",
"event_bus.js": "global event bus singleton",
"family_graph.js": "lineage graph helpers",
"ground_types.js": "ground definitions, multipliers, limits, labels",
"health.js": "health, stress, need, damage, and death helpers",
"history_system.js": "rolling world statistics/history",
"input_mode_manager.js": "pointer/touch/mobile input classification",
"items.js": "item constructors, food/grass helpers, item preview drawing",
"main.js": "startup, restore, UI binding, update/render loop",
"math.js": "shared numeric helpers",
"mechanical_system.js": "mechanical item forces, rails, ropes, pins, fences",
"patch_helpers.js": "compatibility patch helpers",
"perf_profiler.js": "performance profiler and quality adaptation",
"registry_base.js": "registry normalization helper base",
"render.js": "main canvas renderer",
"restore_coordinator.js": "post-load restore ordering/fixups",
"sim_core.js": "simulation primitives and shared helpers",
"sound_pack.js": "logical sound ID to asset mapping",
"snapshot_system.js": "world snapshot capture/restore",
"structures.js": "structure definitions and placement/runtime helpers",
"structure_lifecycle.js": "structure update lifecycle",
"system_order.js": "named update phase order",
"text_catalog.js": "Japanese UI/state/behavior labels",
"version.js": "app version/cache/static query config",
"weather_system.js": "weather selection and application",
}
def rel(path: Path) -> str:
return path.relative_to(ROOT).as_posix()
def iter_files() -> list[str]:
files: list[str] = []
for path in ROOT.rglob("*"):
if not path.is_file():
continue
if any(part in EXCLUDE_DIRS for part in path.relative_to(ROOT).parts):
continue
files.append(rel(path))
return sorted(files)
def load_routing() -> dict:
return json.loads(ROUTING_FILE.read_text(encoding="utf-8"))
def group_for(path: str, routing: dict) -> str:
for group in routing.get("groups", []):
for pattern in group.get("patterns", []):
if fnmatch.fnmatch(path, pattern):
return group["id"]
return path.split("/", 1)[0]
def describe(path: str, routing: dict) -> str:
if path in EXACT_DESCRIPTIONS:
return EXACT_DESCRIPTIONS[path]
if path.startswith("js/"):
name = path.rsplit("/", 1)[-1]
if name in JS_EXACT_DESCRIPTIONS:
return JS_EXACT_DESCRIPTIONS[name]
for prefix, desc in JS_PREFIX_DESCRIPTIONS:
if name.startswith(prefix):
return desc
return "runtime JavaScript module"
if path.startswith("scripts/"):
return "repo script/tooling"
if path.startswith("css/"):
return "stylesheet layer"
if path.startswith("assets/"):
for prefix, desc in GROUP_DESCRIPTIONS.items():
if path.startswith(prefix + "/"):
return desc
return "static asset"
group_id = group_for(path, routing)
for group in routing.get("groups", []):
if group.get("id") == group_id:
return group.get("meaning", "project file")
return "project file"
def matches_query(path: str, query: str, routing: dict) -> bool:
if not query:
return True
q = query.lower()
if q in path.lower():
return True
group_id = group_for(path, routing).lower()
if q == group_id or q in group_id:
return True
return q in describe(path, routing).lower()
def build_records(query: str = "") -> list[dict]:
routing = load_routing()
records = []
for path in iter_files():
if not matches_query(path, query, routing):
continue
records.append({
"path": path,
"group": group_for(path, routing),
"meaning": describe(path, routing),
})
return records
def print_compact(records: list[dict]) -> None:
groups: dict[str, list[dict]] = {}
for record in records:
groups.setdefault(record["group"], []).append(record)
for group_id in sorted(groups):
print(f"{group_id}: {len(groups[group_id])} files")
meanings = sorted({record["meaning"] for record in groups[group_id]})
for meaning in meanings[:8]:
print(f" - {meaning}")
def print_all(records: list[dict]) -> None:
for record in records:
print(f"{record['path']}: {record['meaning']}")
def main() -> int:
parser = argparse.ArgumentParser(description="Print token-aware AI file inventory.")
parser.add_argument("query", nargs="?", default="", help="Optional group/path/meaning filter, e.g. ui, world, assets.")
parser.add_argument("--all", action="store_true", help="Print every matching file instead of grouped summary.")
parser.add_argument("--json", action="store_true", help="Print JSON records.")
args = parser.parse_args()
records = build_records(args.query)
if args.json:
print(json.dumps(records, ensure_ascii=False, indent=2))
elif args.all:
print_all(records)
else:
print_compact(records)
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,6 +1,6 @@
"use strict";
const APP_VERSION = "39.15.39";
const APP_VERSION = "39.15.47";
const CACHE_NAME = `tarinai-colony-${APP_VERSION}`;
const v = `v=${APP_VERSION}`;
// Generated from app_manifest.json. Run scripts/generate_app_files.py after changing static files.