This commit is contained in:
33333-33333 2026-07-01 16:55:49 +09:00
commit c9dd7182aa
35 changed files with 478 additions and 288 deletions

View file

@ -6,7 +6,7 @@ Purpose: static Japanese browser simulation/game. Optimize context by reading th
- 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.101`.
- Version/cache: `app_manifest.json`, `js/version.js`, and generated `service-worker.js` share `39.15.103`.
- 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/simulation_systems.js`.
- Command path: UI/input emits command objects; `js/command_dispatcher.js` routes tool, pointer, selection, save, reset, and UI intents.

View file

@ -3,4 +3,4 @@ tarinai_king_fix_v10
- Burning tarinai and ants draw lightweight flame-shaped overlays.
- Flame firecracker is redder and shows a countdown timer; normal firecracker is darker.
- Standalone flame and fire particles use a simpler flame silhouette.
- Version: 39.15.101
- Version: 39.15.102

View file

@ -1,6 +1,6 @@
{
"purpose": "Token-efficient machine-readable routing for tarinai_. Use scripts/ai_inventory.py for exact current file expansion.",
"version": "39.15.101",
"version": "39.15.103",
"entrypoints": {
"app": "index.html",
"runtime": "js/main.js",

View file

@ -16,7 +16,8 @@ The core loop is:
## Main UI
- **Canvas / field**: the main simulation area. Tarinai, items, effects, ants, weather, and terrain are drawn here.
- **Top buttons**: pause/resume, speed control, ecology guide, sound settings, credits, save, and reset.
- **Top buttons**: pause/resume, speed control, ecology guide, sound settings, credits, save, display settings, and reset.
- **Display settings**: manually adjust effects, shadows, and drawing detail. `High` keeps quality-preserving optimizations active, while lower tiers cut heavier visuals for crowded scenes without lowering object limits.
- **Mobile mode controls**: auto, camera, and tool modes for touch screens.
- **Right panel tabs**: jump between main game, selected tarinai data, tools, colony, events, and family tree.
- **Tarinai data panel**: appears when a tarinai is selected. Shows health, needs, personality, active effects, relations, inventory, behavior reason, and records.

View file

@ -18,7 +18,7 @@ AI-first entrypoint for a static browser simulation/game. Keep this file small;
## Runtime Shape
- No bundler; `index.html` loads ordered global-IIFE scripts with `?v=39.15.101`.
- No bundler; `index.html` loads ordered global-IIFE scripts with `?v=39.15.103`.
- 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`.

View file

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

View file

@ -412,27 +412,27 @@ body::before {
width: 100% !important;
max-width: 100% !important;
position: static !important;
display: flex !important;
flex-direction: column !important;
gap: 1px !important;
}
.lineage-family-tree .tree-node .family-copy strong,
.lineage-family-tree .tree-node .family-copy > span:not(.family-personality-tags) {
max-width: 100% !important;
}
.lineage-family-tree .tree-node .family-copy > .family-personality-tags {
position: absolute !important;
left: 7px !important;
right: 7px !important;
bottom: 5px !important;
width: auto !important;
max-width: none !important;
height: 26px !important;
max-height: 26px !important;
margin: 0 !important;
grid-auto-rows: 12px !important;
position: static !important;
width: 100% !important;
max-width: 100% !important;
height: 24px !important;
max-height: 24px !important;
margin: 2px 0 0 !important;
grid-auto-rows: 11px !important;
gap: 1px 3px !important;
z-index: 3 !important;
}
.lineage-family-tree .tree-node .family-personality-tags em {
height: 12px !important;
line-height: 10px !important;
font-size: 7px !important;
height: 11px !important;
line-height: 9px !important;
font-size: 6.8px !important;
}

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.101" />
<link rel="stylesheet" href="css/layout.css?v=39.15.101" />
<link rel="stylesheet" href="css/panel.css?v=39.15.101" />
<link rel="stylesheet" href="css/components.css?v=39.15.101" />
<link rel="stylesheet" href="css/mobile.css?v=39.15.101" />
<link rel="stylesheet" href="css/base.css?v=39.15.107" />
<link rel="stylesheet" href="css/layout.css?v=39.15.107" />
<link rel="stylesheet" href="css/panel.css?v=39.15.107" />
<link rel="stylesheet" href="css/components.css?v=39.15.107" />
<link rel="stylesheet" href="css/mobile.css?v=39.15.107" />
</head>
<body>
<div id="loadingScreen" class="loading-screen" aria-live="polite">
@ -191,20 +191,25 @@
<div><strong>&#x30A8;&#x30D5;&#x30A7;&#x30AF;&#x30C8;</strong><span>&#x7834;&#x7247;&#x3001;&#x96E8;&#x3001;&#x5149;&#x306E;&#x6F14;&#x51FA;</span></div>
<div class="visual-setting-options" role="group" aria-label="&#x30A8;&#x30D5;&#x30A7;&#x30AF;&#x30C8;">
<button type="button" data-visual-setting="effects" data-visual-value="off">OFF</button>
<button type="button" data-visual-setting="effects" data-visual-value="on">ON</button>
<button type="button" data-visual-setting="effects" data-visual-value="low">&#x4F4E;</button>
<button type="button" data-visual-setting="effects" data-visual-value="mid">&#x4E2D;</button>
<button type="button" data-visual-setting="effects" data-visual-value="high">&#x9AD8;</button>
</div>
</div>
<div class="visual-setting-row">
<div><strong>&#x5F71;</strong><span>&#x5730;&#x9762;&#x306E;&#x843D;&#x3061;&#x5F71;&#x3068;&#x30B9;&#x30D7;&#x30E9;&#x30A4;&#x30C8;&#x306E;&#x5F71;</span></div>
<div class="visual-setting-options" role="group" aria-label="&#x5F71;">
<button type="button" data-visual-setting="shadows" data-visual-value="off">OFF</button>
<button type="button" data-visual-setting="shadows" data-visual-value="on">ON</button>
<button type="button" data-visual-setting="shadows" data-visual-value="low">&#x4F4E;</button>
<button type="button" data-visual-setting="shadows" data-visual-value="mid">&#x4E2D;</button>
<button type="button" data-visual-setting="shadows" data-visual-value="high">&#x9AD8;</button>
</div>
</div>
<div class="visual-setting-row">
<div><strong>&#x63CF;&#x753B;</strong><span>&#x88C5;&#x98FE;&#x3068;&#x89E3;&#x50CF;&#x5EA6;&#x306E;&#x8A2D;&#x5B9A;</span></div>
<div class="visual-setting-options" role="group" aria-label="&#x63CF;&#x753B;">
<button type="button" data-visual-setting="details" data-visual-value="low">&#x4F4E;</button>
<button type="button" data-visual-setting="details" data-visual-value="mid">&#x4E2D;</button>
<button type="button" data-visual-setting="details" data-visual-value="high">&#x9AD8;</button>
</div>
</div>
@ -252,164 +257,164 @@
</div>
<div id="logPushOverlay" class="log-push-overlay" aria-live="polite" aria-atomic="false"></div>
<script src="js/version.js?v=39.15.101" defer></script>
<script src="js/event_bus.js?v=39.15.101" defer></script>
<script src="js/registry_base.js?v=39.15.101" defer></script>
<script src="js/disease_registry.js?v=39.15.101" defer></script>
<script src="js/sound_pack.js?v=39.15.101" defer></script>
<script src="js/deterministic_helpers.js?v=39.15.101" defer></script>
<script src="js/item_tool_metadata.js?v=39.15.101" defer></script>
<script src="js/item_tool_definitions.js?v=39.15.101" defer></script>
<script src="js/item_visual_definitions.js?v=39.15.101" defer></script>
<script src="js/item_food_definitions.js?v=39.15.101" defer></script>
<script src="js/item_effect_definitions.js?v=39.15.101" defer></script>
<script src="js/item_registry.js?v=39.15.101" defer></script>
<script src="js/data.js?v=39.15.101" defer></script>
<script src="js/ground_types.js?v=39.15.101" defer></script>
<script src="js/input_mode_manager.js?v=39.15.101" defer></script>
<script src="js/math.js?v=39.15.101" defer></script>
<script src="js/geometry_helpers.js?v=39.15.101" defer></script>
<script src="js/collision_footprint_system.js?v=39.15.101" defer></script>
<script src="js/physics_helpers.js?v=39.15.101" defer></script>
<script src="js/placement_preview_system.js?v=39.15.101" defer></script>
<script src="js/pin_attachment_system.js?v=39.15.101" defer></script>
<script src="js/physics_shape_editor_system.js?v=39.15.101" defer></script>
<script src="js/mechanical_system.js?v=39.15.101" defer></script>
<script src="js/mechanical_shape_bridge.js?v=39.15.101" defer></script>
<script src="js/constraint_system.js?v=39.15.101" defer></script>
<script src="js/physics_world_system.js?v=39.15.101" defer></script>
<script src="js/physics_projection_system.js?v=39.15.101" defer></script>
<script src="js/assets.js?v=39.15.101" defer></script>
<script src="js/audio.js?v=39.15.101" defer></script>
<script src="js/perf_profiler.js?v=39.15.101" defer></script>
<script src="js/render.js?v=39.15.101" defer></script>
<script src="js/sim_core.js?v=39.15.101" defer></script>
<script src="js/tarinai_seed_factory.js?v=39.15.101" defer></script>
<script src="js/structures.js?v=39.15.101" defer></script>
<script src="js/items.js?v=39.15.101" defer></script>
<script src="js/item_type_initializers.js?v=39.15.101" defer></script>
<script src="js/item_lifecycle_support.js?v=39.15.101" defer></script>
<script src="js/item_update_policy.js?v=39.15.101" defer></script>
<script src="js/fire_runtime_system.js?v=39.15.101" defer></script>
<script src="js/robot_cleaner_system.js?v=39.15.101" defer></script>
<script src="js/item_dynamic_tool_system.js?v=39.15.101" defer></script>
<script src="js/item_dynamic_ball_system.js?v=39.15.101" defer></script>
<script src="js/item_dynamic_duplicator_system.js?v=39.15.101" defer></script>
<script src="js/item_dynamic_pin_system.js?v=39.15.101" defer></script>
<script src="js/item_dynamic_zunchi_system.js?v=39.15.101" defer></script>
<script src="js/item_dynamic_system.js?v=39.15.101" defer></script>
<script src="js/item_lifecycle_decay_system.js?v=39.15.101" defer></script>
<script src="js/item_lifecycle_growth_system.js?v=39.15.101" defer></script>
<script src="js/item_lifecycle_step_frame.js?v=39.15.101" defer></script>
<script src="js/item_lifecycle_step_decay.js?v=39.15.101" defer></script>
<script src="js/item_lifecycle_step_dynamic.js?v=39.15.101" defer></script>
<script src="js/item_lifecycle_step_growth.js?v=39.15.101" defer></script>
<script src="js/update_step_pipeline_runner.js?v=39.15.101" defer></script>
<script src="js/item_lifecycle_pipeline.js?v=39.15.101" defer></script>
<script src="js/item_runtime.js?v=39.15.101" defer></script>
<script src="js/structure_lifecycle.js?v=39.15.101" defer></script>
<script src="js/item_render_helpers.js?v=39.15.101" defer></script>
<script src="js/item_render_runtime.js?v=39.15.101" defer></script>
<script src="js/burn_motion_util.js?v=39.15.101" defer></script>
<script src="js/ants.js?v=39.15.101" defer></script>
<script src="js/health.js?v=39.15.101" defer></script>
<script src="js/tarinai.js?v=39.15.101" defer></script>
<script src="js/tarinai_action_spec.js?v=39.15.101" defer></script>
<script src="js/tarinai_behavior_state.js?v=39.15.101" defer></script>
<script src="js/tarinai_identity_social.js?v=39.15.101" defer></script>
<script src="js/tarinai_action_state.js?v=39.15.101" defer></script>
<script src="js/tarinai_disease_nest.js?v=39.15.101" defer></script>
<script src="js/tarinai_item_effects.js?v=39.15.101" defer></script>
<script src="js/tarinai_needs_core.js?v=39.15.101" defer></script>
<script src="js/tarinai_behavior_text.js?v=39.15.101" defer></script>
<script src="js/tarinai_forced_behavior.js?v=39.15.101" defer></script>
<script src="js/tarinai_nest_sleep_system.js?v=39.15.101" defer></script>
<script src="js/tarinai_item_targeting.js?v=39.15.101" defer></script>
<script src="js/tarinai_consumable_behavior.js?v=39.15.101" defer></script>
<script src="js/tarinai_social_action_runtime.js?v=39.15.101" defer></script>
<script src="js/tarinai_building_behavior.js?v=39.15.101" defer></script>
<script src="js/tarinai_action_definitions.js?v=39.15.101" defer></script>
<script src="js/tarinai_needs_items.js?v=39.15.101" defer></script>
<script src="js/tarinai_food_prototype_mixin.js?v=39.15.101" defer></script>
<script src="js/tarinai_need_planner_system.js?v=39.15.101" defer></script>
<script src="js/tarinai_item_interaction_context.js?v=39.15.101" defer></script>
<script src="js/tarinai_food_interaction_system.js?v=39.15.101" defer></script>
<script src="js/tarinai_contact_item_system.js?v=39.15.101" defer></script>
<script src="js/tarinai_item_interaction_system.js?v=39.15.101" defer></script>
<script src="js/tarinai_sunbath_system.js?v=39.15.101" defer></script>
<script src="js/tarinai_cursor_care_system.js?v=39.15.101" defer></script>
<script src="js/tarinai_local_environment_system.js?v=39.15.101" defer></script>
<script src="js/tarinai_social_move_life.js?v=39.15.101" defer></script>
<script src="js/tarinai_update_step_frame.js?v=39.15.101" defer></script>
<script src="js/tarinai_update_step_ai.js?v=39.15.101" defer></script>
<script src="js/tarinai_update_step_environment.js?v=39.15.101" defer></script>
<script src="js/tarinai_update_step_movement.js?v=39.15.101" defer></script>
<script src="js/tarinai_update_step_health.js?v=39.15.101" defer></script>
<script src="js/tarinai_update_pipeline.js?v=39.15.101" defer></script>
<script src="js/tarinai_runtime.js?v=39.15.101" defer></script>
<script src="js/tarinai_render.js?v=39.15.101" defer></script>
<script src="js/world.js?v=39.15.101" defer></script>
<script src="js/world_view.js?v=39.15.101" defer></script>
<script src="js/family_graph.js?v=39.15.101" defer></script>
<script src="js/world_reset_presets.js?v=39.15.101" defer></script>
<script src="js/world_family_social.js?v=39.15.101" defer></script>
<script src="js/impact_core_system.js?v=39.15.101" defer></script>
<script src="js/impact_response_system.js?v=39.15.101" defer></script>
<script src="js/collision_response_system.js?v=39.15.101" defer></script>
<script src="js/world_combat_effects.js?v=39.15.101" defer></script>
<script src="js/world_environment.js?v=39.15.101" defer></script>
<script src="js/world_temperature_system.js?v=39.15.101" defer></script>
<script src="js/world_pathfinding_system.js?v=39.15.101" defer></script>
<script src="js/world_grass_placement_system.js?v=39.15.101" defer></script>
<script src="js/world_spatial_budget.js?v=39.15.101" defer></script>
<script src="js/world_ants_system.js?v=39.15.101" defer></script>
<script src="js/weather_system.js?v=39.15.101" defer></script>
<script src="js/item_update_scheduler.js?v=39.15.101" defer></script>
<script src="js/simulation_runtime_helpers.js?v=39.15.101" defer></script>
<script src="js/tarinai_update_policy.js?v=39.15.101" defer></script>
<script src="js/simulation_environment_system.js?v=39.15.101" defer></script>
<script src="js/simulation_item_ant_system.js?v=39.15.101" defer></script>
<script src="js/simulation_effects_system.js?v=39.15.101" defer></script>
<script src="js/simulation_creature_system.js?v=39.15.101" defer></script>
<script src="js/simulation_maintenance_system.js?v=39.15.101" defer></script>
<script src="js/simulation_ambient_system.js?v=39.15.101" defer></script>
<script src="js/simulation_systems.js?v=39.15.101" defer></script>
<script src="js/system_order.js?v=39.15.101" defer></script>
<script src="js/simulation.js?v=39.15.101" defer></script>
<script src="js/colony_situation_system.js?v=39.15.101" defer></script>
<script src="js/world_update.js?v=39.15.101" defer></script>
<script src="js/world_tool_actions.js?v=39.15.101" defer></script>
<script src="js/world_placement_log.js?v=39.15.101" defer></script>
<script src="js/world_event_effects.js?v=39.15.101" defer></script>
<script src="js/command_dispatcher.js?v=39.15.101" defer></script>
<script src="js/text_catalog.js?v=39.15.101" defer></script>
<script src="js/ui.js?v=39.15.101" defer></script>
<script src="js/ui_log.js?v=39.15.101" defer></script>
<script src="js/ui_helpers.js?v=39.15.101" defer></script>
<script src="js/ui_selected.js?v=39.15.101" defer></script>
<script src="js/save_schema.js?v=39.15.101" defer></script>
<script src="js/snapshot_system.js?v=39.15.101" defer></script>
<script src="js/restore_coordinator.js?v=39.15.101" defer></script>
<script src="js/history_system.js?v=39.15.101" defer></script>
<script src="js/save_codec.js?v=39.15.101" defer></script>
<script src="js/save_storage.js?v=39.15.101" defer></script>
<script src="js/save_system.js?v=39.15.101" defer></script>
<script src="js/ui_tooltips.js?v=39.15.101" defer></script>
<script src="js/ui_layout_dialogs.js?v=39.15.101" defer></script>
<script src="js/ui_ground.js?v=39.15.101" defer></script>
<script src="js/ui_tools.js?v=39.15.101" defer></script>
<script src="js/ui_input_shared.js?v=39.15.101" defer></script>
<script src="js/ui_pointer_action_system.js?v=39.15.101" defer></script>
<script src="js/ui_input_touch.js?v=39.15.101" defer></script>
<script src="js/ui_input_mouse.js?v=39.15.101" defer></script>
<script src="js/ui_bind.js?v=39.15.101" defer></script>
<script src="js/ui_charts.js?v=39.15.101" defer></script>
<script src="js/ui_family_data.js?v=39.15.101" defer></script>
<script src="js/ui_family_async.js?v=39.15.101" defer></script>
<script src="js/ui_family_layout.js?v=39.15.101" defer></script>
<script src="js/ui_family_paths.js?v=39.15.101" defer></script>
<script src="js/ui_family_render.js?v=39.15.101" defer></script>
<script src="js/main.js?v=39.15.101" defer></script>
<script src="js/debug_tools.js?v=39.15.101" defer></script>
<script src="js/version.js?v=39.15.107" defer></script>
<script src="js/event_bus.js?v=39.15.107" defer></script>
<script src="js/registry_base.js?v=39.15.107" defer></script>
<script src="js/disease_registry.js?v=39.15.107" defer></script>
<script src="js/sound_pack.js?v=39.15.107" defer></script>
<script src="js/deterministic_helpers.js?v=39.15.107" defer></script>
<script src="js/item_tool_metadata.js?v=39.15.107" defer></script>
<script src="js/item_tool_definitions.js?v=39.15.107" defer></script>
<script src="js/item_visual_definitions.js?v=39.15.107" defer></script>
<script src="js/item_food_definitions.js?v=39.15.107" defer></script>
<script src="js/item_effect_definitions.js?v=39.15.107" defer></script>
<script src="js/item_registry.js?v=39.15.107" defer></script>
<script src="js/data.js?v=39.15.107" defer></script>
<script src="js/ground_types.js?v=39.15.107" defer></script>
<script src="js/input_mode_manager.js?v=39.15.107" defer></script>
<script src="js/math.js?v=39.15.107" defer></script>
<script src="js/geometry_helpers.js?v=39.15.107" defer></script>
<script src="js/collision_footprint_system.js?v=39.15.107" defer></script>
<script src="js/physics_helpers.js?v=39.15.107" defer></script>
<script src="js/placement_preview_system.js?v=39.15.107" defer></script>
<script src="js/pin_attachment_system.js?v=39.15.107" defer></script>
<script src="js/physics_shape_editor_system.js?v=39.15.107" defer></script>
<script src="js/mechanical_system.js?v=39.15.107" defer></script>
<script src="js/mechanical_shape_bridge.js?v=39.15.107" defer></script>
<script src="js/constraint_system.js?v=39.15.107" defer></script>
<script src="js/physics_world_system.js?v=39.15.107" defer></script>
<script src="js/physics_projection_system.js?v=39.15.107" defer></script>
<script src="js/assets.js?v=39.15.107" defer></script>
<script src="js/audio.js?v=39.15.107" defer></script>
<script src="js/perf_profiler.js?v=39.15.107" defer></script>
<script src="js/render.js?v=39.15.107" defer></script>
<script src="js/sim_core.js?v=39.15.107" defer></script>
<script src="js/tarinai_seed_factory.js?v=39.15.107" defer></script>
<script src="js/structures.js?v=39.15.107" defer></script>
<script src="js/items.js?v=39.15.107" defer></script>
<script src="js/item_type_initializers.js?v=39.15.107" defer></script>
<script src="js/item_lifecycle_support.js?v=39.15.107" defer></script>
<script src="js/item_update_policy.js?v=39.15.107" defer></script>
<script src="js/fire_runtime_system.js?v=39.15.107" defer></script>
<script src="js/robot_cleaner_system.js?v=39.15.107" defer></script>
<script src="js/item_dynamic_tool_system.js?v=39.15.107" defer></script>
<script src="js/item_dynamic_ball_system.js?v=39.15.107" defer></script>
<script src="js/item_dynamic_duplicator_system.js?v=39.15.107" defer></script>
<script src="js/item_dynamic_pin_system.js?v=39.15.107" defer></script>
<script src="js/item_dynamic_zunchi_system.js?v=39.15.107" defer></script>
<script src="js/item_dynamic_system.js?v=39.15.107" defer></script>
<script src="js/item_lifecycle_decay_system.js?v=39.15.107" defer></script>
<script src="js/item_lifecycle_growth_system.js?v=39.15.107" defer></script>
<script src="js/item_lifecycle_step_frame.js?v=39.15.107" defer></script>
<script src="js/item_lifecycle_step_decay.js?v=39.15.107" defer></script>
<script src="js/item_lifecycle_step_dynamic.js?v=39.15.107" defer></script>
<script src="js/item_lifecycle_step_growth.js?v=39.15.107" defer></script>
<script src="js/update_step_pipeline_runner.js?v=39.15.107" defer></script>
<script src="js/item_lifecycle_pipeline.js?v=39.15.107" defer></script>
<script src="js/item_runtime.js?v=39.15.107" defer></script>
<script src="js/structure_lifecycle.js?v=39.15.107" defer></script>
<script src="js/item_render_helpers.js?v=39.15.107" defer></script>
<script src="js/item_render_runtime.js?v=39.15.107" defer></script>
<script src="js/burn_motion_util.js?v=39.15.107" defer></script>
<script src="js/ants.js?v=39.15.107" defer></script>
<script src="js/health.js?v=39.15.107" defer></script>
<script src="js/tarinai.js?v=39.15.107" defer></script>
<script src="js/tarinai_action_spec.js?v=39.15.107" defer></script>
<script src="js/tarinai_behavior_state.js?v=39.15.107" defer></script>
<script src="js/tarinai_identity_social.js?v=39.15.107" defer></script>
<script src="js/tarinai_action_state.js?v=39.15.107" defer></script>
<script src="js/tarinai_disease_nest.js?v=39.15.107" defer></script>
<script src="js/tarinai_item_effects.js?v=39.15.107" defer></script>
<script src="js/tarinai_needs_core.js?v=39.15.107" defer></script>
<script src="js/tarinai_behavior_text.js?v=39.15.107" defer></script>
<script src="js/tarinai_forced_behavior.js?v=39.15.107" defer></script>
<script src="js/tarinai_nest_sleep_system.js?v=39.15.107" defer></script>
<script src="js/tarinai_item_targeting.js?v=39.15.107" defer></script>
<script src="js/tarinai_consumable_behavior.js?v=39.15.107" defer></script>
<script src="js/tarinai_social_action_runtime.js?v=39.15.107" defer></script>
<script src="js/tarinai_building_behavior.js?v=39.15.107" defer></script>
<script src="js/tarinai_action_definitions.js?v=39.15.107" defer></script>
<script src="js/tarinai_needs_items.js?v=39.15.107" defer></script>
<script src="js/tarinai_food_prototype_mixin.js?v=39.15.107" defer></script>
<script src="js/tarinai_need_planner_system.js?v=39.15.107" defer></script>
<script src="js/tarinai_item_interaction_context.js?v=39.15.107" defer></script>
<script src="js/tarinai_food_interaction_system.js?v=39.15.107" defer></script>
<script src="js/tarinai_contact_item_system.js?v=39.15.107" defer></script>
<script src="js/tarinai_item_interaction_system.js?v=39.15.107" defer></script>
<script src="js/tarinai_sunbath_system.js?v=39.15.107" defer></script>
<script src="js/tarinai_cursor_care_system.js?v=39.15.107" defer></script>
<script src="js/tarinai_local_environment_system.js?v=39.15.107" defer></script>
<script src="js/tarinai_social_move_life.js?v=39.15.107" defer></script>
<script src="js/tarinai_update_step_frame.js?v=39.15.107" defer></script>
<script src="js/tarinai_update_step_ai.js?v=39.15.107" defer></script>
<script src="js/tarinai_update_step_environment.js?v=39.15.107" defer></script>
<script src="js/tarinai_update_step_movement.js?v=39.15.107" defer></script>
<script src="js/tarinai_update_step_health.js?v=39.15.107" defer></script>
<script src="js/tarinai_update_pipeline.js?v=39.15.107" defer></script>
<script src="js/tarinai_runtime.js?v=39.15.107" defer></script>
<script src="js/tarinai_render.js?v=39.15.107" defer></script>
<script src="js/world.js?v=39.15.107" defer></script>
<script src="js/world_view.js?v=39.15.107" defer></script>
<script src="js/family_graph.js?v=39.15.107" defer></script>
<script src="js/world_reset_presets.js?v=39.15.107" defer></script>
<script src="js/world_family_social.js?v=39.15.107" defer></script>
<script src="js/impact_core_system.js?v=39.15.107" defer></script>
<script src="js/impact_response_system.js?v=39.15.107" defer></script>
<script src="js/collision_response_system.js?v=39.15.107" defer></script>
<script src="js/world_combat_effects.js?v=39.15.107" defer></script>
<script src="js/world_environment.js?v=39.15.107" defer></script>
<script src="js/world_temperature_system.js?v=39.15.107" defer></script>
<script src="js/world_pathfinding_system.js?v=39.15.107" defer></script>
<script src="js/world_grass_placement_system.js?v=39.15.107" defer></script>
<script src="js/world_spatial_budget.js?v=39.15.107" defer></script>
<script src="js/world_ants_system.js?v=39.15.107" defer></script>
<script src="js/weather_system.js?v=39.15.107" defer></script>
<script src="js/item_update_scheduler.js?v=39.15.107" defer></script>
<script src="js/simulation_runtime_helpers.js?v=39.15.107" defer></script>
<script src="js/tarinai_update_policy.js?v=39.15.107" defer></script>
<script src="js/simulation_environment_system.js?v=39.15.107" defer></script>
<script src="js/simulation_item_ant_system.js?v=39.15.107" defer></script>
<script src="js/simulation_effects_system.js?v=39.15.107" defer></script>
<script src="js/simulation_creature_system.js?v=39.15.107" defer></script>
<script src="js/simulation_maintenance_system.js?v=39.15.107" defer></script>
<script src="js/simulation_ambient_system.js?v=39.15.107" defer></script>
<script src="js/simulation_systems.js?v=39.15.107" defer></script>
<script src="js/system_order.js?v=39.15.107" defer></script>
<script src="js/simulation.js?v=39.15.107" defer></script>
<script src="js/colony_situation_system.js?v=39.15.107" defer></script>
<script src="js/world_update.js?v=39.15.107" defer></script>
<script src="js/world_tool_actions.js?v=39.15.107" defer></script>
<script src="js/world_placement_log.js?v=39.15.107" defer></script>
<script src="js/world_event_effects.js?v=39.15.107" defer></script>
<script src="js/command_dispatcher.js?v=39.15.107" defer></script>
<script src="js/text_catalog.js?v=39.15.107" defer></script>
<script src="js/ui.js?v=39.15.107" defer></script>
<script src="js/ui_log.js?v=39.15.107" defer></script>
<script src="js/ui_helpers.js?v=39.15.107" defer></script>
<script src="js/ui_selected.js?v=39.15.107" defer></script>
<script src="js/save_schema.js?v=39.15.107" defer></script>
<script src="js/snapshot_system.js?v=39.15.107" defer></script>
<script src="js/restore_coordinator.js?v=39.15.107" defer></script>
<script src="js/history_system.js?v=39.15.107" defer></script>
<script src="js/save_codec.js?v=39.15.107" defer></script>
<script src="js/save_storage.js?v=39.15.107" defer></script>
<script src="js/save_system.js?v=39.15.107" defer></script>
<script src="js/ui_tooltips.js?v=39.15.107" defer></script>
<script src="js/ui_layout_dialogs.js?v=39.15.107" defer></script>
<script src="js/ui_ground.js?v=39.15.107" defer></script>
<script src="js/ui_tools.js?v=39.15.107" defer></script>
<script src="js/ui_input_shared.js?v=39.15.107" defer></script>
<script src="js/ui_pointer_action_system.js?v=39.15.107" defer></script>
<script src="js/ui_input_touch.js?v=39.15.107" defer></script>
<script src="js/ui_input_mouse.js?v=39.15.107" defer></script>
<script src="js/ui_bind.js?v=39.15.107" defer></script>
<script src="js/ui_charts.js?v=39.15.107" defer></script>
<script src="js/ui_family_data.js?v=39.15.107" defer></script>
<script src="js/ui_family_async.js?v=39.15.107" defer></script>
<script src="js/ui_family_layout.js?v=39.15.107" defer></script>
<script src="js/ui_family_paths.js?v=39.15.107" defer></script>
<script src="js/ui_family_render.js?v=39.15.107" defer></script>
<script src="js/main.js?v=39.15.107" defer></script>
<script src="js/debug_tools.js?v=39.15.107" defer></script>
</body>
</html>

View file

@ -428,6 +428,7 @@ function updateFirecracker(item, dt, worldRef) {
if (fire) {
fire.vx = 0;
fire.vy = 0;
updateFireContacts(fire, FIRE_CONTACT_INTERVAL, worldRef);
}
pushFireEffect(worldRef, item.x + Math.cos(a) * Math.min(d, 28), item.y + Math.sin(a) * Math.min(d, 28), 14, item);
}

View file

@ -11,6 +11,7 @@ const HEALTH = (() => {
stress: "\u30b9\u30c8\u30ec\u30b9\u904e\u591a",
poke: "\u3064\u3064\u304b\u308c\u3059\u304e",
firecracker: "\u7206\u7af9",
fire: "\u708e\u4e0a",
genkotsu: "\u3052\u3093\u3053\u3064",
shooting: "\u5c04\u6483",
stoneCrush: "\u77f3\u306b\u6f70\u3055\u308c\u305f",
@ -48,6 +49,7 @@ const HEALTH = (() => {
if (/\u5de8\u5927\u85ac|giant/.test(text)) return CAUSES.giantDrug;
if (/\u77ee\u5c0f\u85ac|dwarf/.test(text)) return CAUSES.dwarfDrug;
if (/\u7206\u7af9|firecracker|explosion/.test(text)) return CAUSES.firecracker;
if (/\u708e\u4e0a|\u706b|\u708e|\u71c3|burn|fire/.test(text)) return CAUSES.fire;
if (/\u4ed5\u69d8\u306b\u3088\u308a\u753b\u9762\u5916\u3067\u524a\u9664|\u753b\u9762\u5916\u3067\u524a\u9664|out.*bounds|offscreen/.test(text)) return CAUSES.outOfBounds;
if (/\u885d\u7a81|collision|\u6025\u6fc0\u306a\u901f\u5ea6\u5909\u5316/.test(text)) return CAUSES.collision;
if (/\u3064\u3064\u304b\u308c|\u3064\u3064\u304d|poke/.test(text)) return CAUSES.poke;
@ -117,6 +119,7 @@ const HEALTH = (() => {
return CAUSES.accident;
}
if (direct === CAUSES.fire || recent === CAUSES.fire || t.lastMajorDamageCause === CAUSES.fire) return CAUSES.fire;
if (recentMajor && t.lastMajorDamageCause === CAUSES.collision && !(t.energy <= 20 || t.mood <= 16 || t.stress >= 124 || t.hunger >= 112)) return CAUSES.collision;
if (recentMajor && (!direct || direct === t.lastMajorDamageCause || t.energy <= 18 || t.mood <= 16 || t.stress >= 124 || t.hunger >= 112)) {
return weakenedDeathReasonFor(t.lastMajorDamageCause);
@ -151,7 +154,7 @@ const HEALTH = (() => {
const cause = dominantDeathCause(t, text, opts) || CAUSES.accident;
if (cause.includes(WEAKENED_SUFFIX)) return cause;
if (opts.fromDamage) return cause;
const canWeaken = cause && !cause.endsWith("\u75c5") && cause !== CAUSES.hunger && cause !== CAUSES.stress && cause !== CAUSES.lifespan && cause !== CAUSES.outOfBounds && cause !== CAUSES.collision && cause !== CAUSES.genkotsu && cause !== CAUSES.weakness && cause !== CAUSES.freeze && cause !== CAUSES.heatstroke;
const canWeaken = cause && !cause.endsWith("\u75c5") && cause !== CAUSES.hunger && cause !== CAUSES.stress && cause !== CAUSES.lifespan && cause !== CAUSES.outOfBounds && cause !== CAUSES.collision && cause !== CAUSES.genkotsu && cause !== CAUSES.fire && cause !== CAUSES.weakness && cause !== CAUSES.freeze && cause !== CAUSES.heatstroke;
if (canWeaken && (opts.weakened || (hasRecentMajorDamage(t) && cause === t.lastMajorDamageCause))) return weakenedDeathReasonFor(cause);
return cause;
}

View file

@ -26,6 +26,12 @@
changed = true;
item.foodServingsRemaining = Math.max(0, before - lost);
item.amount = item.foodServingsRemaining;
const resized = (typeof updateServingFoodVisualSize === "function" ? updateServingFoodVisualSize(item) : false);
if (resized) {
worldRef?.markItemBucketsDirty?.("food-passive-resize");
worldRef?.markSpatialDirty?.("food-passive-resize");
if (worldRef) worldRef.drawListDirty = true;
}
worldRef?.markTerrainDirty?.("food-passive-decay");
worldRef?.emit?.("item:decayed", { item, type: item.type, amount: lost, passive: true });
if (item.foodServingsRemaining <= 0.015) item.amount = 0;

View file

@ -13,7 +13,7 @@ draw(ctx, t, lighting = null) {
}
const servingRatio = isServingFoodType(this.type) ? ((this.foodServingsRemaining ?? this.amount) / Math.max(1, this.foodServingsMax || foodServingsForSize(this.toolSize || "medium"))) : null;
const forceOpaque = this.type === "slave_chain" || this.type === "golden_crown";
const visibleAlpha = forceOpaque ? 1 : (isServingFoodType(this.type) ? clamp(servingRatio ?? 1, 0.24, 1) : clamp(this.amount / 40, 0.24, 1));
const visibleAlpha = forceOpaque ? 1 : (isServingFoodType(this.type) ? clamp(0.72 + (servingRatio ?? 1) * 0.28, 0.72, 1) : clamp(this.amount / 40, 0.24, 1));
const dropT = this.dropMax > 0 ? clamp(this.dropTimer / this.dropMax, 0, 1) : 0;
const dropFallHeight = this.type === "genkotsu" ? Math.max(360, this.r * 5.8) : 170;
const dropY = dropT > 0 ? -dropFallHeight * dropT : 0;

View file

@ -13,7 +13,7 @@ const TOOL_DEFINITIONS = Object.freeze({
poke: { id: "poke", label: "\u3064\u3064\u304f", placeable: false, scalable: false, iconText: "\u{1F448}", tooltip: "\u305f\u308a\u306a\u3044\u3092\u3064\u3064\u304f\u3002\u30dc\u30fc\u30eb\u3082\u3064\u3064\u3044\u3066\u8ee2\u304c\u305b\u308b\u3002" },
pinch: { id: "pinch", label: "\u3064\u307e\u3080", placeable: false, scalable: false, iconText: "\u{1F90F}", tooltip: "\u305f\u308a\u306a\u3044\u3084\u4e00\u90e8\u306e\u7f6e\u304d\u3082\u306e\u3092\u79fb\u52d5\u3055\u305b\u308b\u3002" },
new: { id: "new", label: "\u8ffd\u52a0", placeable: false, scalable: false, icon: "assets/ui/tool_new.webp", tooltip: "\u753b\u9762\u5916\u304b\u3089\u65b0\u3057\u3044\u305f\u308a\u306a\u3044\u3092\u547c\u3076\u3002" },
water_hose: { id: "water_hose", label: "\u6d17\u6d44", placeable: false, scalable: false, iconText: "\u{1F6BF}", tooltip: "\u30c9\u30e9\u30c3\u30b0\u3067\u6c5a\u308c\u3092\u304d\u308c\u3044\u306b\u3059\u308b\u3002" },
water_hose: { id: "water_hose", label: "\u6d17\u6d44", placeable: false, scalable: false, iconText: "\u{1F6BF}", tooltip: "\u30c9\u30e9\u30c3\u30b0\u3067\u6c5a\u308c\u3084\u706b\u3092\u304d\u308c\u3044\u306b\u3059\u308b\u3002" },
rope: { id: "rope", label: "\u30d2\u30e2", placeable: false, scalable: false, iconText: "\u2570", tooltip: "2\u3064\u306e\u5bfe\u8c61\u3092\u9806\u306b\u30af\u30ea\u30c3\u30af\u3057\u3066\u3075\u306b\u3083\u3075\u306b\u3083\u3057\u305f\u30d2\u30e2\u3067\u7e4b\u3050\u3002" },
rod: { id: "rod", label: "\u68d2", placeable: false, scalable: false, iconText: "\u2501", tooltip: "2\u3064\u306e\u5bfe\u8c61\u3092\u9806\u306b\u30af\u30ea\u30c3\u30af\u3057\u3066\u3001\u66f2\u304c\u3089\u306a\u3044\u68d2\u3067\u63a5\u7d9a\u3059\u308b\u3002" },
shoot: { id: "shoot", label: "\u5c04\u6483", placeable: false, scalable: false, icon: "assets/ui/tool_shoot.png", tooltip: "\u9283\u3067\u653b\u6483\u3059\u308b\u3002" },

View file

@ -205,6 +205,7 @@ function initializeItemTypeState(item, type, x, y) {
item.foodServingsRemaining = item.foodServingsMax;
item.passiveFoodDecayTimer = rand(0, passiveFoodDecayInterval());
item.amount = item.foodServingsRemaining;
(typeof updateServingFoodVisualSize === "function" ? updateServingFoodVisualSize(item) : false);
}
return item;
}

View file

@ -14,6 +14,29 @@ function foodServingsForSize(size = "medium") {
function isServingFoodType(type = "") {
return FOOD_SERVING_TYPES.has(type);
}
function foodServingSizeForRemaining(remaining = FOOD_SERVINGS_BY_SIZE.medium) {
const n = Math.max(0, Number(remaining) || 0);
if (n <= FOOD_SERVINGS_BY_SIZE.small + 0.05) return "small";
if (n <= FOOD_SERVINGS_BY_SIZE.medium + 0.05) return "medium";
return "large";
}
function foodServingVisualScaleFor(item) {
if (!item || !isServingFoodType(item.type)) return 1;
const remaining = Math.max(0, Number(item.foodServingsRemaining ?? item.amount ?? FOOD_SERVINGS_BY_SIZE.medium) || 0);
const ratio = remaining / FOOD_SERVINGS_BY_SIZE.medium;
return clamp(Math.sqrt(Math.max(0.04, ratio)), 0.72, 1.35);
}
function updateServingFoodVisualSize(item) {
if (!item || !isServingFoodType(item.type)) return false;
const baseRadius = itemRadiusFor(item.type, 12);
const scale = foodServingVisualScaleFor(item);
const nextR = Math.max(5, baseRadius * scale);
const changed = Math.abs((Number(item.r) || 0) - nextR) > 0.05;
item.r = nextR;
item.foodServingScale = scale;
item.foodServingVisualSize = foodServingSizeForRemaining(item.foodServingsRemaining ?? item.amount);
return changed;
}
const PASSIVE_FOOD_DECAY_PER_SECOND = 0.004;
const GRASS_STAGE_COUNT = 5;
@ -600,6 +623,9 @@ function drawToolItemPreview(ctx, type = "", opts = {}) {
if (typeof window !== "undefined") {
window.isConsumableSpriteType = isConsumableSpriteType;
window.foodServingSizeForRemaining = foodServingSizeForRemaining;
window.foodServingVisualScaleFor = foodServingVisualScaleFor;
window.updateServingFoodVisualSize = updateServingFoodVisualSize;
window.drawConsumableFieldSprite = drawConsumableFieldSprite;
window.drawStoredConsumableSprite = drawStoredConsumableSprite;
window.drawToolItemPreview = drawToolItemPreview;

View file

@ -19,11 +19,11 @@
let lastQualityCheckAt = 0;
const storageKey = "tarinai_visual_settings_v1";
const allowed = {
effects: new Set(["off", "on"]),
shadows: new Set(["off", "on"]),
details: new Set(["low", "high"]),
effects: new Set(["off", "low", "mid", "high"]),
shadows: new Set(["off", "low", "mid", "high"]),
details: new Set(["low", "mid", "high"]),
};
const defaults = Object.freeze({ effects: "on", shadows: "on", details: "high" });
const defaults = Object.freeze({ effects: "high", shadows: "high", details: "high" });
let visualSettings = loadVisualSettings();
let resizeRequested = false;
@ -83,11 +83,12 @@
const raw = String(value || "");
if (key === "effects" || key === "shadows") {
if (raw === "off") return "off";
if (raw === "on" || raw === "low" || raw === "mid" || raw === "high") return "on";
if (raw === "on") return "high";
if (raw === "low" || raw === "mid" || raw === "high") return raw;
}
if (key === "details") {
if (raw === "low") return "low";
if (raw === "high" || raw === "mid") return "high";
if (raw === "mid" || raw === "high") return raw;
}
return defaults[key];
}
@ -119,6 +120,7 @@
function dprScaleForDetails(details) {
if (details === "low") return 0.72;
if (details === "mid") return 0.86;
return 1;
}
@ -182,7 +184,7 @@
function renderQualityTier() { return visualSettings.details; }
function shadowQualityTier() { return visualSettings.shadows; }
function effectsQualityTier() { return visualSettings.effects; }
function simulationQualityTier() { return visualSettings.details; }
function simulationQualityTier() { return "high"; }
function dprScaleValue() { return dprScaleForDetails(visualSettings.details); }
function consumeResizeRequest() {
const v = resizeRequested;

View file

@ -80,6 +80,12 @@
const sizeName = opts.toolSize ?? toolSizeName(worldRef, type);
const r = (opts.r ?? itemRadius(type, 12)) * scale;
const item = { type, x: num(x), y: num(y), r, toolSize: sizeName, foodServingScale: scale, dead: false, isPlacementPreview: true };
if (isServingFoodType(type)) {
item.foodServingsMax = foodServingsForSize(sizeName);
item.foodServingsRemaining = item.foodServingsMax;
item.amount = item.foodServingsRemaining;
(typeof updateServingFoodVisualSize === "function" ? updateServingFoodVisualSize(item) : false);
}
if (isRotatable(type)) item.angle = opts.angle ?? (worldRef?.toolAngleFor ? worldRef.toolAngleFor(type) : defaultAngle(type));
if (type === "reciprocator") {
const axis = item.angle ?? defaultAngle(type);

View file

@ -426,6 +426,16 @@
if (SERVING_FOOD_TYPE_IDS.has(typeId)) return false;
return true;
}
function defaultStructureHp10(typeId) {
if (ITEM_TYPE_IDS[typeId] === "grass_bed") return 1000;
if (ITEM_TYPE_IDS[typeId] === "plushie") return 10;
return 10;
}
function defaultNeedsFromVitals(hunger = 0, energy = 0, sleepPressure = 0) {
const food = Math.max(0, Math.min(100, Math.round(Number(hunger) || 0)));
const sleep = Math.max(0, Math.min(100, Math.round(Math.max(Number(sleepPressure) || 0, 100 - (Number(energy) || 0)))));
return [food, sleep, 0, 0, 0, 0];
}
function writeRelationships(writer, rows = []) {
const list = Array.isArray(rows) ? rows : [];
writer.u(list.length);
@ -523,19 +533,17 @@
const defaultPersonality = seedPersonalityArray(birthSeed, flags);
const defaultTimers = [0, 0, 0, 0, 0, 0, 0, 0, 100];
let bodyMask = 0;
if (arrayHasValues(row[9], [0, 0, 0, 0, 0, 0])) bodyMask |= 1;
if (arrayHasValues(row[10], defaultPersonality)) bodyMask |= 2;
if ((row[11] || []).length) bodyMask |= 4;
if ((row[12] || []).length) bodyMask |= 8;
if (arrayHasValues(row[13], defaultTimers)) bodyMask |= 16;
if (arrayHasValues(row[14], [0, 0, 0])) bodyMask |= 32;
writeBitFields(writer, [bodyMask], [6]);
if (bodyMask & 1) writeFixedPacked(writer, row[9], 6, 6, 2);
if (bodyMask & 2) writeFixedDiffPacked(writer, defaultPersonality, row[10], 4, 8);
if (bodyMask & 4) writeIndexList(writer, row[11]);
if (bodyMask & 8) writeRelationships(writer, row[12]);
if (bodyMask & 16) writeTimers(writer, row[13]);
if (bodyMask & 32) writeModes(writer, row[14]);
if (arrayHasValues(row[10], defaultPersonality)) bodyMask |= 1;
if ((row[11] || []).length) bodyMask |= 2;
if ((row[12] || []).length) bodyMask |= 4;
if (arrayHasValues(row[13], defaultTimers)) bodyMask |= 8;
if (arrayHasValues(row[14], [0, 0, 0])) bodyMask |= 16;
writeBitFields(writer, [bodyMask], [5]);
if (bodyMask & 1) writeFixedDiffPacked(writer, defaultPersonality, row[10], 4, 8);
if (bodyMask & 2) writeIndexList(writer, row[11]);
if (bodyMask & 4) writeRelationships(writer, row[12]);
if (bodyMask & 8) writeTimers(writer, row[13]);
if (bodyMask & 16) writeModes(writer, row[14]);
let tailMask = 0;
if (pinIdx >= 0) tailMask |= 1;
if (nestIdx >= 0) tailMask |= 2;
@ -558,13 +566,13 @@
const generation = (coordState?.generation || 1) + reader.s();
if (coordState) { coordState.x = x; coordState.y = y; coordState.age = age; coordState.generation = generation; }
const [hunger, energy, sleepPressure] = readBitFields(reader, [8, 8, 8]);
const [bodyMask] = readBitFields(reader, [6]);
const needs = (bodyMask & 1) ? readFixedPacked(reader, 6, 6, 2) : [0, 0, 0, 0, 0, 0];
const current = (bodyMask & 2) ? readFixedDiffPacked(reader, seedPersonalityArray(birthSeed, flags), 4, 8) : seedPersonalityArray(birthSeed, flags);
const parents = (bodyMask & 4) ? readIndexList(reader) : [];
const relationships = (bodyMask & 8) ? readRelationships(reader) : [];
const timers = (bodyMask & 16) ? readTimers(reader) : [0, 0, 0, 0, 0, 0, 0, 0, 100];
const modes = (bodyMask & 32) ? readModes(reader) : [0, 0, 0];
const [bodyMask] = readBitFields(reader, [5]);
const needs = defaultNeedsFromVitals(hunger, energy, sleepPressure);
const current = (bodyMask & 1) ? readFixedDiffPacked(reader, seedPersonalityArray(birthSeed, flags), 4, 8) : seedPersonalityArray(birthSeed, flags);
const parents = (bodyMask & 2) ? readIndexList(reader) : [];
const relationships = (bodyMask & 4) ? readRelationships(reader) : [];
const timers = (bodyMask & 8) ? readTimers(reader) : [0, 0, 0, 0, 0, 0, 0, 0, 100];
const modes = (bodyMask & 16) ? readModes(reader) : [0, 0, 0];
const [tailMask] = readBitFields(reader, [5]);
const pinIdx = (tailMask & 1) ? reader.u() : -1;
const nestIdx = (tailMask & 2) ? reader.u() : -1;
@ -576,31 +584,41 @@
function writeItemExtra(writer, typeId, extra = []) {
if (STRUCTURE_TYPE_IDS.has(typeId)) {
writer.s(extra[0] || 0);
writer.s(extra[1] || 0);
const defaultHp = defaultStructureHp10(typeId);
const hp = toSafeInt(extra[0] || 0);
const maxHp = toSafeInt(extra[1] || 0);
let mask = 0;
if ((extra[2] ?? -1) >= 0) mask |= 1;
if ((extra[3] ?? -1) >= 0) mask |= 2;
if (extra[4]) mask |= 4;
if ((extra[5] ?? -1) >= 0) mask |= 8;
if (valuesDiffer(extra[6], 0)) mask |= 16;
writeBitFields(writer, [mask], [5]);
if (mask & 1) writer.u(extra[2]);
if (mask & 2) writer.u(extra[3]);
if (mask & 8) writer.u(extra[5]);
if (mask & 16) writer.s(extra[6] || 0);
if (valuesDiffer(hp, defaultHp)) mask |= 1;
if (valuesDiffer(maxHp, defaultHp)) mask |= 2;
if ((extra[2] ?? -1) >= 0) mask |= 4;
if ((extra[3] ?? -1) >= 0) mask |= 8;
if (extra[4]) mask |= 16;
if ((extra[5] ?? -1) >= 0) mask |= 32;
if (valuesDiffer(extra[6], 0)) mask |= 64;
writeBitFields(writer, [mask], [7]);
if (mask & 1) writer.s(hp);
if (mask & 2) writer.s(maxHp);
if (mask & 4) writer.u(extra[2]);
if (mask & 8) writer.u(extra[3]);
if (mask & 32) writer.u(extra[5]);
if (mask & 64) writer.s(extra[6] || 0);
} else if (typeId === 0) { // grass
writer.s(extra[0] || 0); writer.s(extra[1] || 0);
writer.s(extra[0] || 0);
let mask = 0;
if (valuesDiffer(extra[2], 0)) mask |= 1;
if (extra[3]) mask |= 2;
writeBitFields(writer, [mask], [2]);
if (mask & 1) writer.s(extra[2] || 0);
if (valuesDiffer(extra[1], 100)) mask |= 1;
if (valuesDiffer(extra[2], 0)) mask |= 2;
if (extra[3]) mask |= 4;
writeBitFields(writer, [mask], [3]);
if (mask & 1) writer.s(extra[1] || 0);
if (mask & 2) writer.s(extra[2] || 0);
} else if (typeId === 1) { // zunchi
writeBitFields(writer, [extra[0] || 0], [2]);
writer.s(extra[1] || 0);
if (valuesDiffer(extra[2], 0)) { writer.u(1); writer.s(extra[2] || 0); }
else writer.u(0);
let mask = 0;
if (valuesDiffer(extra[1], 100)) mask |= 1;
if (valuesDiffer(extra[2], 0)) mask |= 2;
writeBitFields(writer, [mask], [2]);
if (mask & 1) writer.s(extra[1] || 0);
if (mask & 2) writer.s(extra[2] || 0);
} else if (typeId === 22) { // signboard
writeSaveString(writer, extra[0] || "");
} else if (typeId === 6) { // duplicator
@ -643,29 +661,29 @@
}
function readItemExtra(reader, typeId) {
if (STRUCTURE_TYPE_IDS.has(typeId)) {
const hp = reader.s();
const maxHp = reader.s();
const [mask] = readBitFields(reader, [5]);
const defaultHp = defaultStructureHp10(typeId);
const [mask] = readBitFields(reader, [7]);
const hp = (mask & 1) ? reader.s() : defaultHp;
const maxHp = (mask & 2) ? reader.s() : defaultHp;
return [
hp,
maxHp,
(mask & 1) ? reader.u() : -1,
(mask & 2) ? reader.u() : -1,
(mask & 4) ? 1 : 0,
(mask & 4) ? reader.u() : -1,
(mask & 8) ? reader.u() : -1,
(mask & 16) ? reader.s() : 0,
(mask & 16) ? 1 : 0,
(mask & 32) ? reader.u() : -1,
(mask & 64) ? reader.s() : 0,
];
}
if (typeId === 0) {
const growth = reader.s();
const health = reader.s();
const [mask] = readBitFields(reader, [2]);
return [growth, health, (mask & 1) ? reader.s() : 0, (mask & 2) ? 1 : 0];
const [mask] = readBitFields(reader, [3]);
return [growth, (mask & 1) ? reader.s() : 100, (mask & 2) ? reader.s() : 0, (mask & 4) ? 1 : 0];
}
if (typeId === 1) {
const [stage] = readBitFields(reader, [2]);
const freshness = reader.s();
return [stage, freshness, reader.u() ? reader.s() : 0];
const [mask] = readBitFields(reader, [2]);
return [stage, (mask & 1) ? reader.s() : 100, (mask & 2) ? reader.s() : 0];
}
if (typeId === 22) return [readSaveString(reader)];
if (typeId === 6) return [reader.s()];

View file

@ -4,8 +4,8 @@
// Owns stable save identifiers shared by snapshot_system.js and save_codec.js.
// Current-format save identifiers only.
(function (global) {
const SNAPSHOT_VERSION = 17;
const BINARY_SCHEMA_VERSION = 22;
const SNAPSHOT_VERSION = 18;
const BINARY_SCHEMA_VERSION = 23;
const FIELD_IDS = Object.freeze(["garden", "cage", "park"]);
const GROUND_IDS = Object.freeze(["soil", "dirt", "concrete", "blanket", "foot_massage", "ice", "laboratory"]);

View file

@ -369,7 +369,7 @@
item.angle = typeof global.normalizedItemAngle === "function" ? global.normalizedItemAngle(u(extra[0], 1000, fallback), fallback) : u(extra[0], 1000, fallback);
if (type === "gate_fence") item.gateOpen = !!extra[1];
} else if (typeof global.isServingFoodType === "function" && global.isServingFoodType(type)) {
item.foodServingsRemaining = u(extra[0], 10, item.amount || 1); item.foodServingsMax = u(extra[1], 10, item.foodServingsRemaining || item.amount || 1); item.amount = item.foodServingsRemaining;
item.foodServingsRemaining = u(extra[0], 10, item.amount || 1); item.foodServingsMax = u(extra[1], 10, item.foodServingsRemaining || item.amount || 1); item.amount = item.foodServingsRemaining; (typeof updateServingFoodVisualSize === "function" ? updateServingFoodVisualSize(item) : false);
}
}
function createSnapshot(worldRef = global.world) {
@ -482,6 +482,9 @@
t.relationships = expandRelationships(t.__savedRelationships || [], worldRef.tarinai, worldRef);
delete t.__savedPinIdx; delete t.__savedNestIdx; delete t.__savedRelationships; delete t.__savedStateInfo;
}
if (typeof global.updateNeeds === "function") {
for (const t of worldRef.tarinai) global.updateNeeds(t, worldRef, 0);
}
worldRef.liveIdNext = Math.max(1, worldRef.tarinai.length + 1);
worldRef.liveIdSerial = Math.max(1, worldRef.tarinai.length + 1);
worldRef.birthSerial = Math.max(Number(worldRef.birthSerial) || 0, ...worldRef.tarinai.map(t => Number(t.birthSerial) || 0));

View file

@ -115,6 +115,14 @@
target.singleUsePending = true;
target.singleUseConsumedById = this.id || target.singleUseConsumedById || "";
}
if (target && target.type !== "nest_box") {
this._sleepExternalMotionUntil = 0;
this._sleepExternalWakeSuppressUntil = Math.max(Number(this._sleepExternalWakeSuppressUntil || 0) || 0, now + 2.4);
this.vx = 0;
this.vy = 0;
this.impulseVx = 0;
this.impulseVy = 0;
}
const text = reason || (target?.type === "nest_box" ? "\u5de3\u7bb1\u306e\u4e2d\u3067\u7720\u3063\u3066\u3044\u308b" : "\u7720\u3063\u3066\u3044\u308b");
setBehaviorText(this, { need: "sleep", actionId: target ? "sleep_in_bed" : "sleep_anywhere", actionLabel: "\u7720\u3063\u3066\u3044\u308b", reasonText: text, target, phase: "perform", source: "behavior" });
return this.setActionState("sleep", { target, reason: text, sleeping: true });
@ -138,9 +146,9 @@
const sleptFor = Number.isFinite(started) ? Math.max(0, now - started) : 0;
const wasActuallySleeping = Boolean((this.state === "sleep" || this.sleeping || session) && session?.consumesGrassBedOnWake);
// Reserve-on-use must not consume the simple bed. It is consumed only by
// a real wake after sleep has started; pre-sleep wake flags just release
// the reservation and leave the bed intact.
if (!wasActuallySleeping || sleptFor < 0.75) {
// a real wake after sleep has had time to settle; immediate collision or
// movement wakeups just release the reservation and leave the bed intact.
if (!wasActuallySleeping || sleptFor < 2) {
if (bed.singleUseConsumedById === this.id) bed.singleUseConsumedById = "";
if (bed.singleUsePending && (!bed.singleUseConsumedById || bed.singleUseConsumedById === this.id)) bed.singleUsePending = false;
this.pendingGrassBedWakeId = "";

View file

@ -518,8 +518,13 @@
if (typeof this.enterPanic === "function") this.enterPanic({ threat: source, target: null, reason: text, fear: 2.2, wake: true, cause: "burning", surpriseTimer: 0.65, awakeLockTimer: 0.8 });
else this.setActionState?.("panic", { target: null, reason: text, wake: true });
this.world?.effects?.push(new Effect("ring", this.x, this.y, { size: Math.max(26, this.radius * 1.35), life: 0.26, color: "rgba(255,110,36,0.58)" }));
const now = Number(this.world?.time || 0);
if (force || !already || now >= Number(this.nextBurnVoiceAt || -999)) {
this.nextBurnVoiceAt = now + 2.6;
this.bubble?.("\u3042\u3061\u3085\u3044\uff01", 1.35, "rgba(214,82,38,0.90)", { force: true });
}
this.world?.emit?.("fire:ignite", { tarinai: this, source });
this.recordChangeCause?.("\u708e\u4e0a", "\u708e");
this.recordChangeCause?.("\u708e\u4e0a", "\u708e\u4e0a");
return true;
},
@ -548,7 +553,7 @@
this.sleeping = false;
this.eatTimer = 0;
if (typeof applyNeedShock === "function") applyNeedShock(this, { health: dt * 6.2, safety: dt * 3.0 });
if (typeof this.damage === "function") this.damage(dt * 3.8, "\u708e");
if (typeof this.damage === "function") this.damage(dt * 3.8, "\u708e\u4e0a");
else this.energy = clamp((this.energy || 0) - dt * 0.12, 0, typeof tarinaiMaxEnergy === "function" ? tarinaiMaxEnergy(this) : (Number(this.maxEnergy) || 100));
this.hurtTimer = Math.max(this.hurtTimer || 0, 0.22);
const water = globalThis.TarinaiItemDynamicToolSystem?.contactWaterDrop?.(this.world, this.x, this.y, Math.max(this.radius || 18, 16));

View file

@ -30,6 +30,7 @@
}
it.foodServingsRemaining = clamp(it.foodServingsRemaining, 0, it.foodServingsMax);
it.amount = it.foodServingsRemaining;
(typeof updateServingFoodVisualSize === "function" ? updateServingFoodVisualSize(it) : false);
},
consumeFoodServing(it, nutrition) {
if (it?.type === "duplicator") {
@ -44,6 +45,12 @@
const value = Number.isFinite(nutrition) ? nutrition : (window.TarinaiItemRegistry.food?.nutrition?.(it.type, 0) ?? 0);
it.foodServingsRemaining = Math.max(0, (it.foodServingsRemaining || 0) - 1);
it.amount = it.foodServingsRemaining;
const resized = (typeof updateServingFoodVisualSize === "function" ? updateServingFoodVisualSize(it) : false);
if (resized) {
this.world?.markItemBucketsDirty?.("food-serving-resize");
this.world?.markSpatialDirty?.("food-serving-resize");
this.world && (this.world.drawListDirty = true);
}
this.world?.emit?.("item:eaten", { tarinai: this, item: it, type: it.type, nutrition: value });
return value;
},

View file

@ -554,12 +554,17 @@ function shouldWakeFromSleep(tarinai, world, needs) {
const session = tarinai.sleepSession || { startedAt: now, minDuration: 8, targetEnergy: 74, maxDuration: 28 };
const started = Number(session.startedAt);
const elapsed = Math.max(0, now - (Number.isFinite(started) ? started : now));
const minSleepDuration = Math.max(2, Number(session.minDuration) || 8);
// 明確な妨害だけは最低睡眠時間より優先して起床させる。
if ((tarinai.hurtTimer || 0) > 0.06 || (tarinai.pokeFlashTimer || 0) > 0.08) return true;
if (findNearbyDanger(world, tarinai, 160)) return true;
// 空腹・睡眠欲の回復・体力回復などの通常要因では、最低2時間は寝続ける。
if (elapsed < minSleepDuration) return false;
const foodNeed = Number(needs?.food ?? tarinai.needRaw?.food ?? tarinai.needs?.food ?? 0) || 0;
if ((tarinai.hunger || 0) >= 84 || foodNeed >= needThreshold("food", "start")) return true;
// \u81ea\u7136\u306a\u7761\u7720\u306f\u77ed\u3044\u5b89\u5168\u30ed\u30c3\u30af\u5f8c\u3001\u5341\u5206\u306b\u56de\u5fa9\u3057\u305f\u3089\u8d77\u304d\u308b\u3002
if (elapsed < (Number(session.minDuration) || 8)) return false;
const sleepNeed = Number(needs?.sleep ?? tarinai.needRaw?.sleep ?? tarinai.needs?.sleep ?? 0) || 0;
if (sleepNeed <= ((typeof needThreshold === "function" ? needThreshold("sleep", "continue") : 34) + 4)) return true;
if ((tarinai.energy || 0) >= (Number(session.targetEnergy) || 74)) return true;
@ -567,6 +572,7 @@ function shouldWakeFromSleep(tarinai, world, needs) {
return false;
}
function protectSleepSession(tarinai, world, needs) {
if (!(tarinai?.state === "sleep" || tarinai?.sleeping)) return false;
tarinai.sleeping = true;

View file

@ -61,7 +61,7 @@
const near = typeof worldRef.nearbyTarinai === "function" ? worldRef.nearbyTarinai(bed.x, bed.y, radius) : (worldRef.tarinai || []);
for (const t of near || []) {
if (!t || t.dead || isTarinaiHiddenInNestBox(worldRef, t)) continue;
if (t.state === "sleep" || t.state === "seek_bed" || t.target === bed) n += 1;
if (t.target === bed && (t.state === "sleep" || t.state === "seek_bed" || t.sleeping)) n += 1;
}
return n;
}
@ -81,7 +81,7 @@
if (sleepTargetBlocked(worldRef, tarinai, item)) return false;
const capacity = item.type === "nest_box" ? nestBoxCapacity(worldRef, item) : 1;
const occupancy = bedOccupancy(worldRef, item);
return occupancy < capacity || (tarinai.insideNestBoxId && tarinai.insideNestBoxId === item.id);
return occupancy < capacity || tarinai.target === item || (tarinai.insideNestBoxId && tarinai.insideNestBoxId === item.id);
}
function findStructure(worldRef, tarinai, opts = {}) {
@ -135,12 +135,12 @@
return findOwnedStructure(worldRef, tarinai, "grass_bed", maxDist)
|| nearestSleepPlaceOfType(worldRef, tarinai, "nest_box", maxDist)
|| nearestSleepPlaceOfType(worldRef, tarinai, "bed", maxDist)
|| nearestSleepPlaceOfType(worldRef, tarinai, "grass_bed", maxDist)
|| null;
}
function findNearestSleepPlace(worldRef, tarinai, maxDist = 520) {
return findPrioritySleepPlaceBeforeGrassBedBuild(worldRef, tarinai, maxDist)
|| findUnownedStructure(worldRef, tarinai, "grass_bed", maxDist)
|| null;
}

View file

@ -581,11 +581,13 @@
const hpActive = (this.hpBarTimer || 0) > 0;
const stressActive = (this.stressBarTimer || 0) > 0;
if ((hpActive || stressActive) && (showDecor || this.world.selected === this || this.hurtTimer > 0.04)) {
if (hpActive || stressActive) {
const bw = Math.max(28, this.radius * 2.35);
const bh = 5;
const x = this.x - bw / 2;
const baseY = this.y - drawH * 0.55 - 16;
const stressY = hpActive ? baseY + 7 : baseY;
const hpY = stressActive ? baseY - 7 : baseY;
const drawMeter = (y, pct, fill, alpha) => {
ctx.save();
ctx.globalAlpha = alpha;
@ -607,11 +609,11 @@
};
if (stressActive) {
const pct = clamp((this.stress || 0) / 130, 0, 1);
drawMeter(baseY, pct, pct > 0.72 ? "rgba(178,74,110,0.92)" : "rgba(212,126,84,0.90)", clamp((this.stressBarTimer || 0) / 0.7, 0.35, 1));
drawMeter(stressY, pct, pct > 0.72 ? "rgba(178,74,110,0.92)" : "rgba(212,126,84,0.90)", clamp((this.stressBarTimer || 0) / 0.7, 0.35, 1));
}
if (hpActive) {
const pct = typeof tarinaiEnergyRatio === "function" ? tarinaiEnergyRatio(this) : clamp(this.energy / Math.max(1, this.maxEnergy || 100), 0, 1);
drawMeter(baseY - (stressActive ? 11 : 0), pct, pct > 0.45 ? "rgba(118,190,80,0.88)" : (pct > 0.20 ? "rgba(232,172,68,0.90)" : "rgba(218,82,70,0.92)"), clamp((this.hpBarTimer || 0) / 0.7, 0.35, 1));
drawMeter(hpY, pct, pct > 0.45 ? "rgba(118,190,80,0.88)" : (pct > 0.20 ? "rgba(232,172,68,0.90)" : "rgba(218,82,70,0.92)"), clamp((this.hpBarTimer || 0) / 0.7, 0.35, 1));
}
}
},

View file

@ -144,7 +144,7 @@ TEXT_CATALOG.targetLabel = function targetLabelFromCatalog(target) {
TEXT_CATALOG.reasonLabel = function reasonLabelFromCatalog(t) {
if (!t) return "\u306a\u3057";
if ((t.pinchThoughtTimer || 0) > 0) return "\u3064\u307e\u307e\u308c\u3066\u3044\u308b";
if ((t.pinchThoughtTimer || 0) > 0 && (t.playerHeld || t._heldByPlayer)) return "\u3064\u307e\u307e\u308c\u3066\u3044\u308b";
if (t.lastPanicCause === "tarinai_champion" && (t.state === "panic" || (t.fearTimer || 0) > 0.04)) return "\u305F\u308A\u306A\u3044\u738B\u304C\u6016\u304F\u3066\u9003\u3052\u3066\u3044\u308B\u3002";
const behaviorState = currentTarinaiBehavior(t);
const behaviorLabel = behaviorText(t);

View file

@ -102,6 +102,24 @@ function lineagePartnerPathFromPositions(a, b, nodeW, nodeH, unionY, attachOffse
if (a.rowIndex !== b.rowIndex) return lineageCrossGenerationPartnerPath(a, b, nodeW, nodeH, unionY, context);
return lineageSameGenerationPartnerPath(a, b, nodeW, nodeH, unionY, attachOffsetA, attachOffsetB);
}
function lineageChildAnchorOnMarriageLine(partner, fallbackX, fallbackY) {
const horizontals = (partner?.segments || [])
.filter(seg => seg.kind === "h" && Math.abs(seg.x2 - seg.x1) > 10)
.sort((a, b) => {
const ty = Number.isFinite(partner?.unionY) ? partner.unionY : fallbackY;
const dy = Math.abs(a.y1 - ty) - Math.abs(b.y1 - ty);
if (Math.abs(dy) > 0.01) return dy;
return Math.abs(b.x2 - b.x1) - Math.abs(a.x2 - a.x1);
});
const seg = horizontals[0] || null;
if (!seg) return { x: fallbackX, y: fallbackY };
const minX = Math.min(seg.x1, seg.x2) + 8;
const maxX = Math.max(seg.x1, seg.x2) - 8;
const midX = (seg.x1 + seg.x2) / 2;
const x = minX <= maxX ? clamp(Number.isFinite(fallbackX) ? fallbackX : midX, minX, maxX) : midX;
return { x, y: seg.y1 };
}
function lineageVerticalBridgePath(x, y1, y2, bridges = [], radius = 8.5) {
if (!Number.isFinite(x) || !Number.isFinite(y1) || !Number.isFinite(y2)) return '';
@ -170,6 +188,44 @@ function lineageBuildChildPathGroup(group, nodeW, nodeH, bridgeHorizontals) {
return { d };
}
function lineageChildBusSpan(group, nodeW) {
const childCenters = (group.children || []).map(c => c.x + nodeW / 2);
if (!childCenters.length) return null;
return {
left: Math.min(group.joinX, ...childCenters),
right: Math.max(group.joinX, ...childCenters),
};
}
function lineageSpansOverlap(a, b, pad = 12) {
if (!a || !b) return false;
return Math.max(a.left, b.left) <= Math.min(a.right, b.right) + pad;
}
function lineageSeparateChildBusLanes(groupsInGap, nodeW) {
const lanes = [];
const ordered = (groupsInGap || [])
.filter(group => group && Number.isFinite(group.busY))
.map(group => ({ group, span: lineageChildBusSpan(group, nodeW), baseY: group.busY }))
.filter(entry => entry.span)
.sort((a, b) => a.baseY - b.baseY || a.span.left - b.span.left || a.span.right - b.span.right);
for (const entry of ordered) {
let laneIndex = 0;
while (lanes[laneIndex]?.some(prev => Math.abs(prev.group.busY - entry.baseY) < 12 && lineageSpansOverlap(prev.span, entry.span))) {
laneIndex += 1;
}
if (!lanes[laneIndex]) lanes[laneIndex] = [];
const fromY = Number(entry.group.fromY);
const childTop = Number(entry.group.childTop);
const minY = Number.isFinite(fromY) ? fromY + 26 : -Infinity;
const maxY = Number.isFinite(childTop) ? childTop - 18 : Infinity;
const offset = laneIndex * 9;
entry.group.busY = clamp(entry.baseY - offset, minY, maxY);
lanes[laneIndex].push(entry);
}
}
function lineageComponentSignature(component, family) {
family = lineageEdgeFamily(family);
const idSet = new Set(component.map(x => x?.id).filter(Boolean));
@ -281,7 +337,7 @@ function lineageAssignPartnerAttachOffsets(linkGroups) {
for (const [key, groups] of buckets.entries()) {
groups.sort((a, b) => a.joinX - b.joinX || a.childTop - b.childTop);
const n = groups.length;
const step = 9;
const step = 14;
groups.forEach((group, i) => {
const offset = n <= 1 ? 0 : (i - (n - 1) / 2) * step;
const [nodeId] = key.split(':');
@ -328,8 +384,8 @@ function lineageBuildLinks(nodes, idSet, layout) {
for (const groupsInGap of byGap.values()) {
groupsInGap.sort((a, b) => a.joinX - b.joinX || a.children.length - b.children.length);
const top = Math.min(...groupsInGap.map(g => g.parentBottom)) + 44;
const bottom = Math.max(...groupsInGap.map(g => g.childTop)) - 42;
const top = Math.min(...groupsInGap.map(g => g.parentBottom)) + 58;
const bottom = Math.max(...groupsInGap.map(g => g.childTop)) - 54;
const span = Math.max(16, bottom - top);
groupsInGap.forEach((group, i) => {
const laneY = top + span * ((i + 1) / (groupsInGap.length + 1));
@ -343,22 +399,21 @@ function lineageBuildLinks(nodes, idSet, layout) {
const attachB = 0;
const partnerY = ((left.y + nodeH / 2) + (right.y + nodeH / 2)) / 2;
const partner = lineagePartnerPathFromPositions(left, right, nodeW, nodeH, partnerY, attachA, attachB, layout);
if (Number.isFinite(partner.unionX)) group.joinX = partner.unionX;
const unionY = Number.isFinite(partner.unionY) ? partner.unionY : partnerY;
const clearY = clamp(group.parentBottom + 22 + i * 2, unionY + 10, group.childTop - 36);
group.fromY = clearY;
group.busY = group.children.length === 1 ? Math.min(group.childTop - 18, clearY + 28) : Math.min(group.childTop - 24, Math.max(clearY + 24, laneY + 18 + i * 3));
const anchor = lineageChildAnchorOnMarriageLine(partner, Number.isFinite(partner.unionX) ? partner.unionX : group.joinX, unionY);
group.joinX = anchor.x;
group.fromY = anchor.y;
group.busY = group.children.length === 1 ? Math.min(group.childTop - 22, anchor.y + 38) : Math.min(group.childTop - 28, Math.max(anchor.y + 34, laneY + 24 + i * 5));
if (partner.d) {
partnerLinks.push(partner.d);
horizontalSegments.push(...partner.segments.filter(seg => seg.kind === 'h'));
}
const trunk = lineageVerticalBridgePath(group.joinX, unionY, group.fromY, []);
if (trunk) childLinks.push(trunk);
} else {
group.fromY = group.parentBottom;
group.busY = group.children.length === 1 ? Math.min(group.childTop - 18, group.fromY + 30) : Math.min(group.childTop - 24, Math.max(group.fromY + 28, laneY + 18 + i * 3));
group.busY = group.children.length === 1 ? Math.min(group.childTop - 22, group.fromY + 38) : Math.min(group.childTop - 28, Math.max(group.fromY + 34, laneY + 24 + i * 5));
}
});
lineageSeparateChildBusLanes(groupsInGap, nodeW);
}
for (const groupsInGap of byGap.values()) {

View file

@ -91,19 +91,25 @@ function lineageBuildLinksFast(nodes, idSet, layout) {
childLinkEdges += parents.length;
const childX = childPos.x + nodeW / 2;
const childY = childPos.y;
const fromY = Math.max(...parents.map(p => p.y + nodeH));
const joinX = parents.reduce((sum, p) => sum + p.x + nodeW / 2, 0) / parents.length;
let fromY = Math.max(...parents.map(p => p.y + nodeH));
let joinX = parents.reduce((sum, p) => sum + p.x + nodeW / 2, 0) / parents.length;
if (parents.length >= 2) {
const left = parents[0];
const right = parents[parents.length - 1];
const partnerKey = parents.map(p => p.node?.id).filter(Boolean).sort().join("+");
if (partnerKey && !seenPartners.has(partnerKey)) {
seenPartners.add(partnerKey);
const y = ((left.y + nodeH / 2) + (right.y + nodeH / 2)) / 2;
partnerLinks.push(`M ${(left.x + nodeW).toFixed(1)} ${y.toFixed(1)} L ${right.x.toFixed(1)} ${y.toFixed(1)}`);
const y = ((left.y + nodeH / 2) + (right.y + nodeH / 2)) / 2;
const x1 = left.x + nodeW;
const x2 = right.x;
if (x2 > x1) {
joinX = clamp(joinX, x1 + 8, x2 - 8);
fromY = y;
if (partnerKey && !seenPartners.has(partnerKey)) {
seenPartners.add(partnerKey);
partnerLinks.push(`M ${x1.toFixed(1)} ${y.toFixed(1)} L ${x2.toFixed(1)} ${y.toFixed(1)}`);
}
}
}
const laneY = Math.min(childY - 18, fromY + Math.max(28, (childY - fromY) * 0.48));
const laneY = Math.min(childY - 22, fromY + Math.max(36, (childY - fromY) * 0.54));
childLinks.push(`M ${joinX.toFixed(1)} ${fromY.toFixed(1)} L ${joinX.toFixed(1)} ${laneY.toFixed(1)} L ${childX.toFixed(1)} ${laneY.toFixed(1)} L ${childX.toFixed(1)} ${childY.toFixed(1)}`);
}
return { partnerLinks, childLinks, childLinkGroups, childLinkEdges };

View file

@ -302,6 +302,13 @@
if (uiCache.grabKind === "item") syncGrabbedPhysicsItem(target, "pinch-release");
target.playerHeld = false;
target._heldByPlayer = false;
if (uiCache.grabKind === "tarinai") {
target.pinchThoughtTimer = 0;
if (target.thought === "つままれている") target.thought = "";
const behavior = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(target) : null;
const reason = String(behavior?.reason || behavior?.text || behavior?.label || "");
if (reason === "つままれている" && typeof clearBehavior === "function") clearBehavior(target);
}
if (target.type === "duplicator") {
target.duplicatorLoadSuppressedUntil = Math.max(target.duplicatorLoadSuppressedUntil || 0, (world.time || 0) + 1.2);
}

View file

@ -1,8 +1,8 @@
"use strict";
(function () {
const APP_VERSION = "39.15.101";
const APP_BUILD = "tarinai-fire-effects-v39-15-101";
const APP_VERSION = "39.15.107";
const APP_BUILD = "tarinai-family-line-lanes-v39-15-107";
const APP_CACHE_NAME = `tarinai-colony-${APP_VERSION}`;
const STATIC_VERSION_PARAM = `v=${APP_VERSION}`;
@ -21,4 +21,3 @@
window.TARINAI_VERSION = APP_VERSION;
window.TARINAI_APP = config;
})();

View file

@ -295,6 +295,15 @@
it.grassStage = -1;
}
cleaned += p * 0.72;
} else if (it.type === "fire") {
it.amount = Math.max(0, (Number(it.amount) || 0) - dt * 540 * (0.35 + p));
it.flameLife = Math.max(0, (Number(it.flameLife) || 0) - dt * 5.2 * (0.35 + p));
it.fireShrinkPulse = Math.max(Number(it.fireShrinkPulse || 0), 0.18);
if (it.amount <= 4 || it.flameLife <= 0.05) {
it.amount = 0;
it.flameLife = 0;
}
cleaned += p * 1.15;
} else if (it.type === "grass_bed") {
it.amount = 0;
it.hp = 0;
@ -320,6 +329,20 @@
t.vy += ny * (95 + 165 * p) + deterministicRange(this, "water-hose-jitter-y", -10, 10, t, x, y);
t.surpriseTimer = Math.max(t.surpriseTimer || 0, 0.24);
t.applyWaterEffect?.(dt * (1.0 + p * 1.8), "hose");
if (t.burning || (t.burnTimer || 0) > 0) {
t.extinguishFire?.("\u6d17\u6d44\u3067\u6d88\u706b\u3055\u308c\u305f");
cleaned += p * 1.10;
}
}
for (const ant of this.nearbyAnts?.(x, y, radius + 45) || []) {
if (!ant || ant.dead) continue;
const d = distXY(ant.x, ant.y, x, y);
if (d > radius + (ant.r || 4)) continue;
const p = clamp(1 - d / (radius + (ant.r || 4)), 0, 1);
if (ant.burning || (ant.burnTimer || 0) > 0) {
ant.extinguishFire?.("water-hose");
cleaned += p * 0.75;
}
}
if (deterministicChance(this, "water-hose-ring", 0.72, x, y)) {
this.effects.push(new Effect("ring", x + deterministicRange(this, "water-hose-ring-x", -18, 18, x, y), y + deterministicRange(this, "water-hose-ring-y", -12, 12, x, y), { size: deterministicRange(this, "water-hose-ring-size", 8, 22, x, y), life: deterministicRange(this, "water-hose-ring-life", 0.18, 0.32, x, y), color: "rgba(128,204,238,0.60)" }));

View file

@ -660,10 +660,10 @@
}
if (isServingFoodType(item.type)) {
item.toolSize = data.toolSize || item.toolSize || "medium";
item.foodServingScale = Number(data.foodServingScale || item.foodServingScale || 1) || 1;
item.foodServingsMax = Math.max(1, Number(data.foodServingsMax || item.foodServingsMax || item.amount || 1));
item.foodServingsRemaining = item.foodServingsMax;
item.foodServingsRemaining = clamp(Number(data.foodServingsRemaining ?? item.foodServingsMax) || item.foodServingsMax, 0, item.foodServingsMax);
item.amount = item.foodServingsRemaining;
(typeof updateServingFoodVisualSize === "function" ? updateServingFoodVisualSize(item) : false);
}
if (Number.isFinite(Number(item.x))) item.prevX = item.x;
if (Number.isFinite(Number(item.y))) item.prevY = item.y;

View file

@ -406,10 +406,10 @@
item.r *= scale;
if (isServingFoodType(item.type)) {
const servings = foodServingsForSize(size);
item.foodServingScale = scale;
item.foodServingsMax = servings;
item.foodServingsRemaining = servings;
item.amount = servings;
(typeof updateServingFoodVisualSize === "function" ? updateServingFoodVisualSize(item) : false);
} else if (["grass", "zunchi"].includes(item.type)) {
item.amount *= scale * scale;
item.foodServingScale = scale;

View file

@ -1,6 +1,6 @@
"use strict";
const APP_VERSION = "39.15.101";
const APP_VERSION = "39.15.107";
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.