This commit is contained in:
33333-33333 2026-07-01 22:59:49 +09:00
commit acf93ec6df
22 changed files with 320 additions and 261 deletions

View file

@ -8,6 +8,7 @@ Purpose: static Japanese browser simulation/game. Optimize context by reading th
- 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.103`.
- Main loop: `js/main.js` initializes assets/audio/world/UI/save, then drives update/render.
- Performance/display settings: `js/perf_profiler.js` owns manual visual settings and profiler buckets; display tiers affect rendering/visuals, not simulation-quality branches.
- 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.
- Persistence: save files are schema/codec/storage/coordinator modules; snapshot/history modules keep world restoration and stats consistent.
@ -45,6 +46,8 @@ Purpose: static Japanese browser simulation/game. Optimize context by reading th
- 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`.
- Fire/burning behavior: `js/fire_runtime_system.js`, `js/item_dynamic_tool_system.js`, relevant item definitions, `js/world_combat_effects.js`, and nearby environment helpers.
- Performance/crowding: `js/perf_profiler.js`, `js/item_update_scheduler.js`, `js/tarinai_update_policy.js`, `js/constraint_system.js`, `js/world_spatial_budget.js`, and render/detail code.
- 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`.
@ -53,6 +56,7 @@ Purpose: static Japanese browser simulation/game. Optimize context by reading th
- 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.
- Keep intentional global exports for compatibility with regression checks and browser runtime; remove public facades only after repo-wide caller checks.
- Prefer focused edits in the owning subsystem; split modules are intentional compatibility boundaries.
- Avoid reintroducing `simulationQualityTier` branches unless a real simulation-quality setting is restored; current quality controls are visual/detail controls.
- Run `node --check js/*.js` and `python scripts/regression_check.py` for behavior changes.

View file

@ -17,7 +17,7 @@ The core loop is:
- **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, 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.
- **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; simulation cadence remains quality-preserving.
- **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.
@ -48,7 +48,7 @@ The core loop is:
- **ずんだ餅 / sweet**: favored food; can cure some illness.
- **へこ餅 / love mochi**: makes reproduction behavior more likely for a while.
- **けんか餅 / fight mochi**: makes fighting behavior more likely for a while.
- **草 / grass**: food and comfort source; can grow and be used for simple beds.
- **草 / grass**: food and comfort source; can grow, be used for simple beds, and sometimes dampen nearby fire instead of immediately catching.
- **水滴 / water**: drinkable water, also appears during rain and can be set in duplicators.
- **ずんだ汁 / zunda juice**: clears item effects from a tarinai.
- **複製機 / duplicator**: can be loaded with compatible food, medicine, grass, water, pushpins, or oshibyo and then produces or applies that content.
@ -89,7 +89,7 @@ The core loop is:
- **石 / stone**: heavy object; dangerous when dropped or moved into others.
- **げんこつ / genkotsu**: large fist impact on the ground.
- **爆竹 / firecracker**: creates an explosion.
- **爆竹 / firecracker**: creates an explosion; flame variants scatter fire while respecting active-fire limits.
- **画鋲 / pushpin**: rolls when dropped; can lodge in tarinai and cause panic plus continuous damage.
- **おしり鋲 / oshibyo**: blocks zunchi when lodged; pulling it out releases stored zunchi.
- **射撃 / shoot**: attacks with a gun effect.
@ -140,7 +140,7 @@ Tarinai can be pushed by physics objects, carried or affected by ants, dragged b
- **Field types**: park, garden, and cage provide different field sizes.
- **Ground types**: sand, dirt, concrete, blanket, foot massage, ice, and laboratory-style ground affect stress, grass, and movement.
- **Weather**: rain and other weather influence water, mood, and environment effects.
- **Grass ecology**: grass grows, feeds tarinai, comforts them, and can become bed material.
- **Grass ecology**: grass grows, feeds tarinai, comforts them, can become bed material, and has a small chance to reduce fire before burning.
- **Dirt and corpses**: zunchi, splats, corpses, and ant corpses affect hygiene, disease, and observation results.
- **Day/light conditions**: lighting and sun influence rendering and sunbath behavior.

View file

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

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.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" />
<link rel="stylesheet" href="css/base.css?v=39.15.110" />
<link rel="stylesheet" href="css/layout.css?v=39.15.110" />
<link rel="stylesheet" href="css/panel.css?v=39.15.110" />
<link rel="stylesheet" href="css/components.css?v=39.15.110" />
<link rel="stylesheet" href="css/mobile.css?v=39.15.110" />
</head>
<body>
<div id="loadingScreen" class="loading-screen" aria-live="polite">
@ -257,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.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>
<script src="js/version.js?v=39.15.110" defer></script>
<script src="js/event_bus.js?v=39.15.110" defer></script>
<script src="js/registry_base.js?v=39.15.110" defer></script>
<script src="js/disease_registry.js?v=39.15.110" defer></script>
<script src="js/sound_pack.js?v=39.15.110" defer></script>
<script src="js/deterministic_helpers.js?v=39.15.110" defer></script>
<script src="js/item_tool_metadata.js?v=39.15.110" defer></script>
<script src="js/item_tool_definitions.js?v=39.15.110" defer></script>
<script src="js/item_visual_definitions.js?v=39.15.110" defer></script>
<script src="js/item_food_definitions.js?v=39.15.110" defer></script>
<script src="js/item_effect_definitions.js?v=39.15.110" defer></script>
<script src="js/item_registry.js?v=39.15.110" defer></script>
<script src="js/data.js?v=39.15.110" defer></script>
<script src="js/ground_types.js?v=39.15.110" defer></script>
<script src="js/input_mode_manager.js?v=39.15.110" defer></script>
<script src="js/math.js?v=39.15.110" defer></script>
<script src="js/geometry_helpers.js?v=39.15.110" defer></script>
<script src="js/collision_footprint_system.js?v=39.15.110" defer></script>
<script src="js/physics_helpers.js?v=39.15.110" defer></script>
<script src="js/placement_preview_system.js?v=39.15.110" defer></script>
<script src="js/pin_attachment_system.js?v=39.15.110" defer></script>
<script src="js/physics_shape_editor_system.js?v=39.15.110" defer></script>
<script src="js/mechanical_system.js?v=39.15.110" defer></script>
<script src="js/mechanical_shape_bridge.js?v=39.15.110" defer></script>
<script src="js/constraint_system.js?v=39.15.110" defer></script>
<script src="js/physics_world_system.js?v=39.15.110" defer></script>
<script src="js/physics_projection_system.js?v=39.15.110" defer></script>
<script src="js/assets.js?v=39.15.110" defer></script>
<script src="js/audio.js?v=39.15.110" defer></script>
<script src="js/perf_profiler.js?v=39.15.110" defer></script>
<script src="js/render.js?v=39.15.110" defer></script>
<script src="js/sim_core.js?v=39.15.110" defer></script>
<script src="js/tarinai_seed_factory.js?v=39.15.110" defer></script>
<script src="js/structures.js?v=39.15.110" defer></script>
<script src="js/items.js?v=39.15.110" defer></script>
<script src="js/item_type_initializers.js?v=39.15.110" defer></script>
<script src="js/item_lifecycle_support.js?v=39.15.110" defer></script>
<script src="js/item_update_policy.js?v=39.15.110" defer></script>
<script src="js/fire_runtime_system.js?v=39.15.110" defer></script>
<script src="js/robot_cleaner_system.js?v=39.15.110" defer></script>
<script src="js/item_dynamic_tool_system.js?v=39.15.110" defer></script>
<script src="js/item_dynamic_ball_system.js?v=39.15.110" defer></script>
<script src="js/item_dynamic_duplicator_system.js?v=39.15.110" defer></script>
<script src="js/item_dynamic_pin_system.js?v=39.15.110" defer></script>
<script src="js/item_dynamic_zunchi_system.js?v=39.15.110" defer></script>
<script src="js/item_dynamic_system.js?v=39.15.110" defer></script>
<script src="js/item_lifecycle_decay_system.js?v=39.15.110" defer></script>
<script src="js/item_lifecycle_growth_system.js?v=39.15.110" defer></script>
<script src="js/item_lifecycle_step_frame.js?v=39.15.110" defer></script>
<script src="js/item_lifecycle_step_decay.js?v=39.15.110" defer></script>
<script src="js/item_lifecycle_step_dynamic.js?v=39.15.110" defer></script>
<script src="js/item_lifecycle_step_growth.js?v=39.15.110" defer></script>
<script src="js/update_step_pipeline_runner.js?v=39.15.110" defer></script>
<script src="js/item_lifecycle_pipeline.js?v=39.15.110" defer></script>
<script src="js/item_runtime.js?v=39.15.110" defer></script>
<script src="js/structure_lifecycle.js?v=39.15.110" defer></script>
<script src="js/item_render_helpers.js?v=39.15.110" defer></script>
<script src="js/item_render_runtime.js?v=39.15.110" defer></script>
<script src="js/burn_motion_util.js?v=39.15.110" defer></script>
<script src="js/ants.js?v=39.15.110" defer></script>
<script src="js/health.js?v=39.15.110" defer></script>
<script src="js/tarinai.js?v=39.15.110" defer></script>
<script src="js/tarinai_action_spec.js?v=39.15.110" defer></script>
<script src="js/tarinai_behavior_state.js?v=39.15.110" defer></script>
<script src="js/tarinai_identity_social.js?v=39.15.110" defer></script>
<script src="js/tarinai_action_state.js?v=39.15.110" defer></script>
<script src="js/tarinai_disease_nest.js?v=39.15.110" defer></script>
<script src="js/tarinai_item_effects.js?v=39.15.110" defer></script>
<script src="js/tarinai_needs_core.js?v=39.15.110" defer></script>
<script src="js/tarinai_behavior_text.js?v=39.15.110" defer></script>
<script src="js/tarinai_forced_behavior.js?v=39.15.110" defer></script>
<script src="js/tarinai_nest_sleep_system.js?v=39.15.110" defer></script>
<script src="js/tarinai_item_targeting.js?v=39.15.110" defer></script>
<script src="js/tarinai_consumable_behavior.js?v=39.15.110" defer></script>
<script src="js/tarinai_social_action_runtime.js?v=39.15.110" defer></script>
<script src="js/tarinai_building_behavior.js?v=39.15.110" defer></script>
<script src="js/tarinai_action_definitions.js?v=39.15.110" defer></script>
<script src="js/tarinai_needs_items.js?v=39.15.110" defer></script>
<script src="js/tarinai_food_prototype_mixin.js?v=39.15.110" defer></script>
<script src="js/tarinai_need_planner_system.js?v=39.15.110" defer></script>
<script src="js/tarinai_item_interaction_context.js?v=39.15.110" defer></script>
<script src="js/tarinai_food_interaction_system.js?v=39.15.110" defer></script>
<script src="js/tarinai_contact_item_system.js?v=39.15.110" defer></script>
<script src="js/tarinai_item_interaction_system.js?v=39.15.110" defer></script>
<script src="js/tarinai_sunbath_system.js?v=39.15.110" defer></script>
<script src="js/tarinai_cursor_care_system.js?v=39.15.110" defer></script>
<script src="js/tarinai_local_environment_system.js?v=39.15.110" defer></script>
<script src="js/tarinai_social_move_life.js?v=39.15.110" defer></script>
<script src="js/tarinai_update_step_frame.js?v=39.15.110" defer></script>
<script src="js/tarinai_update_step_ai.js?v=39.15.110" defer></script>
<script src="js/tarinai_update_step_environment.js?v=39.15.110" defer></script>
<script src="js/tarinai_update_step_movement.js?v=39.15.110" defer></script>
<script src="js/tarinai_update_step_health.js?v=39.15.110" defer></script>
<script src="js/tarinai_update_pipeline.js?v=39.15.110" defer></script>
<script src="js/tarinai_runtime.js?v=39.15.110" defer></script>
<script src="js/tarinai_render.js?v=39.15.110" defer></script>
<script src="js/world.js?v=39.15.110" defer></script>
<script src="js/world_view.js?v=39.15.110" defer></script>
<script src="js/family_graph.js?v=39.15.110" defer></script>
<script src="js/world_reset_presets.js?v=39.15.110" defer></script>
<script src="js/world_family_social.js?v=39.15.110" defer></script>
<script src="js/impact_core_system.js?v=39.15.110" defer></script>
<script src="js/impact_response_system.js?v=39.15.110" defer></script>
<script src="js/collision_response_system.js?v=39.15.110" defer></script>
<script src="js/world_combat_effects.js?v=39.15.110" defer></script>
<script src="js/world_environment.js?v=39.15.110" defer></script>
<script src="js/world_temperature_system.js?v=39.15.110" defer></script>
<script src="js/world_pathfinding_system.js?v=39.15.110" defer></script>
<script src="js/world_grass_placement_system.js?v=39.15.110" defer></script>
<script src="js/world_spatial_budget.js?v=39.15.110" defer></script>
<script src="js/world_ants_system.js?v=39.15.110" defer></script>
<script src="js/weather_system.js?v=39.15.110" defer></script>
<script src="js/item_update_scheduler.js?v=39.15.110" defer></script>
<script src="js/simulation_runtime_helpers.js?v=39.15.110" defer></script>
<script src="js/tarinai_update_policy.js?v=39.15.110" defer></script>
<script src="js/simulation_environment_system.js?v=39.15.110" defer></script>
<script src="js/simulation_item_ant_system.js?v=39.15.110" defer></script>
<script src="js/simulation_effects_system.js?v=39.15.110" defer></script>
<script src="js/simulation_creature_system.js?v=39.15.110" defer></script>
<script src="js/simulation_maintenance_system.js?v=39.15.110" defer></script>
<script src="js/simulation_ambient_system.js?v=39.15.110" defer></script>
<script src="js/simulation_systems.js?v=39.15.110" defer></script>
<script src="js/system_order.js?v=39.15.110" defer></script>
<script src="js/simulation.js?v=39.15.110" defer></script>
<script src="js/colony_situation_system.js?v=39.15.110" defer></script>
<script src="js/world_update.js?v=39.15.110" defer></script>
<script src="js/world_tool_actions.js?v=39.15.110" defer></script>
<script src="js/world_placement_log.js?v=39.15.110" defer></script>
<script src="js/world_event_effects.js?v=39.15.110" defer></script>
<script src="js/command_dispatcher.js?v=39.15.110" defer></script>
<script src="js/text_catalog.js?v=39.15.110" defer></script>
<script src="js/ui.js?v=39.15.110" defer></script>
<script src="js/ui_log.js?v=39.15.110" defer></script>
<script src="js/ui_helpers.js?v=39.15.110" defer></script>
<script src="js/ui_selected.js?v=39.15.110" defer></script>
<script src="js/save_schema.js?v=39.15.110" defer></script>
<script src="js/snapshot_system.js?v=39.15.110" defer></script>
<script src="js/restore_coordinator.js?v=39.15.110" defer></script>
<script src="js/history_system.js?v=39.15.110" defer></script>
<script src="js/save_codec.js?v=39.15.110" defer></script>
<script src="js/save_storage.js?v=39.15.110" defer></script>
<script src="js/save_system.js?v=39.15.110" defer></script>
<script src="js/ui_tooltips.js?v=39.15.110" defer></script>
<script src="js/ui_layout_dialogs.js?v=39.15.110" defer></script>
<script src="js/ui_ground.js?v=39.15.110" defer></script>
<script src="js/ui_tools.js?v=39.15.110" defer></script>
<script src="js/ui_input_shared.js?v=39.15.110" defer></script>
<script src="js/ui_pointer_action_system.js?v=39.15.110" defer></script>
<script src="js/ui_input_touch.js?v=39.15.110" defer></script>
<script src="js/ui_input_mouse.js?v=39.15.110" defer></script>
<script src="js/ui_bind.js?v=39.15.110" defer></script>
<script src="js/ui_charts.js?v=39.15.110" defer></script>
<script src="js/ui_family_data.js?v=39.15.110" defer></script>
<script src="js/ui_family_async.js?v=39.15.110" defer></script>
<script src="js/ui_family_layout.js?v=39.15.110" defer></script>
<script src="js/ui_family_paths.js?v=39.15.110" defer></script>
<script src="js/ui_family_render.js?v=39.15.110" defer></script>
<script src="js/main.js?v=39.15.110" defer></script>
<script src="js/debug_tools.js?v=39.15.110" defer></script>
</body>
</html>

View file

@ -483,17 +483,15 @@
}
function ropeLodSettings(worldRef, length) {
const tier = global.TarinaiPerf.simulationQualityTier?.() || "high";
const ropeCount = Number(worldRef?.itemCounts?.rope || 0) || 0;
let spacing = 34;
let maxParticles = 24;
let iterations = 6;
let particleStride = 1;
if (tier === "low") { spacing = 56; maxParticles = 15; iterations = 4; particleStride = 2; }
if (ropeCount > 12) { spacing *= 1.18; maxParticles = Math.max(10, maxParticles - 4); iterations = Math.max(3, iterations - 1); }
if (ropeCount > 24) { spacing *= 1.28; maxParticles = Math.max(8, maxParticles - 4); iterations = Math.max(3, iterations - 1); particleStride = 2; }
const targetCount = Math.max(4, Math.min(maxParticles, Math.ceil(Math.max(24, length) / spacing) + 1));
return { tier, ropeCount, spacing, maxParticles, iterations, particleStride, targetCount };
return { ropeCount, spacing, maxParticles, iterations, particleStride, targetCount };
}
function ensureRopeParticles(item, a, b, length, worldRef = null) {
@ -681,8 +679,7 @@
const getRopeObstacleRects = () => {
if (ropeObstacleRects) return ropeObstacleRects;
if (!worldRef?.nearbySolidObstacleRects) return (ropeObstacleRects = []);
const maxChecks = lod.tier === "low" ? 18 : 38;
ropeObstacleRects = worldRef.nearbySolidObstacleRects(ropeCx, ropeCy, ropeQueryRadius + 64, { maxChecks }) || [];
ropeObstacleRects = worldRef.nearbySolidObstacleRects(ropeCx, ropeCy, ropeQueryRadius + 64, { maxChecks: 38 }) || [];
return ropeObstacleRects;
};
const collideParticles = () => {
@ -714,7 +711,7 @@
for (let iter = 0; iter < lod.iterations; iter += 1) {
ps[0].x = a.x; ps[0].y = a.y;
ps[ps.length - 1].x = b.x; ps[ps.length - 1].y = b.y;
const shouldCollide = lod.tier === "low" ? iter === 1 : (iter === 2 || iter === lod.iterations - 1);
const shouldCollide = iter === 2 || iter === lod.iterations - 1;
if (shouldCollide) changed = collideParticles() || changed;
for (let i = 0; i < ps.length - 1; i += 1) {
const p1 = ps[i], p2 = ps[i + 1];

View file

@ -212,8 +212,9 @@ function updateFirecracker(item, dt, worldRef) {
for (const it of nearby) {
if (!it || it.dead || it === item) continue;
if (it.type === "grass" && distXY(item.x, item.y, it.x, it.y) <= spreadRadius + Math.max(it.r || 16, 12)) {
changed = tryGrassExtinguishFire(it, item, worldRef, dt) || changed;
if ((item.amount || 0) > 0) changed = igniteGrassByFire(it, item, worldRef, dt) || changed;
const extinguished = tryGrassExtinguishFire(it, item, worldRef, dt);
changed = extinguished || changed;
if (!extinguished && (item.amount || 0) > 0) changed = igniteGrassByFire(it, item, worldRef, dt) || changed;
}
if (it.type === "zunchi" && distXY(item.x, item.y, it.x, it.y) <= spreadRadius + Math.max(it.r || 15, 12)) changed = igniteZunchiByFire(it, item, worldRef, dt) || changed;
if (it.type === "water" && distXY(item.x, item.y, it.x, it.y) <= spreadRadius + Math.max(it.r || 10, 8)) {
@ -461,8 +462,6 @@ function updateFirecracker(item, dt, worldRef) {
panicTarinaiFromFire,
igniteAntByFire,
igniteGrassByFire,
igniteZunchiByFire,
tryGrassExtinguishFire,
spawnFireAt: (worldRef, x, y, opts = {}) => spawnFireAt(worldRef, x, y, opts),
scatterFireFrom,
compactWorldFires,

View file

@ -2,6 +2,8 @@
const HEALTH = (() => {
const MAJOR_DAMAGE_WINDOW = 24;
const WEAKENED_MAJOR_DAMAGE_MIN_AGE = 3;
const WEAKENED_MAJOR_DAMAGE_MAX_AGE = 20;
const MAJOR_DAMAGE_REACTION_WINDOW = 1.0;
const WEAKENED_TRACE_WINDOW = 45;
const WEAKENED_SUFFIX = "\u3067\u8870\u5f31";
@ -100,6 +102,12 @@ const HEALTH = (() => {
return Boolean(t.lastMajorDamageCause && now - (t.lastMajorDamageAt || -999) <= MAJOR_DAMAGE_WINDOW);
}
function hasLingeringMajorDamage(t) {
const now = t.world?.time || 0;
const age = now - (t.lastMajorDamageAt || -999);
return Boolean(t.lastMajorDamageCause && age >= WEAKENED_MAJOR_DAMAGE_MIN_AGE && age < WEAKENED_MAJOR_DAMAGE_MAX_AGE);
}
function isFightingContext(t, recent) {
return Boolean(t.fightTimer > 0.04 || t.defeatedTimer > 0.04 || t.defeatedById || t.fightWinnerId || recent === CAUSES.fight);
}
@ -121,7 +129,7 @@ const HEALTH = (() => {
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)) {
if (hasLingeringMajorDamage(t) && (!direct || direct === t.lastMajorDamageCause || t.energy <= 18 || t.mood <= 16 || t.stress >= 124 || t.hunger >= 112)) {
return weakenedDeathReasonFor(t.lastMajorDamageCause);
}
const weakenedTrace = recentDamageCause(t, WEAKENED_TRACE_WINDOW);
@ -144,7 +152,8 @@ const HEALTH = (() => {
if (text.includes(CAUSES.lifespan) || text.includes("\u5bff\u547d")) return CAUSES.lifespan;
if (direct) return direct;
if (t.stress >= 118 || /\u30b9\u30c8\u30ec\u30b9|stress|\u6c17\u5206/.test(text)) return CAUSES.stress;
if (opts.lowHealth || t.energy <= 0.5) return CAUSES.weakness;
if ((opts.lowHealth || t.energy <= 0.5) && hasLingeringMajorDamage(t)) return weakenedDeathReasonFor(t.lastMajorDamageCause);
if (opts.lowHealth || t.energy <= 0.5) return CAUSES.accident;
return "";
}
@ -155,7 +164,7 @@ const HEALTH = (() => {
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.fire && cause !== CAUSES.weakness && cause !== CAUSES.freeze && cause !== CAUSES.heatstroke;
if (canWeaken && (opts.weakened || (hasRecentMajorDamage(t) && cause === t.lastMajorDamageCause))) return weakenedDeathReasonFor(cause);
if (canWeaken && (opts.weakened || (hasLingeringMajorDamage(t) && cause === t.lastMajorDamageCause))) return weakenedDeathReasonFor(cause);
return cause;
}

View file

@ -91,13 +91,11 @@
fireTempMultiplier: (...args) => fireApi().fireTempMultiplier(...args),
contactWaterDrop: (...args) => fireApi().contactWaterDrop(...args),
consumeWaterForFire: (...args) => fireApi().consumeWaterForFire(...args),
igniteTarinaiByFire: (...args) => fireApi().igniteTarinaiByFire(...args),
panicTarinaiFromFire: (...args) => fireApi().panicTarinaiFromFire(...args),
igniteAntByFire: (...args) => fireApi().igniteAntByFire(...args),
igniteGrassByFire: (...args) => fireApi().igniteGrassByFire(...args),
spawnFireAt: (...args) => fireApi().spawnFireAt(...args),
scatterFireFrom: (...args) => fireApi().scatterFireFrom(...args),
compactWorldFires: (...args) => fireApi().compactWorldFires(...args),
updateFire: (...args) => fireApi().updateFire(...args),
});
})(typeof window !== "undefined" ? window : globalThis);

View file

@ -55,6 +55,13 @@
].join(":");
}
function scheduleDelay(item, interval, initial = false) {
const key = item?.id || item?._schedulerToken || item?.type || "item";
const salt = initial ? "item-scheduler-initial" : `item-scheduler:${Math.floor((item?._schedulerLastAt || 0) * 3)}`;
const unit = typeof stableUnit === "function" ? stableUnit(key, salt) : Math.random();
const spread = initial ? (0.42 + unit * 1.18) : (0.88 + unit * 0.24);
return interval * spread;
}
function scheduleItem(state, item, now, interval = itemUpdateInterval(item)) {
if (!item || item.dead || !Number.isFinite(interval)) return false;
@ -64,8 +71,9 @@
}
const token = (item._schedulerToken || 0) + 1;
item._schedulerToken = token;
if (!Number.isFinite(item._schedulerLastAt)) item._schedulerLastAt = now;
heapPush(state.heap, { time: now + interval, item, token });
const initial = !Number.isFinite(item._schedulerLastAt);
if (initial) item._schedulerLastAt = now;
heapPush(state.heap, { time: now + scheduleDelay(item, interval, initial), item, token });
return true;
}
@ -145,13 +153,9 @@
}
function physicsBudgetFor(worldRef) {
const tier = global.TarinaiPerf.simulationQualityTier?.() || "high";
const itemCount = (worldRef?.items || []).length || 0;
const activeHint = worldRef?._itemUpdateRealtime?.length || 0;
const mobile = Boolean(global.TarinaiInputMode.snapshot().touchFirst || (global.innerWidth || 9999) <= 760);
if (mobile && tier !== "high") return { maxPairs: 112, maxLinks: 72, postMaxPairs: 28, maxSubsteps: 2 };
if (mobile) return { maxPairs: 150, maxLinks: 96, postMaxPairs: 40, maxSubsteps: 2 };
if (tier === "low") return { maxPairs: 140, maxLinks: 90, postMaxPairs: 36, maxSubsteps: 2 };
return itemCount > 80
? { maxPairs: 240, maxLinks: 170, postMaxPairs: 78, maxSubsteps: 4 }
: { maxPairs: 300, maxLinks: 210, postMaxPairs: 100, maxSubsteps: 5 };

View file

@ -98,7 +98,7 @@ function loop() {
const endRender = window.TarinaiPerf.begin("render.total");
render();
if (endRender) endRender();
window.TarinaiPerf.endFrame(rawDt, window.__tarinaiFps || 0);
window.TarinaiPerf.endFrame();
if (window.TarinaiPerf.consumeResizeRequest() && typeof resizeCanvas === "function") resizeCanvas();
statsTimer += dt;
const statsInterval = 2.5;

View file

@ -16,7 +16,7 @@
const metrics = new Map();
let frameOpen = null;
let frameCount = 0;
let lastQualityCheckAt = 0;
let lastSmoothAt = 0;
const storageKey = "tarinai_visual_settings_v1";
const allowed = {
effects: new Set(["off", "low", "mid", "high"]),
@ -124,7 +124,7 @@
return 1;
}
function endFrame(rawDt = 0, fps = global.__tarinaiFps || 0) {
function endFrame() {
frameCount += 1;
if (frameOpen != null && debugEnabled) {
const elapsed = Math.max(0, nowMs() - frameOpen);
@ -139,8 +139,8 @@
frameOpen = null;
if (debugEnabled) stack.length = 0;
const t = nowMs();
if (t - lastQualityCheckAt >= 750) {
lastQualityCheckAt = t;
if (t - lastSmoothAt >= 750) {
lastSmoothAt = t;
smoothBuckets();
}
}
@ -164,27 +164,8 @@
return true;
}
function setVisualSettings(patch) {
let changed = false;
const next = { ...visualSettings };
for (const [key, value] of Object.entries(patch || {})) {
if (!Object.prototype.hasOwnProperty.call(defaults, key)) continue;
const nextValue = normalizeVisualSetting(key, value);
if (!allowed[key]?.has(nextValue) || next[key] === nextValue) continue;
next[key] = nextValue;
changed = true;
if (key === "details") resizeRequested = true;
}
if (!changed) return false;
visualSettings = next;
saveVisualSettings();
return true;
}
function renderQualityTier() { return visualSettings.details; }
function shadowQualityTier() { return visualSettings.shadows; }
function effectsQualityTier() { return visualSettings.effects; }
function simulationQualityTier() { return "high"; }
function dprScaleValue() { return dprScaleForDetails(visualSettings.details); }
function consumeResizeRequest() {
const v = resizeRequested;
@ -232,12 +213,9 @@
endFrame,
renderQualityTier,
shadowQualityTier,
effectsQualityTier,
simulationQualityTier,
visualLevel,
visualSettings: visualSettingsValue,
setVisualSetting,
setVisualSettings,
dprScale: dprScaleValue,
consumeResizeRequest,
setMetric,

View file

@ -919,13 +919,14 @@ function syncCarriedPlushieToOwner(it, worldRef) {
function collectVisibleRenderStack(worldRef, visibleRect) {
const end = window.TarinaiPerf.begin("render.stackBuild");
try {
if (!worldRef._visibleRenderStack) worldRef._visibleRenderStack = { backItems: [], layered: [], carriedPlushies: [], lodgedPins: [], ballLodgedPins: [] };
if (!worldRef._visibleRenderStack) worldRef._visibleRenderStack = { backItems: [], layered: [], carriedPlushies: [], lodgedPins: [], ballLodgedPins: [], champions: [] };
const stack = worldRef._visibleRenderStack;
stack.backItems.length = 0;
stack.layered.length = 0;
stack.carriedPlushies.length = 0;
stack.lodgedPins.length = 0;
if (stack.ballLodgedPins) stack.ballLodgedPins.length = 0;
if (stack.champions) stack.champions.length = 0;
const seen = new Set();
const addItem = (it) => {
if (it && it.isStructure && it.type === "plushie" && it.carriedById) syncCarriedPlushieToOwner(it, worldRef);
@ -950,6 +951,7 @@ function collectVisibleRenderStack(worldRef, visibleRect) {
if (!entity || entity.dead || seen.has(entity) || !isEntityVisibleInRect(entity, visibleRect)) return;
seen.add(entity);
stack.layered.push({ entity, y: renderLayerSortY(entity), rank: renderLayerKindRank(entity) });
if (entity.isTarinaiChampion && typeof entity.drawChampionCrown === "function") (stack.champions || (stack.champions = [])).push(entity);
};
worldRef.ensureSpatial?.("render-visible");
if (worldRef.spatial?.nearbyRectInto) {
@ -1505,7 +1507,7 @@ function render() {
const endBackItems = renderPerfBegin("render.draw.backItems");
for (const it of renderStack.backItems) {
if (isEntityVisibleInRect(it, visibleRect)) it.draw(ctx, world.time, lighting);
it.draw(ctx, world.time, lighting);
}
renderPerfEnd(endBackItems);
@ -1523,14 +1525,14 @@ function render() {
if (isBall) {
for (const it of renderStack.ballLodgedPins || []) {
if (!it || drawnAttachedPinIds.has(it.id) || it.pinBallId !== entity.id) continue;
if (isEntityVisibleInRect(it, visibleRect)) it.draw(ctx, world.time, lighting);
it.draw(ctx, world.time, lighting);
drawnAttachedPinIds.add(it.id);
}
}
if (isTarinaiEntity) {
for (const it of renderStack.lodgedPins || []) {
if (!it || drawnAttachedPinIds.has(it.id) || it.pinTargetId !== entity.id) continue;
if (isEntityVisibleInRect(it, visibleRect)) it.draw(ctx, world.time, lighting);
it.draw(ctx, world.time, lighting);
drawnAttachedPinIds.add(it.id);
}
}
@ -1538,31 +1540,27 @@ function render() {
const endLayered = renderPerfBegin("render.draw.layered");
for (const entry of renderStack.layered) {
if (isEntityVisibleInRect(entry.entity, visibleRect)) {
entry.entity.draw(ctx, world.time, lighting);
drawAttachedPinsForEntity(entry.entity);
}
entry.entity.draw(ctx, world.time, lighting);
drawAttachedPinsForEntity(entry.entity);
}
renderPerfEnd(endLayered);
const endAttachments = renderPerfBegin("render.draw.attachments");
for (const it of renderStack.carriedPlushies) {
syncCarriedPlushieToOwner(it, world);
if (isEntityVisibleInRect(it, visibleRect)) it.draw(ctx, world.time, lighting);
it.draw(ctx, world.time, lighting);
}
for (const t of world.tarinai || []) {
if (t && !t.dead && t.isTarinaiChampion && typeof t.drawChampionCrown === "function" && isEntityVisibleInRect(t, visibleRect)) {
t.drawChampionCrown(ctx, world.time, lighting);
}
for (const t of renderStack.champions || []) {
t.drawChampionCrown(ctx, world.time, lighting);
}
for (const it of renderStack.ballLodgedPins || []) {
if (!it || drawnAttachedPinIds.has(it.id)) continue;
if (isEntityVisibleInRect(it, visibleRect)) it.draw(ctx, world.time, lighting);
it.draw(ctx, world.time, lighting);
drawnAttachedPinIds.add(it.id);
}
for (const it of renderStack.lodgedPins) {
if (!it || drawnAttachedPinIds.has(it.id)) continue;
if (isEntityVisibleInRect(it, visibleRect)) it.draw(ctx, world.time, lighting);
it.draw(ctx, world.time, lighting);
drawnAttachedPinIds.add(it.id);
}
renderPerfEnd(endAttachments);

View file

@ -16,6 +16,7 @@
const h = helpers;
const end = global.TarinaiPerf.begin("update.tarinai.individual") || null;
const visibleRect = h.updateVisibleWorldRect(worldRef, 160);
const nearVisibleRect = h.updateVisibleWorldRect(worldRef, 520);
const now = worldRef.time || 0;
const stats = {
total: 0,
@ -48,7 +49,7 @@
stats.total += 1;
const behaviorId = updatePolicy.behaviorId(t);
inc(stats.states, behaviorId);
const cadence = updatePolicy.updateCadence(worldRef, t, visibleRect);
const cadence = updatePolicy.updateCadence(worldRef, t, visibleRect, nearVisibleRect);
const interval = Number.isFinite(cadence.interval) ? cadence.interval : Infinity;
inc(stats.lanes, cadence.lane || "unknown");
if (worldRef.selected === t) stats.selected += 1;

View file

@ -3,8 +3,45 @@
// Layer: simulation/system-effects
// Owns frame updates for transient visual/effect entities.
(function (global) {
function updateEffect(effect, dt, frameId = 0) {
if (!effect || effect.dead) return false;
const runDt = Math.min(0.24, Math.max(0, Number(effect._effectUpdateAccum || 0) + dt));
effect._effectUpdateAccum = 0;
effect._effectUpdatedFrame = frameId;
effect.update(runDt);
return true;
}
function updateEffects(worldRef, dt) {
for (const ef of worldRef.effects) ef.update(dt);
const effects = worldRef.effects || [];
const total = effects.length;
const frameId = (worldRef._effectUpdateFrameId || 0) + 1;
worldRef._effectUpdateFrameId = frameId;
if (total <= 56) {
for (const ef of effects) updateEffect(ef, dt, frameId);
worldRef._effectUpdateStats = { total, updated: total, skipped: 0, batched: false };
return;
}
const budget = Math.max(40, Math.ceil(total * 0.42));
let cursor = Math.max(0, Math.floor(worldRef._effectUpdateCursor || 0)) % total;
let updated = 0;
let scanned = 0;
while (updated < budget && scanned < total) {
const ef = effects[cursor];
cursor = (cursor + 1) % total;
scanned += 1;
if (!ef || ef.dead) continue;
updateEffect(ef, dt, frameId);
updated += 1;
}
for (let i = 0; i < total; i += 1) {
const ef = effects[i];
if (!ef || ef.dead || ef._effectUpdatedFrame === frameId) continue;
if (Number.isFinite(ef._effectUpdateAccum)) ef._effectUpdateAccum = Math.min(0.24, ef._effectUpdateAccum + dt);
else ef._effectUpdateAccum = dt;
}
worldRef._effectUpdateCursor = cursor;
worldRef._effectUpdateStats = { total, updated, skipped: Math.max(0, total - updated), batched: true };
}
global.TarinaiEffectsSimulationSystem = Object.freeze({

View file

@ -38,7 +38,11 @@
worldRef.updateWeather(dt);
worldRef.updateTemperature?.(dt);
worldRef.updateTerrainDirtyState?.(dt);
worldRef.evaluateColonyMood?.();
const now = worldRef.time || 0;
if (now >= (worldRef.nextColonyMoodEvaluateAt || 0)) {
worldRef.nextColonyMoodEvaluateAt = now + 0.65;
worldRef.evaluateColonyMood?.();
}
}
function prepareSpatialFrame(worldRef) {

View file

@ -17,10 +17,15 @@
}
worldRef.countsTimer += dt;
const countsInterval = 2.0;
if (worldRef.countsTimer >= countsInterval || itemsCompacted || worldRef.items.length !== itemCountBefore) {
worldRef.updateItemCounts();
worldRef.updateEffectCounts();
const countsInterval = 2.75;
const itemCountChanged = worldRef.items.length !== itemCountBefore || worldRef.items.length !== (worldRef._lastMaintenanceItemCount ?? worldRef.items.length);
const effectCountChanged = worldRef.effects.length !== (worldRef._lastMaintenanceEffectCount ?? worldRef.effects.length);
const countsDue = worldRef.countsTimer >= countsInterval;
if (countsDue || itemsCompacted || itemCountChanged || effectCountChanged || worldRef.itemBucketsDirty) {
if (countsDue || itemsCompacted || itemCountChanged || worldRef.itemBucketsDirty) worldRef.updateItemCounts();
if (countsDue || itemsCompacted || effectCountChanged) worldRef.updateEffectCounts();
worldRef._lastMaintenanceItemCount = worldRef.items.length;
worldRef._lastMaintenanceEffectCount = worldRef.effects.length;
worldRef.countsTimer = 0;
}
if ((worldRef.time || 0) >= (worldRef.nextGrassLimitCheckAt || 0)) {
@ -71,7 +76,8 @@
}
worldRef.drawSortTimer += dt;
const sortInterval = 0.25;
const visibleMoving = Boolean(worldRef.spatialTarinaiDirty || worldRef.spatialDynamicItemsDirty || worldRef.spatialAntDirty);
const sortInterval = visibleMoving ? 0.25 : 0.55;
if (worldRef.drawSortTimer >= sortInterval) {
worldRef.drawListDirty = true;
worldRef.drawSortTimer = 0;

View file

@ -11,7 +11,7 @@ function isDuplicatorFoodSource(item) {
return !!item && item.type === "duplicator";
}
const NORMAL_POOP_MEAL_THRESHOLD = 3;
const NORMAL_POOP_MEAL_THRESHOLD = 4;
function normalPoopUnitsFor(foodType = "", amount = 0, options = {}) {
const type = String(foodType || "");

View file

@ -63,18 +63,17 @@
return false;
}
function cadence(worldRef, tarinai, visibleRect) {
function cadence(worldRef, tarinai, visibleRect, nearVisibleRect = null) {
if (!tarinai || tarinai.dead) return { interval: Infinity, lane: "dead", smoothMotion: false, visible: false, urgent: false };
const isSelected = worldRef.selected === tarinai;
if (isSelected) return { interval: 0, lane: "selected", smoothMotion: false, visible: true, urgent: true };
const id = behaviorId(tarinai);
const state = String(tarinai.state || id || "");
const tier = global.TarinaiPerf.simulationQualityTier?.() || "high";
const count = (worldRef.tarinai || []).length;
const mobile = Boolean(global.TarinaiInputMode.snapshot().touchFirst || (global.innerWidth || 9999) <= 760);
const isVisible = visible(worldRef, tarinai, visibleRect);
const farRect = isVisible ? visibleRect : helpers.updateVisibleWorldRect(worldRef, 520);
const farRect = isVisible ? visibleRect : (nearVisibleRect || helpers.updateVisibleWorldRect(worldRef, 520));
const isNear = isVisible || Boolean(helpers.pointInRect(tarinai.x || 0, tarinai.y || 0, farRect));
const motionNeed = hasMotionNeed(tarinai);
@ -95,7 +94,7 @@
// runs on visible skipped frames.
const panicUrgent = (tarinai.fearTimer || 0) > 0.20 || /panic/i.test(id);
if (panicUrgent) {
const interval = isVisible ? (mobile ? 0.13 : (tier === "low" ? 0.12 : 0.08)) : (isNear ? (mobile ? 0.24 : 0.18) : (mobile ? 0.46 : 0.36));
const interval = isVisible ? (mobile ? 0.13 : 0.08) : (isNear ? (mobile ? 0.24 : 0.18) : (mobile ? 0.46 : 0.36));
return { interval, lane: "panic", smoothMotion: Boolean(isVisible && !worldRef.isTarinaiHiddenInNestBox?.(tarinai)), visible: isVisible, urgent: true };
}
@ -104,10 +103,10 @@
if (/birth_ritual/i.test(id) || state === "birth_ritual" || (tarinai.birthRitualTimer || 0) > 0.04) {
lane = "birth";
interval = isVisible ? (tier === "low" ? 0.16 : 0.12) : 0.24;
interval = isVisible ? 0.12 : 0.24;
} else if (/approach_parent_or_child|follow_parent|approach_mate|seek_bed|play_ball|seek_material|build/i.test(id) || /follow_parent|seek_bed|play_ball/.test(state)) {
lane = "moving";
interval = isVisible ? (tier === "low" ? 0.14 : 0.10) : (isNear ? 0.22 : 0.55);
interval = isVisible ? 0.10 : (isNear ? 0.22 : 0.55);
} else if (/eat_food|eat/i.test(id) || state === "eat" || (tarinai.eatTimer || 0) > 0.04) {
lane = "eat";
interval = isVisible ? 0.22 : 0.48;
@ -116,16 +115,20 @@
interval = isVisible ? 0.42 : (isNear ? 0.70 : 1.10);
} else if (/idle|none/.test(id) || state === "idle") {
lane = "idle";
if (isVisible) interval = tier === "low" ? 0.24 : (count > 28 ? 0.16 : 0.12);
if (isVisible) interval = count > 28 ? 0.16 : 0.12;
else interval = isNear ? 0.55 : 1.10;
} else if (isVisible) {
lane = "visible";
interval = tier === "low" ? (count > 24 ? 0.18 : 0.14) : (count > 36 ? 0.11 : (count > 24 ? 0.08 : 0.05));
interval = count > 36 ? 0.11 : (count > 24 ? 0.08 : 0.05);
} else {
lane = isNear ? "near" : "far";
interval = isNear ? (tier === "low" ? 0.40 : 0.30) : (tier === "low" ? 1.20 : 0.90);
interval = isNear ? 0.30 : 0.90;
}
if (Number.isFinite(interval) && !/birth|eat|sleep/.test(lane)) {
if (count > 54) interval *= 1.26;
else if (count > 38) interval *= 1.14;
}
if (mobile && Number.isFinite(interval)) interval *= count > 24 ? 1.35 : 1.18;
const passivePhysicsNeed = hasPassivePhysicsNeed(tarinai, worldRef, isVisible);
const sleepPhysical = lane === "sleep" && passivePhysicsNeed;

View file

@ -1,8 +1,8 @@
"use strict";
(function () {
const APP_VERSION = "39.15.107";
const APP_BUILD = "tarinai-family-line-lanes-v39-15-107";
const APP_VERSION = "39.15.110";
const APP_BUILD = "tarinai-fps-cadence-v39-15-110";
const APP_CACHE_NAME = `tarinai-colony-${APP_VERSION}`;
const STATIC_VERSION_PARAM = `v=${APP_VERSION}`;

View file

@ -336,18 +336,38 @@ const RESET_PRESETS = Object.freeze({
addPresetItemsScattered(worldRef, "ball", presetCount(field, 6, 3, 10), center, { pad: 88, reason: "preset-happy-ball" });
}
function addDefaultPresetRobotCleaner(worldRef, presetId = "default") {
if (!worldRef || normalizeResetPresetId(presetId) === "empty") return;
if ((worldRef.items || []).some(item => item && !item.dead && item.type === "robot_cleaner")) return;
const center = { x0: worldRef.w * 0.16, x1: worldRef.w * 0.84, y0: worldRef.h * 0.16, y1: worldRef.h * 0.84 };
addPresetItemsScattered(worldRef, "robot_cleaner", 1, center, {
pad: 112,
reason: `preset-${normalizeResetPresetId(presetId)}-default-robot-cleaner`,
avoidOverlap: true,
avoidAllObstacles: true,
attempts: 140,
maxRadius: 260,
});
if ((worldRef.items || []).some(item => item && !item.dead && item.type === "robot_cleaner")) return;
const p = randPoint(worldRef, 88, center);
addPresetItem(worldRef, "robot_cleaner", p.x, p.y, { reason: `preset-${normalizeResetPresetId(presetId)}-default-robot-cleaner` });
}
function generatePresetWorld(worldRef, field, presetId, seedPopulation) {
switch (normalizeResetPresetId(presetId)) {
const preset = normalizeResetPresetId(presetId);
switch (preset) {
case "empty": return;
case "athletic": return generateAthleticPreset(worldRef, field);
case "crowded": return generateCrowdedPreset(worldRef, field);
case "family": return generateFamilyPreset(worldRef, field);
case "war": return generateWarPreset(worldRef, field);
case "happy": return generateHappyPreset(worldRef, field);
case "athletic": generateAthleticPreset(worldRef, field); break;
case "crowded": generateCrowdedPreset(worldRef, field); break;
case "family": generateFamilyPreset(worldRef, field); break;
case "war": generateWarPreset(worldRef, field); break;
case "happy": generateHappyPreset(worldRef, field); break;
case "default":
default:
return generateDefaultPreset(worldRef, field, seedPopulation);
generateDefaultPreset(worldRef, field, seedPopulation);
break;
}
addDefaultPresetRobotCleaner(worldRef, preset);
}
function groundForResetPreset(presetId = "default") {

View file

@ -116,6 +116,7 @@
},
scheduler: this._itemUpdateSchedulerStats || null,
creatures: this._creatureUpdateStats || null,
effects: this._effectUpdateStats || null,
terrain: this.terrainDirtyStatsThisFrame || null,
nearby: this.nearbyQueryStatsThisFrame || null,
constraintDirty: this.constraintDirtyStatsThisFrame || null,

View file

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